Merge lp:~mterry/ubuntu-ui-toolkit/blue-spinner into lp:ubuntu-ui-toolkit

Proposed by Michael Terry
Status: Superseded
Proposed branch: lp:~mterry/ubuntu-ui-toolkit/blue-spinner
Merge into: lp:ubuntu-ui-toolkit
Diff against target: 7431 lines (+5766/-504)
67 files modified
app-launch-profiler/app-launch-tracepoints.c (+2/-2)
components.api (+14/-3)
debian/control (+1/-0)
debian/control.gles (+1/-0)
debian/libubuntugestures5-dev.install (+0/-1)
debian/libubuntumetrics5-dev.install (+0/-1)
debian/libubuntutoolkit5-dev.install (+0/-1)
debian/rules (+1/-0)
documentation/overview.qdoc (+16/-0)
src/Ubuntu/Components/1.2/ActivityIndicator.qml (+5/-0)
src/Ubuntu/Components/1.3/ActionList.qml (+0/-47)
src/Ubuntu/Components/1.3/ActivityIndicator.qml (+5/-0)
src/Ubuntu/Components/ComponentModule.pro (+0/-1)
src/Ubuntu/Components/Popups/1.3/Popover.qml (+30/-25)
src/Ubuntu/Components/Popups/1.3/popupUtils.js (+5/-0)
src/Ubuntu/Components/Themes/Ambiance/1.2/ActivityIndicatorStyle.qml (+0/-2)
src/Ubuntu/Components/Themes/Ambiance/1.3/ActivityIndicatorStyle.qml (+0/-2)
src/Ubuntu/Components/Themes/Ambiance/1.3/ScrollbarStyle.qml (+49/-28)
src/Ubuntu/Components/Themes/Ambiance/1.3/ScrollingActionBarStyle.qml (+203/-0)
src/Ubuntu/Components/Themes/Ambiance/Ambiance.pro (+1/-0)
src/Ubuntu/Components/Themes/Ambiance/qmldir (+1/-0)
src/Ubuntu/Components/qmldir (+0/-1)
src/Ubuntu/Test/UbuntuTestCase13.qml (+1/-1)
src/Ubuntu/UbuntuMetrics/applicationmonitor.cpp (+3/-1)
src/Ubuntu/UbuntuToolkit/UbuntuToolkit.pro (+22/-4)
src/Ubuntu/UbuntuToolkit/actionlist.cpp (+158/-0)
src/Ubuntu/UbuntuToolkit/actionlist_p.h (+58/-0)
src/Ubuntu/UbuntuToolkit/exclusivegroup.cpp (+168/-0)
src/Ubuntu/UbuntuToolkit/exclusivegroup_p.h (+62/-0)
src/Ubuntu/UbuntuToolkit/menu.cpp (+671/-0)
src/Ubuntu/UbuntuToolkit/menu_p.h (+75/-0)
src/Ubuntu/UbuntuToolkit/menu_p_p.h (+95/-0)
src/Ubuntu/UbuntuToolkit/menubar.cpp (+340/-0)
src/Ubuntu/UbuntuToolkit/menubar_p.h (+65/-0)
src/Ubuntu/UbuntuToolkit/menubar_p_p.h (+78/-0)
src/Ubuntu/UbuntuToolkit/menugroup.cpp (+164/-0)
src/Ubuntu/UbuntuToolkit/menugroup_p.h (+60/-0)
src/Ubuntu/UbuntuToolkit/privates/splitviewhandler.cpp (+137/-0)
src/Ubuntu/UbuntuToolkit/privates/splitviewhandler_p_p.h (+52/-0)
src/Ubuntu/UbuntuToolkit/quickutils.cpp (+4/-2)
src/Ubuntu/UbuntuToolkit/quickutils_p.h (+1/-0)
src/Ubuntu/UbuntuToolkit/splitview.cpp (+675/-0)
src/Ubuntu/UbuntuToolkit/splitview_p.h (+160/-0)
src/Ubuntu/UbuntuToolkit/splitview_p_p.h (+154/-0)
src/Ubuntu/UbuntuToolkit/splitviewlayout.cpp (+252/-0)
src/Ubuntu/UbuntuToolkit/ubuntutoolkitmodule.cpp (+18/-4)
src/Ubuntu/UbuntuToolkit/ucaction.cpp (+186/-37)
src/Ubuntu/UbuntuToolkit/ucaction_p.h (+33/-7)
src/Ubuntu/UbuntuToolkit/uclistitem.cpp (+46/-22)
src/Ubuntu/UbuntuToolkit/ucmathutils_p.h (+4/-4)
tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_actionbar.py (+43/-11)
tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_flickable.py (+13/-13)
tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/MyDialog.qml (+36/-0)
tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_actionbar.ActionBarTestCase.qml (+13/-1)
tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_actionbar.ScrollingActionBarTestCase.qml (+79/-0)
tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_actionbar.py (+18/-8)
tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_popups.WindowTestCase.qml (+36/-0)
tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_popups.py (+19/-0)
tests/unit/components/tst_action.qml (+107/-1)
tests/unit/components/tst_activityindicator.qml (+7/-0)
tests/unit/components/tst_menu.qml (+137/-0)
tests/unit/visual/ScrollbarTestCase13.qml (+3/-0)
tests/unit/visual/tst_actionbar.13.qml (+299/-148)
tests/unit/visual/tst_scrollbar.13.qml (+238/-126)
tests/unit/visual/tst_splitview.13.qml (+383/-0)
tests/unit/visual/tst_splitview_page.13.qml (+143/-0)
tests/unit/visual/tst_splitview_repeater.13.qml (+116/-0)
To merge this branch: bzr merge lp:~mterry/ubuntu-ui-toolkit/blue-spinner
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Pending
Ubuntu SDK team Pending
Review via email: mp+295475@code.launchpad.net

This proposal has been superseded by a proposal from 2016-09-06.

Commit message

Make ActivityIndicator spinner blue instead of orange.

Description of the change

In my continuing quest to make Ubuntu color-consistent, I've gone ahead and updated the spinner asset from the old activity color (orange -- actually, we were using the old orange DD4814 instead of new orange E95420; tsk, tsk) to the new activity color (blue).

I just did this using Gimp's color exchange dialog. So no room for human error I hope. Looks good to me anyway.

To post a comment you must log in.
Revision history for this message
Michael Terry (mterry) wrote :

When talking with Paty about this, she said: "The activity indicator/spinner should also be blue, but I've just checked with Rae (<email address hidden> // irc: raeshambrook) who is the visual designer in charge of the colour palette and she said there were some issues with that spinner. So she is going to check this and confirm which is the right asset. Sooo... I'll come back to you as soon as she does."

So I'm waiting on that.

Revision history for this message
Cris Dywan (kalikiana) wrote :

Any update?

Revision history for this message
Michael Terry (mterry) wrote :

From the linked bug:

Rae Shambrook (raecontreras) wrote on 2016-06-16:
A new blue spinner asset has been given to Zsombor to implement

So I guess this is out of my hands. Maybe this MP should be marked rejected and Zsombor should be poked.

Though if I had known how long that would take, I would have argued for just landing this palette swap MP in the meantime...

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

> From the linked bug:
>
> Rae Shambrook (raecontreras) wrote on 2016-06-16:
> A new blue spinner asset has been given to Zsombor to implement
>
> So I guess this is out of my hands. Maybe this MP should be marked rejected
> and Zsombor should be poked.
>
> Though if I had known how long that would take, I would have argued for just
> landing this palette swap MP in the meantime...

These assets look exactly the same as the ones you gave me, so I'd say let's get it in

Revision history for this message
Michael Terry (mterry) wrote :

Ah, nm. Let's land this one.

<zsombi> mterry: well, my asset is nothing else than the previous one having the blue color, so in that sense if yours si the same, we can proceed with that :)
<mterry> Michael Terry zsombi: seriously? GUH! I had palette-swapped the existing asset in gimp like months ago and then Design said, "wait, we want to change the asset too"
<zsombi> mterry: I am patching the progress bar visuals right now, and had no plans to have the new activity indicator design, only the color - eventually
<mterry> Michael Terry zsombi: so yours looks like http://bazaar.launchpad.net/~mterry/ubuntu-ui-toolkit/blue-spinner/download/head:/spinner30.png-20130227062500-zxb8nbsbac23o3la-1/spinner%4030.png ?
<zsombi> mterry: they wanted us to rewrite teh whole component, with some fancy animation here'n'there, but just told them it's not gonna happen, there are way many more compponents that need love but the ActivityIndicator... so I got a new asset :D
<zsombi> mterry: haha, it does, let's get yours in then :)

1324. By Michael Terry

Merge staging

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'app-launch-profiler/app-launch-tracepoints.c'
2--- app-launch-profiler/app-launch-tracepoints.c 2016-08-16 08:43:28 +0000
3+++ app-launch-profiler/app-launch-tracepoints.c 2016-09-06 13:08:47 +0000
4@@ -28,8 +28,8 @@
5
6 int main (int argc, char* argv[])
7 {
8- #pragma unused(argc)
9- #pragma unused(argv)
10+ (void)argc;
11+ (void)argv;
12 tracepoint(app,invokeApplauncher);
13 return 0;
14 }
15
16=== modified file 'components.api'
17--- components.api 2016-08-09 03:48:53 +0000
18+++ components.api 2016-09-06 13:08:47 +0000
19@@ -10,13 +10,17 @@
20 readonly property bool pressed
21 readonly property UCMargins sensingMargins
22 Ubuntu.Components.Action 1.3 1.0 0.1 UCAction: QtObject
23+ property bool checkable 1.3
24+ property bool checked 1.3
25 property string description
26 property bool enabled
27+ property ExclusiveGroup exclusiveGroup 1.3
28 property string iconName
29 property url iconSource
30 property Component itemHint
31 property string keywords
32 signal triggered(var value)
33+ signal toggled(bool value) 1.3
34 function trigger(var value)
35 function trigger()
36 property string name
37@@ -59,9 +63,12 @@
38 Ubuntu.Components.ActionList 1.0 0.1: QtObject
39 property list<Action> actions
40 default property list<Action> children
41-Ubuntu.Components.ActionList 1.3: QtObject
42- property list<Action> actions
43- default property list<Action> children
44+Ubuntu.Components.ActionList 1.3 ActionList: QtObject
45+ default property list<Action> actions
46+ signal added(Action action)
47+ signal removed(Action action)
48+ function addAction(Action action)
49+ function removeAction(Action action)
50 Ubuntu.Components.ActionManager 1.0 0.1 UCActionManager: QtObject
51 default property list<Action> actions
52 readonly property ActionContext globalContext
53@@ -458,6 +465,10 @@
54 OperationPending
55 Ubuntu.Metrics.Event: Enum
56 UserInterfaceReady
57+Ubuntu.Components.ExclusiveGroup 1.3 ExclusiveGroup: ActionList
58+ readonly property QtObject current
59+ function bindCheckable(QtObject object)
60+ function unbindCheckable(QtObject object)
61 Ubuntu.Components.ListItems.Expandable 1.0 0.1: Empty
62 property bool collapseOnClick
63 property double collapsedHeight
64
65=== modified file 'debian/control'
66--- debian/control 2016-08-18 16:51:24 +0000
67+++ debian/control 2016-09-06 13:08:47 +0000
68@@ -77,6 +77,7 @@
69 qml-module-qtquick-layouts,
70 qml-module-qtquick-window2,
71 qml-module-qtquick2,
72+ qml-module-ubuntu-components-labs,
73 qml-module-ubuntu-performancemetrics,
74 qtdeclarative5-unity-action-plugin (>= 1.1.0),
75 suru-icon-theme,
76
77=== modified file 'debian/control.gles'
78--- debian/control.gles 2016-07-28 10:54:13 +0000
79+++ debian/control.gles 2016-09-06 13:08:47 +0000
80@@ -103,6 +103,7 @@
81 qml-module-qtquick-layouts,
82 qml-module-qtquick-window2,
83 qml-module-qtquick2,
84+ qml-module-ubuntu-components-labs,
85 qml-module-ubuntu-performancemetrics-gles,
86 qtdeclarative5-unity-action-plugin (>= 1.1.0),
87 suru-icon-theme,
88
89=== modified file 'debian/libubuntugestures5-dev.install'
90--- debian/libubuntugestures5-dev.install 2016-07-06 11:27:11 +0000
91+++ debian/libubuntugestures5-dev.install 2016-09-06 13:08:47 +0000
92@@ -5,7 +5,6 @@
93 usr/include/*/qt5/UbuntuGestures/ubuntugesturesglobal.h
94 usr/include/*/qt5/UbuntuGestures/ubuntugesturesmodule.h
95 usr/include/*/qt5/UbuntuGestures/ubuntugesturesversion.h
96-usr/lib/*/libUbuntuGestures.la
97 usr/lib/*/libUbuntuGestures.prl
98 usr/lib/*/libUbuntuGestures.so
99 usr/lib/*/pkgconfig/UbuntuGestures.pc
100
101=== modified file 'debian/libubuntumetrics5-dev.install'
102--- debian/libubuntumetrics5-dev.install 2016-07-26 16:33:27 +0000
103+++ debian/libubuntumetrics5-dev.install 2016-09-06 13:08:47 +0000
104@@ -6,7 +6,6 @@
105 usr/include/*/qt5/UbuntuMetrics/logger.h
106 usr/include/*/qt5/UbuntuMetrics/ubuntumetricsglobal.h
107 usr/include/*/qt5/UbuntuMetrics/ubuntumetricsversion.h
108-usr/lib/*/libUbuntuMetrics.la
109 usr/lib/*/libUbuntuMetrics.prl
110 usr/lib/*/libUbuntuMetrics.so
111 usr/lib/*/pkgconfig/UbuntuMetrics.pc
112
113=== modified file 'debian/libubuntutoolkit5-dev.install'
114--- debian/libubuntutoolkit5-dev.install 2016-07-07 07:21:48 +0000
115+++ debian/libubuntutoolkit5-dev.install 2016-09-06 13:08:47 +0000
116@@ -5,7 +5,6 @@
117 usr/include/*/qt5/UbuntuToolkit/ubuntutoolkitglobal.h
118 usr/include/*/qt5/UbuntuToolkit/ubuntutoolkitmodule.h
119 usr/include/*/qt5/UbuntuToolkit/ubuntutoolkitversion.h
120-usr/lib/*/libUbuntuToolkit.la
121 usr/lib/*/libUbuntuToolkit.prl
122 usr/lib/*/libUbuntuToolkit.so
123 usr/lib/*/pkgconfig/UbuntuToolkit.pc
124
125=== modified file 'debian/rules'
126--- debian/rules 2016-07-07 18:47:22 +0000
127+++ debian/rules 2016-09-06 13:08:47 +0000
128@@ -43,6 +43,7 @@
129 mkdir -p debian/tmp/`qmake -query QT_INSTALL_DOCS`/qch
130 mkdir -p debian/tmp/usr/share/ubuntu-ui-toolkit/doc
131 cp -r $(CURDIR)/documentation/*qch debian/tmp/`qmake -query QT_INSTALL_DOCS`/qch
132+ rm -f debian/tmp/usr/lib/$(DEB_HOST_MULTIARCH)/*.la
133 # FIXME: Due to autopilot not being in the archive we ship docs for now
134 # sphinx-build -b singlehtml documentation/autopilot-helpers documentation/autopilot-helpers/_build/html
135 # sphinx-build -b json documentation/autopilot-helpers documentation/autopilot-helpers/_build/json
136
137=== modified file 'documentation/overview.qdoc'
138--- documentation/overview.qdoc 2016-07-27 15:40:52 +0000
139+++ documentation/overview.qdoc 2016-09-06 13:08:47 +0000
140@@ -129,6 +129,22 @@
141 \endcode
142 \annotatedlist ubuntu-services
143
144+ \part Performance Metrics
145+ Available through:
146+ \code
147+ import Ubuntu.PerformanceMetrics 1.0
148+ \endcode
149+ \annotatedlist ubuntu-performance-metrics
150+
151+ \part Labs
152+ The Labs module contains a set of components which have unstable API. Those
153+ should not be used in applications as their interface may change any time.
154+ Available through:
155+ \code
156+ import Ubuntu.Components.Labs 1.0
157+ \endcode
158+ \annotatedlist ubuntu-labs
159+
160 \part Test extensions
161 Available through:
162 \code
163
164=== modified file 'src/Ubuntu/Components/1.2/ActivityIndicator.qml'
165--- src/Ubuntu/Components/1.2/ActivityIndicator.qml 2016-08-17 14:42:42 +0000
166+++ src/Ubuntu/Components/1.2/ActivityIndicator.qml 2016-09-06 13:08:47 +0000
167@@ -52,5 +52,10 @@
168 */
169 property bool running: false
170
171+ implicitWidth: units.gu(3)
172+ implicitHeight: units.gu(3)
173+ width: units.gu(3)
174+ height: units.gu(3)
175+
176 style: Theme.createStyleComponent("ActivityIndicatorStyle.qml", indicator)
177 }
178
179=== removed file 'src/Ubuntu/Components/1.3/ActionList.qml'
180--- src/Ubuntu/Components/1.3/ActionList.qml 2016-05-25 12:48:10 +0000
181+++ src/Ubuntu/Components/1.3/ActionList.qml 1970-01-01 00:00:00 +0000
182@@ -1,47 +0,0 @@
183-/*
184- * Copyright 2012 Canonical Ltd.
185- *
186- * This program is free software; you can redistribute it and/or modify
187- * it under the terms of the GNU Lesser General Public License as published by
188- * the Free Software Foundation; version 3.
189- *
190- * This program is distributed in the hope that it will be useful,
191- * but WITHOUT ANY WARRANTY; without even the implied warranty of
192- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
193- * GNU Lesser General Public License for more details.
194- *
195- * You should have received a copy of the GNU Lesser General Public License
196- * along with this program. If not, see <http://www.gnu.org/licenses/>.
197- */
198-
199-import QtQuick 2.4
200-import Ubuntu.Components 1.3
201-
202-/*!
203- \qmltype ActionList
204- \inqmlmodule Ubuntu.Components
205- \ingroup ubuntu
206- \brief List of \l Action items
207-*/
208-
209-QtObject {
210- id: list
211- // internal objects using nested elements,
212- // which isn't allowed by QtObject; this fix makes this possible
213- /*!
214- Default property to allow adding of children.
215- \qmlproperty list<Action> children
216- \default
217- */
218- default property alias children: list.actions
219-
220- /*!
221- List of already defined actions when not defining them as children of the ActionList.
222- Note that when you set this property, the children of the ActionList will be ignored,
223- so do not set the list and define children.
224-
225- The advantage of setting actions over using the children is that the same
226- \l Action items can be used in several sets of actions.
227- */
228- property list<Action> actions
229-}
230
231=== modified file 'src/Ubuntu/Components/1.3/ActivityIndicator.qml'
232--- src/Ubuntu/Components/1.3/ActivityIndicator.qml 2016-08-17 14:42:42 +0000
233+++ src/Ubuntu/Components/1.3/ActivityIndicator.qml 2016-09-06 13:08:47 +0000
234@@ -52,5 +52,10 @@
235 */
236 property bool running: false
237
238+ implicitWidth: units.gu(3)
239+ implicitHeight: units.gu(3)
240+ width: units.gu(3)
241+ height: units.gu(3)
242+
243 styleName: "ActivityIndicatorStyle"
244 }
245
246=== modified file 'src/Ubuntu/Components/ComponentModule.pro'
247--- src/Ubuntu/Components/ComponentModule.pro 2016-07-29 13:21:05 +0000
248+++ src/Ubuntu/Components/ComponentModule.pro 2016-09-06 13:08:47 +0000
249@@ -78,7 +78,6 @@
250
251 #1.3
252 QML_FILES += 1.3/ActionBar.qml \
253- 1.3/ActionList.qml \
254 1.3/ActivityIndicator.qml \
255 1.3/AdaptivePageLayout.qml \
256 1.3/AnimatedItem.qml \
257
258=== modified file 'src/Ubuntu/Components/Popups/1.3/Popover.qml'
259--- src/Ubuntu/Components/Popups/1.3/Popover.qml 2016-03-17 17:40:51 +0000
260+++ src/Ubuntu/Components/Popups/1.3/Popover.qml 2016-09-06 13:08:47 +0000
261@@ -33,9 +33,8 @@
262 \qml
263 import QtQuick 2.4
264 import Ubuntu.Components 1.3
265- import Ubuntu.Components.ListItems 1.3 as ListItem
266 import Ubuntu.Components.Popups 1.3
267-
268+ import Ubuntu.Components.ListItems 1.3 as Old_ListItem
269 Rectangle {
270 color: theme.palette.normal.background
271 width: units.gu(80)
272@@ -52,29 +51,35 @@
273 top: parent.top
274 right: parent.right
275 }
276- ListItem.Header { text: "Standard list items" }
277- ListItem.Standard { text: "Do something" }
278- ListItem.Standard { text: "Do something else" }
279- ListItem.Header { text: "Buttons" }
280- ListItem.SingleControl {
281- highlightWhenPressed: false
282- control: Button {
283- text: "Do nothing"
284- anchors {
285- fill: parent
286- margins: units.gu(1)
287- }
288- }
289- }
290- ListItem.SingleControl {
291- highlightWhenPressed: false
292- control: Button {
293- text: "Close"
294- anchors {
295- fill: parent
296- margins: units.gu(1)
297- }
298- onClicked: PopupUtils.close(popover)
299+
300+ // there is no equivalent yet to ListItem.Header
301+ Old_ListItem.Header { text: "Standard list items" }
302+
303+ ListItem {
304+ // shall specify the height when Using ListItemLayout inside ListItem
305+ height: somethingLayout.height + (divider.visible ? divider.height : 0)
306+ ListItemLayout {
307+ id: somethingLayout
308+ title.text: "Do somethings"
309+ }
310+ onClicked: console.log("clicked on ListItem with onClicked implemented")
311+ }
312+ ListItem {
313+ // shall specify the height when Using ListItemLayout inside ListItem
314+ height: somethingElseLayout.height + (divider.visible ? divider.height : 0)
315+ ListItemLayout {
316+ id: somethingElseLayout
317+ title.text: "Do somethings"
318+ subtitle.text: "else"
319+ }
320+ }
321+ ListItem {
322+ // shall specify the height when Using ListItemLayout inside ListItem
323+ height: closeBtn.height + (divider.visible ? divider.height : 0)
324+ Button {
325+ id: closeBtn
326+ text: "Close button"
327+ onClicked: PopupUtils.close(popover);
328 }
329 }
330 }
331
332=== modified file 'src/Ubuntu/Components/Popups/1.3/popupUtils.js'
333--- src/Ubuntu/Components/Popups/1.3/popupUtils.js 2016-04-13 19:32:20 +0000
334+++ src/Ubuntu/Components/Popups/1.3/popupUtils.js 2016-09-06 13:08:47 +0000
335@@ -60,6 +60,11 @@
336 return null;
337 }
338
339+ if (!rootObject) {
340+ print("PopupUtils.open(): Failed to get the root object.");
341+ return null;
342+ }
343+
344 var popupObject;
345 if (params !== undefined) {
346 popupObject = popupComponent.createObject(rootObject, params);
347
348=== modified file 'src/Ubuntu/Components/Themes/Ambiance/1.2/ActivityIndicatorStyle.qml'
349--- src/Ubuntu/Components/Themes/Ambiance/1.2/ActivityIndicatorStyle.qml 2016-08-17 14:42:42 +0000
350+++ src/Ubuntu/Components/Themes/Ambiance/1.2/ActivityIndicatorStyle.qml 2016-09-06 13:08:47 +0000
351@@ -20,8 +20,6 @@
352 Image {
353 id: container
354
355- implicitWidth: units.gu(3)
356- implicitHeight: units.gu(3)
357 smooth: true
358 visible: styledItem.running
359 fillMode: Image.PreserveAspectFit
360
361=== modified file 'src/Ubuntu/Components/Themes/Ambiance/1.3/ActivityIndicatorStyle.qml'
362--- src/Ubuntu/Components/Themes/Ambiance/1.3/ActivityIndicatorStyle.qml 2016-08-17 14:42:42 +0000
363+++ src/Ubuntu/Components/Themes/Ambiance/1.3/ActivityIndicatorStyle.qml 2016-09-06 13:08:47 +0000
364@@ -20,8 +20,6 @@
365 Image {
366 id: container
367
368- implicitWidth: units.gu(3)
369- implicitHeight: units.gu(3)
370 smooth: true
371 visible: styledItem.running && styledItem.visible
372 fillMode: Image.PreserveAspectFit
373
374=== modified file 'src/Ubuntu/Components/Themes/Ambiance/1.3/ScrollbarStyle.qml'
375--- src/Ubuntu/Components/Themes/Ambiance/1.3/ScrollbarStyle.qml 2016-08-02 11:18:34 +0000
376+++ src/Ubuntu/Components/Themes/Ambiance/1.3/ScrollbarStyle.qml 2016-09-06 13:08:47 +0000
377@@ -316,7 +316,9 @@
378 property string propPosRatio: (isVertical) ? "yPosition" : "xPosition"
379 property string propSizeRatio: (isVertical) ? "heightRatio" : "widthRatio"
380 property string propCoordinate: (isVertical) ? "y" : "x"
381+ property string otherPropCoordinate: (isVertical) ? "x" : "y"
382 property string propSize: (isVertical) ? "height" : "width"
383+ property string otherPropSize: (isVertical) ? "width" : "height"
384 property string propAtBeginning: (isVertical) ? "atYBeginning" : "atXBeginning"
385 property string propAtEnd: (isVertical) ? "atYEnd" : "atXEnd"
386
387@@ -622,8 +624,6 @@
388 property bool lockDrag: false
389
390 property bool hoveringThumb: false
391- property string scrollingProp: isVertical ? "y" : "x"
392- property string sizeProp: isVertical ? "height" : "width"
393
394 function initDrag(isMouse) {
395 __disableStateBinding = true
396@@ -648,20 +648,29 @@
397 if (!thumbArea.pressed) return
398
399 var mouseScrollingProp = isVertical ? mouseY : mouseX
400- if (mouseScrollingProp < slider[scrollingProp]) {
401+ if (mouseScrollingProp < slider[scrollbarUtils.propCoordinate]) {
402 scrollBy(-pageSize*visuals.longScrollingRatio, true)
403- } else if (mouseScrollingProp > slider[scrollingProp] + slider[sizeProp]) {
404+ } else if (mouseScrollingProp > slider[scrollbarUtils.propCoordinate] + slider[scrollbarUtils.propSize]) {
405 scrollBy(pageSize*visuals.longScrollingRatio, true)
406 }
407 }
408
409+ //we consider hover if it's inside the TROUGH along the scrolling axis
410+ //and inside the THUMB along the non-scrolling axis
411+ //NOTE: mouseX and mouseY are assumed to be relative to thumbArea
412 function handleHover(mouseX, mouseY) {
413+ //NOTE: we're assuming thumbArea has same size/position as the trough.
414+ //Use mapToItem to map the coordinates if that assumption falls (i.e. to implement
415+ //a larger touch detection area around the thumb)
416 var mouseScrollingProp = isVertical ? mouseY : mouseX
417- //don't count as hover if the user is already press-and-holding the trough to
418- //scroll page by page
419+ var otherProp = isVertical ? mouseX : mouseY
420+
421+ //don't count as hover if the user is already press-and-holding
422+ //the trough to scroll page by page
423 hoveringThumb = !(pressHoldTimer.running && pressHoldTimer.startedBy === thumbArea)
424- && mouseScrollingProp >= slider[scrollingProp] &&
425- mouseScrollingProp <= slider[scrollingProp] + slider[sizeProp]
426+ && mouseScrollingProp >= slider[scrollbarUtils.propCoordinate] &&
427+ mouseScrollingProp <= slider[scrollbarUtils.propCoordinate] + slider[scrollbarUtils.propSize] &&
428+ otherProp >= 0 && otherProp <= trough[scrollbarUtils.otherPropSize]
429 }
430
431 function saveFlickableScrollingState() {
432@@ -691,14 +700,9 @@
433 previousY = mouse.y
434 }
435
436- anchors {
437- fill: trough
438- // set margins adding 2 dp for error area
439- leftMargin: (!isVertical || frontAligned) ? 0 : units.dp(-2)
440- rightMargin: (!isVertical || rearAligned) ? 0 : units.dp(-2)
441- topMargin: (isVertical || topAligned) ? 0 : units.dp(-2)
442- bottomMargin: (isVertical || bottomAligned) ? 0 : units.dp(-2)
443- }
444+ //NOTE: remember to update the handleHover check if you add anchor margins
445+ anchors.fill: trough
446+
447 enabled: isScrollable && interactive && __canFitSteppersAndShorterThumb
448 onPressed: {
449 cacheMousePosition(mouse)
450@@ -710,8 +714,8 @@
451 if (firstStepper.visible) {
452 var mouseScrollingProp = isVertical ? mouseY : mouseX
453 //don't start the press and hold timer to avoid conflicts with the drag
454- if (mouseScrollingProp < slider[scrollingProp] ||
455- mouseScrollingProp > (slider[scrollingProp] + slider[sizeProp])) {
456+ if (mouseScrollingProp < slider[scrollbarUtils.propCoordinate] ||
457+ mouseScrollingProp > (slider[scrollbarUtils.propCoordinate] + slider[scrollbarUtils.propSize])) {
458 handlePress(mouseX, mouseY)
459 pressHoldTimer.startTimer(thumbArea)
460 } else {
461@@ -761,7 +765,8 @@
462 pressHoldTimer.stop()
463 }
464 onReleased: {
465- handleHover(mouseX, mouseY)
466+ //don't call handleHover here as touch release also triggers this handler
467+ //see bug #1616868
468 resetDrag()
469 pressHoldTimer.stop()
470 }
471@@ -797,18 +802,29 @@
472
473 //logic to support different colour on hovering
474 hoverEnabled: true
475+ //This means this mouse filter will only handle real mouse events!
476+ //i.e. the synthesized mouse events created when you use
477+ //touchscreen will not trigger it! This way we can have logic that
478+ //will not trigger when using touch
479+ Mouse.ignoreSynthesizedEvents: true
480 Mouse.enabled: true
481- Mouse.ignoreSynthesizedEvents: true
482 Mouse.onEntered: {
483 hoveringThumb = false
484 handleHover(event.x, event.y)
485 }
486 Mouse.onPositionChanged: {
487+ //We need to update the hover state also onPosChanged because this area
488+ //covers the whole trough, not just the thumb, so entered/exited are not enough
489+ //e.g. when mouse moves from inside the thumb to inside the trough, or when you
490+ //click on the trough and the thumb scrolls and goes under the mouse cursor
491 handleHover(mouse.x, mouse.y)
492 }
493 Mouse.onExited: {
494 hoveringThumb = false
495 }
496+ Mouse.onReleased: {
497+ handleHover(mouse.x, mouse.y)
498+ }
499
500 Timer {
501 id: pressHoldTimer
502@@ -845,6 +861,7 @@
503
504 MouseArea {
505 id: steppersMouseArea
506+ objectName: "steppersMouseArea"
507 //size is handled by the states
508
509 property bool hoveringFirstStepper: false
510@@ -897,12 +914,7 @@
511 hoveringFirstStepper = false
512 hoveringSecondStepper = false
513 }
514-
515- //We don't change the size of the images because we let the image reader figure the size out,
516- //though that means we have to hide the images somehow while the mousearea is visible but has
517- //null size. We choose to enable clipping here instead of creating bindings on images' visible prop
518- clip: true
519- onPressed: {
520+ Mouse.onPressed: {
521 //This additional trigger of the hovering logic is useful especially in testing
522 //environments, where simulating a click on the first stepper will generate onEntered,
523 //but then clicking on the second one (without a mouseMove) won't, because they're
524@@ -910,7 +922,16 @@
525 //we ensure that the hovering logic is correct even when there are no mouse moves between
526 //clicks on different steppers (like it happens in tst_actionSteppers test).
527 handleHover(mouse)
528+ }
529+ Mouse.onReleased: {
530+ handleHover(mouse)
531+ }
532
533+ //We don't change the size of the images because we let the image reader figure the size out,
534+ //though that means we have to hide the images somehow while the mousearea is visible but has
535+ //null size. We choose to enable clipping here instead of creating bindings on images' visible prop
536+ clip: true
537+ onPressed: {
538 handlePress()
539 pressHoldTimer.startTimer(steppersMouseArea)
540 }
541@@ -952,7 +973,7 @@
542 anchors.centerIn: parent
543 width: __stepperAssetWidth
544 rotation: isVertical ? 180 : 90
545- //NOTE: Qt.resolvedUrl was removed because it does not seem to be needed and
546+ //NOTE: Qt.resolvedUrl was removed because it does not seem to be needed and
547 //the QML profiler showed it's relatively expensive
548 source: visible ? "../artwork/toolkit_scrollbar-stepper.svg" : ""
549 asynchronous: true
550@@ -998,7 +1019,7 @@
551 anchors.centerIn: parent
552 width: __stepperAssetWidth
553 rotation: isVertical ? 0 : -90
554- //NOTE: Qt.resolvedUrl was removed because it does not seem to be needed and
555+ //NOTE: Qt.resolvedUrl was removed because it does not seem to be needed and
556 //the QML profiler showed it's relatively expensive
557 source: visible ? "../artwork/toolkit_scrollbar-stepper.svg" : ""
558 asynchronous: true
559
560=== added file 'src/Ubuntu/Components/Themes/Ambiance/1.3/ScrollingActionBarStyle.qml'
561--- src/Ubuntu/Components/Themes/Ambiance/1.3/ScrollingActionBarStyle.qml 1970-01-01 00:00:00 +0000
562+++ src/Ubuntu/Components/Themes/Ambiance/1.3/ScrollingActionBarStyle.qml 2016-09-06 13:08:47 +0000
563@@ -0,0 +1,203 @@
564+/*
565+ * Copyright 2016 Canonical Ltd.
566+ *
567+ * This program is free software; you can redistribute it and/or modify
568+ * it under the terms of the GNU Lesser General Public License as published by
569+ * the Free Software Foundation; version 3.
570+ *
571+ * This program is distributed in the hope that it will be useful,
572+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
573+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
574+ * GNU Lesser General Public License for more details.
575+ *
576+ * You should have received a copy of the GNU Lesser General Public License
577+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
578+ */
579+import QtQuick 2.4
580+import Ubuntu.Components 1.3
581+import Ubuntu.Components.Styles 1.3 as Style
582+
583+Style.ActionBarStyle {
584+ id: actionBarStyle
585+ implicitHeight: units.gu(5)
586+
587+ clip: true // avoid showing the icons that are scrolled out of the view.
588+ backgroundColor: theme.palette.normal.background
589+ buttons {
590+ foregroundColor: theme.palette.normal.backgroundText
591+ pressedForegroundColor: buttons.foregroundColor
592+ disabledForegroundColor: theme.palette.disabled.backgroundText
593+ backgroundColor: "transparent" // background is already colored
594+ pressedBackgroundColor: theme.palette.highlighted.background
595+ disabledBackgroundColor: buttons.backgroundColor
596+ }
597+
598+ property QtObject scrollButtons: Style.ActionItemProperties {
599+ foregroundColor: actionBarStyle.buttons.foregroundColor
600+ pressedForegroundColor: actionBarStyle.buttons.pressedForegroundColor
601+ backgroundColor: actionBarStyle.backgroundColor // must be opaque to hide the icon buttons
602+ pressedBackgroundColor: actionBarStyle.buttons.pressedBackgroundColor
603+ property real width: units.gu(4)
604+ property int fadeDuration: UbuntuAnimation.FastDuration
605+ }
606+
607+ /*!
608+ The default action delegate if the styled item does
609+ not provide a delegate.
610+ */
611+ // FIXME: This ListItem { AbstractButton { } } construction can be avoided
612+ // when StyledItem supports cursor keys navigation, see bug #1573616.
613+ // FIXME: For the first and last item in the list, it is possible to move
614+ // the focus inside the ListItem to the AbstractButton, see bug #1590005.
615+ // FIXME: Focus can go on disabled item. See bug #1611327.
616+ defaultDelegate: ListItem {
617+ width: units.gu(4)
618+ height: actionBarStyle.height
619+ enabled: modelData.enabled
620+ objectName: modelData.objectName + "_button"
621+ AbstractButton {
622+ id: button
623+ anchors.fill: parent
624+ style: IconButtonStyle {
625+ foregroundColor: button.pressed ?
626+ actionBarStyle.buttons.pressedForegroundColor :
627+ button.enabled ?
628+ actionBarStyle.buttons.foregroundColor :
629+ actionBarStyle.buttons.disabledForegroundColor
630+ backgroundColor: button.pressed ?
631+ actionBarStyle.buttons.pressedBackgroundColor :
632+ button.enabled ?
633+ actionBarStyle.buttons.backgroundColor :
634+ actionBarStyle.buttons.disabledBackgroundColor
635+ }
636+ action: modelData
637+ activeFocusOnTab: false
638+ }
639+ divider.visible: false
640+ }
641+
642+ Rectangle {
643+ color: actionBarStyle.backgroundColor
644+ id: listViewContainer
645+ anchors.fill: parent
646+
647+ property var visibleActions: getVisibleActions(styledItem.actions)
648+ function getVisibleActions(actions) {
649+ var visibleActionList = [];
650+ for (var i = 0; i < actions.length; i++) {
651+ var action = actions[i];
652+ if (action && action.hasOwnProperty("visible") && action.visible) {
653+ visibleActionList.push(action);
654+ }
655+ }
656+ return visibleActionList;
657+ }
658+
659+ ListView {
660+ id: actionsListView
661+ objectName: "actions_listview"
662+ anchors {
663+ right: parent.right
664+ top: parent.top
665+ bottom: parent.bottom
666+ rightMargin: scrollButtons.width
667+ }
668+ displayMarginBeginning: scrollButtons.width
669+ displayMarginEnd: scrollButtons.width
670+ leftMargin: -scrollButtons.width
671+ rightMargin: -scrollButtons.width
672+ width: listViewContainer.width - 2*scrollButtons.width
673+ layoutDirection: Qt.RightToLeft
674+
675+ orientation: ListView.Horizontal
676+ boundsBehavior: Flickable.StopAtBounds
677+ delegate: styledItem.delegate
678+ model: listViewContainer.visibleActions
679+
680+ // This gives the correct behavior when scrollButtons.width is
681+ // larger/smaller/equal compared to the width of the delegate.
682+ onCurrentIndexChanged: positionViewAtIndex(currentIndex, ListView.Contain)
683+
684+ SmoothedAnimation {
685+ objectName: "actions_scroll_animation"
686+ id: contentXAnim
687+ target: actionsListView
688+ property: "contentX"
689+ duration: UbuntuAnimation.FastDuration
690+ }
691+
692+ // direction == 1 to show more icons on the left
693+ // direction == -1 to show more icons on the right
694+ function scroll(direction) {
695+ if (contentXAnim.running) contentXAnim.stop();
696+ var newContentX = actionsListView.contentX - (actionsListView.width * direction);
697+ contentXAnim.from = actionsListView.contentX;
698+ // make sure we don't overshoot bounds
699+ contentXAnim.to = MathUtils.clamp(
700+ newContentX,
701+ originX + scrollButtons.width,
702+ actionsListView.originX + actionsListView.contentWidth
703+ - actionsListView.width - scrollButtons.width);
704+ contentXAnim.start();
705+ }
706+ }
707+
708+ Component {
709+ id: scrollButtonComponent
710+ AbstractButton {
711+ id: scrollButton
712+ width: actionBarStyle.scrollButtons.width
713+ enabled: opacity === 1.0
714+ onClicked: actionsListView.scroll(scrollDirection)
715+ opacity: buttonOpacity
716+ objectName: buttonName
717+ Behavior on opacity {
718+ UbuntuNumberAnimation {
719+ duration: actionBarStyle.scrollButtons.fadeDuration
720+ }
721+ }
722+ Rectangle {
723+ anchors.fill: parent
724+ color: scrollButton.pressed ? actionBarStyle.scrollButtons.pressedBackgroundColor
725+ : actionBarStyle.scrollButtons.backgroundColor
726+ }
727+ Icon {
728+ // FIXME: Use new theme icon from
729+ // https://code.launchpad.net/~tiheum/ubuntu-themes/toolkit-arrows/+merge/298609
730+ // after it lands in overlay and archive.
731+ anchors.centerIn: parent
732+ width: units.gu(1)
733+ height: units.gu(1)
734+ rotation: iconRotation
735+ name: "chevron"
736+ color: scrollButton.pressed ? actionBarStyle.scrollButtons.pressedForegroundColor
737+ : actionBarStyle.scrollButtons.foregroundColor
738+ }
739+ }
740+ }
741+ Loader {
742+ anchors {
743+ left: parent.left
744+ top: parent.top
745+ bottom: parent.bottom
746+ }
747+ sourceComponent: scrollButtonComponent
748+ property int iconRotation: 180
749+ property real buttonOpacity: actionsListView.atXBeginning ? 0.0 : 1.0
750+ property int scrollDirection: 1
751+ property string buttonName: "leading_scroll_button"
752+ }
753+ Loader {
754+ anchors {
755+ right: parent.right
756+ top: parent.top
757+ bottom: parent.bottom
758+ }
759+ sourceComponent: scrollButtonComponent
760+ property int iconRotation: 0
761+ property real buttonOpacity: actionsListView.atXEnd ? 0.0 : 1.0
762+ property int scrollDirection: -1
763+ property string buttonName: "trailing_scroll_button"
764+ }
765+ }
766+}
767
768=== modified file 'src/Ubuntu/Components/Themes/Ambiance/Ambiance.pro'
769--- src/Ubuntu/Components/Themes/Ambiance/Ambiance.pro 2016-04-21 11:34:55 +0000
770+++ src/Ubuntu/Components/Themes/Ambiance/Ambiance.pro 2016-09-06 13:08:47 +0000
771@@ -103,6 +103,7 @@
772 1.3/ProgressionVisualStyle.qml \
773 1.3/PullToRefreshStyle.qml \
774 1.3/ScrollbarStyle.qml \
775+ 1.3/ScrollingActionBarStyle.qml \
776 1.3/SectionsStyle.qml \
777 1.3/SheetForegroundStyle.qml \
778 1.3/SliderStyle.qml \
779
780=== modified file 'src/Ubuntu/Components/Themes/Ambiance/artwork/spinner@30.png'
781Binary files src/Ubuntu/Components/Themes/Ambiance/artwork/spinner@30.png 2013-08-07 12:21:32 +0000 and src/Ubuntu/Components/Themes/Ambiance/artwork/spinner@30.png 2016-09-06 13:08:47 +0000 differ
782=== modified file 'src/Ubuntu/Components/Themes/Ambiance/artwork/spinner@8.png'
783Binary files src/Ubuntu/Components/Themes/Ambiance/artwork/spinner@8.png 2013-08-07 12:21:32 +0000 and src/Ubuntu/Components/Themes/Ambiance/artwork/spinner@8.png 2016-09-06 13:08:47 +0000 differ
784=== modified file 'src/Ubuntu/Components/Themes/Ambiance/qmldir'
785--- src/Ubuntu/Components/Themes/Ambiance/qmldir 2016-02-19 11:18:51 +0000
786+++ src/Ubuntu/Components/Themes/Ambiance/qmldir 2016-09-06 13:08:47 +0000
787@@ -59,6 +59,7 @@
788 #version 1.3
789 AmbianceNormal 1.3 ./1.3/AmbianceNormal.qml
790 ActionBarStyle 1.3 ./1.3/ActionBarStyle.qml
791+ScrollingActionBarStyle 1.3 ./1.3/ScrollingActionBarStyle.qml
792 MainViewStyle 1.3 ./1.3/MainViewStyle.qml
793 ListItemOptionSelector 1.3 ./1.3/ListItemOptionSelector.qml
794 OptionSelectorStyle 1.3 ./1.3/OptionSelectorStyle.qml
795
796=== modified file 'src/Ubuntu/Components/qmldir'
797--- src/Ubuntu/Components/qmldir 2016-07-29 13:21:05 +0000
798+++ src/Ubuntu/Components/qmldir 2016-09-06 13:08:47 +0000
799@@ -96,7 +96,6 @@
800 #################################################
801 #version 1.3
802 ActionBar 1.3 1.3/ActionBar.qml
803-ActionList 1.3 1.3/ActionList.qml
804 AdaptivePageLayout 1.3 1.3/AdaptivePageLayout.qml
805 PageColumnsLayout 1.3 1.3/PageColumnsLayout.qml
806 PageColumn 1.3 1.3/PageColumn.qml
807
808=== modified file 'src/Ubuntu/Test/UbuntuTestCase13.qml'
809--- src/Ubuntu/Test/UbuntuTestCase13.qml 2016-03-15 13:17:53 +0000
810+++ src/Ubuntu/Test/UbuntuTestCase13.qml 2016-09-06 13:08:47 +0000
811@@ -15,7 +15,7 @@
812 */
813
814 import QtQuick 2.4
815-import QtTest 1.0
816+import QtTest 1.1
817 import Ubuntu.Components 1.3
818
819 /*!
820
821=== modified file 'src/Ubuntu/UbuntuMetrics/applicationmonitor.cpp'
822--- src/Ubuntu/UbuntuMetrics/applicationmonitor.cpp 2016-08-06 00:54:30 +0000
823+++ src/Ubuntu/UbuntuMetrics/applicationmonitor.cpp 2016-09-06 13:08:47 +0000
824@@ -567,7 +567,9 @@
825 event.generic.id = id;
826 // We don't bother fixing up non null-terminated string, just potential
827 // overflows.
828- event.generic.stringSize = qMin(size, UMGenericEvent::maxStringSize);
829+ // Note: GCC5 comoiler/linker cannot find UMGenericEvent::maxStringSize
830+ // if used in qMin(); force type to satisfy it
831+ event.generic.stringSize = qMin(size, quint32(UMGenericEvent::maxStringSize));
832 memcpy(event.generic.string, string, event.generic.stringSize);
833 d->m_loggingThread->push(&event);
834 return true;
835
836=== modified file 'src/Ubuntu/UbuntuToolkit/UbuntuToolkit.pro'
837--- src/Ubuntu/UbuntuToolkit/UbuntuToolkit.pro 2016-08-04 17:25:09 +0000
838+++ src/Ubuntu/UbuntuToolkit/UbuntuToolkit.pro 2016-09-06 13:08:47 +0000
839@@ -44,7 +44,15 @@
840 mousetouchadaptor_p.h \
841 mousetouchadaptor_p_p.h \
842 propertychange_p.h \
843- ubuntutoolkitmodule.h
844+ ubuntutoolkitmodule.h \
845+ splitview_p.h \
846+ splitview_p_p.h \
847+ privates/splitviewhandler_p_p.h \
848+ menu_p_p.h \
849+ menubar_p_p.h \
850+ menu_p.h \
851+ menubar_p.h \
852+ menugroup_p.h
853
854 SOURCES += \
855 colorutils.cpp \
856@@ -52,7 +60,13 @@
857 asyncloader.cpp \
858 mousetouchadaptor.cpp \
859 propertychange.cpp \
860- ubuntutoolkitmodule.cpp
861+ ubuntutoolkitmodule.cpp \
862+ splitview.cpp \
863+ privates/splitviewhandler.cpp \
864+ splitviewlayout.cpp \
865+ menu.cpp \
866+ menubar.cpp \
867+ menugroup.cpp
868
869 HEADERS += \
870 uctheme_p.h \
871@@ -149,7 +163,9 @@
872 privates/appheaderbase_p.h \
873 label_p.h \
874 ucbottomedgeregion_p_p.h \
875- privates/ucscrollbarutils_p.h
876+ privates/ucscrollbarutils_p.h \
877+ actionlist_p.h \
878+ exclusivegroup_p.h
879
880 SOURCES += \
881 uctheme.cpp \
882@@ -226,7 +242,9 @@
883 privates/ucpagewrapper.cpp \
884 privates/ucpagewrapperincubator.cpp \
885 privates/appheaderbase.cpp \
886- privates/ucscrollbarutils.cpp
887+ privates/ucscrollbarutils.cpp \
888+ actionlist.cpp \
889+ exclusivegroup.cpp
890
891 # adapters
892 SOURCES += $$PWD/adapters/alarmsadapter_organizer.cpp
893
894=== added file 'src/Ubuntu/UbuntuToolkit/actionlist.cpp'
895--- src/Ubuntu/UbuntuToolkit/actionlist.cpp 1970-01-01 00:00:00 +0000
896+++ src/Ubuntu/UbuntuToolkit/actionlist.cpp 2016-09-06 13:08:47 +0000
897@@ -0,0 +1,158 @@
898+/*
899+ * Copyright 2016 Canonical Ltd.
900+ *
901+ * This program is free software; you can redistribute it and/or modify
902+ * it under the terms of the GNU Lesser General Public License as published by
903+ * the Free Software Foundation; version 3.
904+ *
905+ * This program is distributed in the hope that it will be useful,
906+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
907+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
908+ * GNU Lesser General Public License for more details.
909+ *
910+ * You should have received a copy of the GNU Lesser General Public License
911+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
912+ */
913+
914+#include "actionlist_p.h"
915+#include "ucaction_p.h"
916+
917+UT_NAMESPACE_BEGIN
918+
919+/*!
920+ * \qmltype ActionList
921+ * \inqmlmodule Ubuntu.Components
922+ * \ingroup ubuntu
923+ * \brief List of \l Action items
924+ * An ActionList provies a way of grouping actions together.
925+ * \qml
926+ * ActionList {
927+ * Action {
928+ * id: action1
929+ * }
930+ * Action {
931+ * id: action2
932+ * }
933+ * }
934+ * \endqml
935+ */
936+/*!
937+ * \qmlsignal ActionList::added(Action action)
938+ * \since Ubuntu.Components 1.3
939+ * Signal called when an action is added to the list
940+ */
941+/*!
942+ * \qmlsignal ActionList::removed(Action action)
943+ * \since Ubuntu.Components 1.3
944+ * Signal called when an action is removed from the list
945+ */
946+ActionList::ActionList(QObject *parent)
947+ : QObject(parent)
948+{
949+}
950+
951+/*!
952+ * \qmlmethod ActionList::addAction(Action action)
953+ * \since Ubuntu.Components 1.3
954+ * Adds an Action to the list programatically.
955+ * \qml
956+ * Item {
957+ * Instantiator {
958+ * model: 4
959+ * onObjectAdded: actionList.addAction(object)
960+ * onObjectRemoved: actionList.removeAction(object)
961+ *
962+ * Action {}
963+ * }
964+ *
965+ * ActionList {
966+ * id: actionList
967+ * }
968+ * }
969+ * \endqml
970+ * \sa ActionList::removeAction
971+ */
972+void ActionList::addAction(UCAction *action)
973+{
974+ if (m_actions.contains(action)) {
975+ return;
976+ }
977+ m_actions.append(action);
978+ Q_EMIT added(action);
979+}
980+
981+/*!
982+ * \qmlmethod ActionList::removeAction(Action action)
983+ * \since Ubuntu.Components 1.3
984+ * Removes an action from the list programatically.
985+ * \sa ActionList::addAction
986+ */
987+void ActionList::removeAction(UCAction *action)
988+{
989+ if (!action) {
990+ return;
991+ }
992+ if (m_actions.removeOne(action)) {
993+ Q_EMIT removed(action);
994+ }
995+}
996+
997+/*!
998+ * \qmlproperty list<Action> ActionList::actions
999+ * \default
1000+ * List of Actions in this ActionList
1001+ * Note that when you set this property, the children of the ActionList will be ignored,
1002+ * so do not set the list and define children.
1003+ *
1004+ * The advantage of setting actions over using the children is that the same
1005+ * \l Action items can be used in several sets of actions.
1006+ */
1007+QQmlListProperty<UCAction> ActionList::actions()
1008+{
1009+ return QQmlListProperty<UCAction>(this, 0,
1010+ ActionList::append,
1011+ ActionList::count,
1012+ ActionList::at,
1013+ ActionList::clear);
1014+}
1015+
1016+const QList<UCAction*> &ActionList::list() const
1017+{
1018+ return m_actions;
1019+}
1020+
1021+void ActionList::append(QQmlListProperty<UCAction> *list, UCAction *action)
1022+{
1023+ ActionList *actionList = qobject_cast<ActionList*>(list->object);
1024+ if (actionList) {
1025+ actionList->addAction(action);
1026+ }
1027+}
1028+
1029+void ActionList::clear(QQmlListProperty<UCAction> *list)
1030+{
1031+ ActionList *actionList = qobject_cast<ActionList*>(list->object);
1032+ if (actionList) {
1033+ actionList->m_actions.clear();
1034+ }
1035+}
1036+
1037+UCAction* ActionList::at(QQmlListProperty<UCAction> *list, int index)
1038+{
1039+ ActionList *actionList = qobject_cast<ActionList*>(list->object);
1040+ if (actionList) {
1041+ return actionList->m_actions.value(index, nullptr);
1042+ }
1043+ return Q_NULLPTR;
1044+}
1045+
1046+int ActionList::count(QQmlListProperty<UCAction> *list)
1047+{
1048+ ActionList *actionList = qobject_cast<ActionList*>(list->object);
1049+ if (actionList) {
1050+ return actionList->m_actions.count();
1051+ }
1052+ return 0;
1053+}
1054+
1055+UT_NAMESPACE_END
1056
1057=== added file 'src/Ubuntu/UbuntuToolkit/actionlist_p.h'
1058--- src/Ubuntu/UbuntuToolkit/actionlist_p.h 1970-01-01 00:00:00 +0000
1059+++ src/Ubuntu/UbuntuToolkit/actionlist_p.h 2016-09-06 13:08:47 +0000
1060@@ -0,0 +1,58 @@
1061+/*
1062+ * Copyright 2016 Canonical Ltd.
1063+ *
1064+ * This program is free software; you can redistribute it and/or modify
1065+ * it under the terms of the GNU Lesser General Public License as published by
1066+ * the Free Software Foundation; version 3.
1067+ *
1068+ * This program is distributed in the hope that it will be useful,
1069+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1070+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1071+ * GNU Lesser General Public License for more details.
1072+ *
1073+ * You should have received a copy of the GNU Lesser General Public License
1074+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1075+ */
1076+
1077+#ifndef ACTIONLIST_H
1078+#define ACTIONLIST_H
1079+
1080+#include <QObject>
1081+#include <QtQml/QQmlListProperty>
1082+#include "ubuntutoolkitglobal.h"
1083+
1084+UT_NAMESPACE_BEGIN
1085+
1086+class UCAction;
1087+class UBUNTUTOOLKIT_EXPORT ActionList : public QObject
1088+{
1089+ Q_OBJECT
1090+ Q_PROPERTY(QQmlListProperty<UT_PREPEND_NAMESPACE(UCAction)> actions READ actions)
1091+ Q_CLASSINFO("DefaultProperty", "actions")
1092+public:
1093+ explicit ActionList(QObject *parent = 0);
1094+
1095+ QQmlListProperty<UCAction> actions();
1096+
1097+ const QList<UCAction*> &list() const;
1098+
1099+public Q_SLOTS:
1100+ void addAction(UT_PREPEND_NAMESPACE(UCAction) *action);
1101+ void removeAction(UT_PREPEND_NAMESPACE(UCAction) *action);
1102+
1103+Q_SIGNALS:
1104+ void added(UCAction *action);
1105+ void removed(UCAction *action);
1106+
1107+protected:
1108+ QList<UCAction*> m_actions;
1109+
1110+ static void append(QQmlListProperty<UCAction> *list, UCAction *action);
1111+ static void clear(QQmlListProperty<UCAction> *list);
1112+ static UCAction* at(QQmlListProperty<UCAction> *list, int index);
1113+ static int count(QQmlListProperty<UCAction> *list);
1114+};
1115+
1116+UT_NAMESPACE_END
1117+
1118+#endif // ACTIONLIST_H
1119
1120=== added file 'src/Ubuntu/UbuntuToolkit/exclusivegroup.cpp'
1121--- src/Ubuntu/UbuntuToolkit/exclusivegroup.cpp 1970-01-01 00:00:00 +0000
1122+++ src/Ubuntu/UbuntuToolkit/exclusivegroup.cpp 2016-09-06 13:08:47 +0000
1123@@ -0,0 +1,168 @@
1124+/*
1125+ * Copyright 2016 Canonical Ltd.
1126+ *
1127+ * This program is free software; you can redistribute it and/or modify
1128+ * it under the terms of the GNU Lesser General Public License as published by
1129+ * the Free Software Foundation; version 3.
1130+ *
1131+ * This program is distributed in the hope that it will be useful,
1132+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1133+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1134+ * GNU Lesser General Public License for more details.
1135+ *
1136+ * You should have received a copy of the GNU Lesser General Public License
1137+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1138+ *
1139+ */
1140+
1141+#include "exclusivegroup_p.h"
1142+#include "ucaction_p.h"
1143+
1144+#include <QSignalMapper>
1145+
1146+#define CHECKED_PROPERTY "checked"
1147+
1148+UT_NAMESPACE_BEGIN
1149+
1150+static const char *checkableSignals[] = {
1151+ "toggled(bool)",
1152+ 0
1153+};
1154+
1155+static bool isChecked(const QObject *o)
1156+{
1157+ if (!o) return false;
1158+ QVariant checkedVariant = o->property(CHECKED_PROPERTY);
1159+ return checkedVariant.isValid() && checkedVariant.toBool();
1160+}
1161+
1162+/*!
1163+ * \qmltype ExclusiveGroup
1164+ * \inqmlmodule Ubuntu.Components
1165+ * \since Ubuntu.Components 1.3
1166+ * \ingroup ubuntu
1167+ * \inherits ActionList
1168+ * \brief ExclusiveGroup provides a way to declare several checkable controls as mutually exclusive.
1169+ *
1170+ * The ExclusiveGroup will only allow a single object to have it's checkable property set to "true"
1171+ * at any one time. The exclusive group accepts child Actions, but objects other than Actions can be
1172+ * used by using the \l bindCheckable function as long as they support one of the required signals,
1173+ * and a "checked" property.
1174+ * \qml
1175+ * ExclusiveGroup {
1176+ * Action {
1177+ * parameterType: Action.Bool
1178+ * state: true
1179+ * }
1180+ * Action {
1181+ * parameterType: Action.Bool
1182+ * state: false
1183+ * }
1184+ * }
1185+ * \endqml
1186+ */
1187+ExclusiveGroup::ExclusiveGroup(QObject *parent)
1188+ : ActionList(parent)
1189+ , m_signalMapper(new QSignalMapper(this))
1190+ , m_entranceGuard(false)
1191+{
1192+ connect(this, &ActionList::added, this, &ExclusiveGroup::onActionAdded);
1193+ connect(this, &ActionList::removed, this, &ExclusiveGroup::onActionRemoved);
1194+
1195+ int index = m_signalMapper->metaObject()->indexOfMethod("map()");
1196+ m_updateCurrentMethod = m_signalMapper->metaObject()->method(index);
1197+ connect(m_signalMapper, static_cast<void(QSignalMapper::*)(QObject *)>(&QSignalMapper::mapped), this, [this](QObject *object) {
1198+ if (isChecked(object)) {
1199+ setCurrent(object);
1200+ }
1201+ });
1202+}
1203+
1204+void ExclusiveGroup::onActionAdded(UCAction *action)
1205+{
1206+ action->setExclusiveGroup(this);
1207+}
1208+
1209+void ExclusiveGroup::onActionRemoved(UCAction *action)
1210+{
1211+ action->setExclusiveGroup(nullptr);
1212+}
1213+
1214+/*!
1215+ * \qmlproperty Action ExclusiveGroup::current
1216+ * Returns the currently checked action
1217+ */
1218+void ExclusiveGroup::setCurrent(QObject *object)
1219+{
1220+ if (m_current == object)
1221+ return;
1222+
1223+ if (m_current)
1224+ m_current->setProperty(CHECKED_PROPERTY, QVariant(false));
1225+ m_current = object;
1226+ if (m_current)
1227+ m_current->setProperty(CHECKED_PROPERTY, QVariant(true));
1228+ Q_EMIT currentChanged();
1229+}
1230+
1231+QObject *ExclusiveGroup::current() const
1232+{
1233+ return m_current.data();
1234+}
1235+
1236+/*!
1237+ * \qmlmethod void ExclusiveGroup::bindCheckable(object object)
1238+ * Explicitly bind an objects checkability to this exclusive group.
1239+ * \note This only works with objects which support the following signals signals:
1240+ * \list
1241+ * \li \b toggled(bool)
1242+ * \endlist
1243+ * \qml
1244+ * Item {
1245+ * ExclusiveGroup {
1246+ * id: exclusiveGroup
1247+ * }
1248+ * Instantiator {
1249+ * model: 4
1250+ * onObjectAdded: exclusiveGroup.bindCheckable(object)
1251+ * onObjectRemoved: exclusiveGroup.unbindCheckable(object)
1252+ *
1253+ * Action {
1254+ * checkable: true
1255+ * }
1256+ * }
1257+ * }
1258+ * \endqml
1259+ * \sa ExclusiveGroup::unbindCheckable
1260+ */
1261+void ExclusiveGroup::bindCheckable(QObject *object)
1262+{
1263+ for (const char **signalName = checkableSignals; *signalName; signalName++) {
1264+ int signalIndex = object->metaObject()->indexOfSignal(*signalName);
1265+ if (signalIndex != -1) {
1266+ QMetaMethod signalMethod = object->metaObject()->method(signalIndex);
1267+ connect(object, signalMethod, m_signalMapper, m_updateCurrentMethod, Qt::UniqueConnection);
1268+ m_signalMapper->setMapping(object, object);
1269+ connect(object, SIGNAL(destroyed(QObject*)), this, SLOT(unbindCheckable(QObject*)), Qt::UniqueConnection);
1270+ if (!m_current && isChecked(object))
1271+ setCurrent(object);
1272+ break;
1273+ }
1274+ }
1275+}
1276+
1277+/*!
1278+ * \qmlmethod void ExclusiveGroup::unbindCheckable(object object)
1279+ * Explicitly unbind an objects checkability from this exclusive group.
1280+ * \sa ExclusiveGroup::bindCheckable
1281+ */
1282+void ExclusiveGroup::unbindCheckable(QObject *object)
1283+{
1284+ if (m_current == object)
1285+ setCurrent(0);
1286+
1287+ disconnect(object, 0, m_signalMapper, 0);
1288+ disconnect(object, SIGNAL(destroyed(QObject*)), this, SLOT(unbindCheckable(QObject*)));
1289+}
1290+
1291+UT_NAMESPACE_END
1292
1293=== added file 'src/Ubuntu/UbuntuToolkit/exclusivegroup_p.h'
1294--- src/Ubuntu/UbuntuToolkit/exclusivegroup_p.h 1970-01-01 00:00:00 +0000
1295+++ src/Ubuntu/UbuntuToolkit/exclusivegroup_p.h 2016-09-06 13:08:47 +0000
1296@@ -0,0 +1,62 @@
1297+/*
1298+ * Copyright 2016 Canonical Ltd.
1299+ *
1300+ * This program is free software; you can redistribute it and/or modify
1301+ * it under the terms of the GNU Lesser General Public License as published by
1302+ * the Free Software Foundation; version 3.
1303+ *
1304+ * This program is distributed in the hope that it will be useful,
1305+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1306+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1307+ * GNU Lesser General Public License for more details.
1308+ *
1309+ * You should have received a copy of the GNU Lesser General Public License
1310+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1311+ *
1312+ */
1313+
1314+#ifndef EXCLUSIVEGROUP_H
1315+#define EXCLUSIVEGROUP_H
1316+
1317+#include "actionlist_p.h"
1318+#include "ubuntutoolkitglobal.h"
1319+
1320+#include <QMetaMethod>
1321+#include <QPointer>
1322+
1323+class QSignalMapper;
1324+
1325+UT_NAMESPACE_BEGIN
1326+
1327+class UBUNTUTOOLKIT_EXPORT ExclusiveGroup : public ActionList
1328+{
1329+ Q_OBJECT
1330+ Q_PROPERTY(QObject* current READ current NOTIFY currentChanged)
1331+
1332+public:
1333+ explicit ExclusiveGroup(QObject *parent = 0);
1334+
1335+ QObject* current() const;
1336+
1337+ Q_INVOKABLE void bindCheckable(QObject* object);
1338+ Q_INVOKABLE void unbindCheckable(QObject* object);
1339+
1340+Q_SIGNALS:
1341+ void currentChanged();
1342+
1343+protected Q_SLOTS:
1344+ void onActionAdded(UCAction* action);
1345+ void onActionRemoved(UCAction* action);
1346+
1347+private:
1348+ void setCurrent(QObject* action);
1349+
1350+ QSignalMapper* m_signalMapper;
1351+ QPointer<QObject> m_current;
1352+ QMetaMethod m_updateCurrentMethod;
1353+ bool m_entranceGuard;
1354+};
1355+
1356+UT_NAMESPACE_END
1357+
1358+#endif // EXCLUSIVEGROUP_H
1359
1360=== added file 'src/Ubuntu/UbuntuToolkit/menu.cpp'
1361--- src/Ubuntu/UbuntuToolkit/menu.cpp 1970-01-01 00:00:00 +0000
1362+++ src/Ubuntu/UbuntuToolkit/menu.cpp 2016-09-06 13:08:47 +0000
1363@@ -0,0 +1,671 @@
1364+/*
1365+ * Copyright 2016 Canonical Ltd.
1366+ *
1367+ * This program is free software; you can redistribute it and/or modify
1368+ * it under the terms of the GNU Lesser General Public License as published by
1369+ * the Free Software Foundation; version 3.
1370+ *
1371+ * This program is distributed in the hope that it will be useful,
1372+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1373+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1374+ * GNU Lesser General Public License for more details.
1375+ *
1376+ * You should have received a copy of the GNU Lesser General Public License
1377+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1378+ */
1379+
1380+#include "menu_p.h"
1381+#include "menu_p_p.h"
1382+#include "menubar_p.h"
1383+#include "actionlist_p.h"
1384+#include "menugroup_p.h"
1385+
1386+// Qt
1387+#include <QPointer>
1388+#include <QQuickItem>
1389+#include <QLoggingCategory>
1390+#include <QtGui/qpa/qplatformtheme.h>
1391+#include <QtGui/qpa/qplatformmenu.h>
1392+#include <private/qguiapplication_p.h>
1393+#include <private/qquickitem_p.h>
1394+
1395+#include <functional>
1396+
1397+Q_LOGGING_CATEGORY(ucMenu, "ubuntu.components.Menu", QtMsgType::QtWarningMsg)
1398+
1399+UT_NAMESPACE_BEGIN
1400+
1401+namespace {
1402+
1403+QWindow* findWindowForObject(QObject* object)
1404+{
1405+ QObject* window = object;
1406+ while (window && !window->isWindowType()) {
1407+ window = window->parent();
1408+ if (QQuickItem* item = qobject_cast<QQuickItem*>(window)) {
1409+ window = item->window();
1410+ }
1411+ }
1412+ return qobject_cast<QWindow*>(window);
1413+}
1414+
1415+// recursively get the all the object from the menu group which are not lists or groups.
1416+QObjectList getActionsFromMenuGroup(MenuGroup* menuGroup) {
1417+
1418+ QObjectList objectList;
1419+
1420+ Q_FOREACH(QObject* data, menuGroup->list()) {
1421+
1422+ if (auto actionList = qobject_cast<ActionList*>(data)) {
1423+ Q_FOREACH(UCAction* action, actionList->list()) {
1424+ objectList << action;
1425+ }
1426+ } else if (auto subMenuGroup = qobject_cast<MenuGroup*>(data)) {
1427+ objectList << getActionsFromMenuGroup(subMenuGroup);
1428+ } else {
1429+ objectList << data;
1430+ }
1431+ }
1432+ return objectList;
1433+}
1434+
1435+// recursively get the first object from the menu group which is not a list or a group.
1436+QObject* getFirstObject(MenuGroup* menuGroup) {
1437+
1438+ Q_FOREACH(QObject* data, menuGroup->list()) {
1439+
1440+ if (auto subMenuGroup = qobject_cast<MenuGroup*>(data)) {
1441+ QObject* object = getFirstObject(subMenuGroup);
1442+ if (object) { return object; }
1443+ } else if (auto actionList = qobject_cast<ActionList*>(data)) {
1444+ return actionList->list().count() > 0 ? actionList->list()[0] : 0;
1445+ } else {
1446+ return data;
1447+ }
1448+ }
1449+ return Q_NULLPTR;
1450+}
1451+
1452+}
1453+
1454+MenuPrivate::MenuPrivate(Menu *qq)
1455+ : q_ptr(qq)
1456+ , m_platformMenu(QGuiApplicationPrivate::platformTheme()->createPlatformMenu())
1457+{
1458+}
1459+
1460+MenuPrivate::~MenuPrivate()
1461+{
1462+ qDeleteAll(m_platformItems);
1463+ m_platformItems.clear();
1464+
1465+ delete m_platformMenu;
1466+ m_platformMenu = Q_NULLPTR;
1467+}
1468+
1469+void MenuPrivate::insertObject(int index, QObject *o)
1470+{
1471+ Q_Q(Menu);
1472+ if (!o) return;
1473+ qCDebug(ucMenu).nospace() << "Menu::insertObject(index="<< index << ", object=" << o << ")";
1474+
1475+ if (!m_platformMenu) {
1476+ m_data.insert(m_data.count() > index ? index : m_data.count(), o);
1477+ return;
1478+ }
1479+
1480+ // If the menus contains lists or groups, we need to alter the insertion index to account for them.
1481+ int actualIndex = 0;
1482+ bool insertSeparator = false;
1483+ for (int i = 0; i < index && i < m_data.count(); i++) {
1484+ QObject* data = m_data[i];
1485+
1486+ int dataObjectCount = 0;
1487+ if (auto menuGroup = qobject_cast<MenuGroup*>(data)) {
1488+ dataObjectCount = getActionsFromMenuGroup(menuGroup).count();
1489+ } else if (auto list = qobject_cast<ActionList*>(data)) {
1490+ dataObjectCount = list->list().count();
1491+ } else {
1492+ dataObjectCount = 1;
1493+ }
1494+ actualIndex += dataObjectCount;
1495+ // insert a separator before the new item if there are previous items.
1496+ insertSeparator |= dataObjectCount > 0;
1497+
1498+ // account for item separators if it's not the last data item.
1499+ actualIndex += dataObjectCount > 0 && (i+1 < index && i+1 < m_data.count()) ? 1: 0;
1500+ }
1501+
1502+ // need to make sure the item after the insertion index has a separator
1503+ if (index < m_data.count()) {
1504+ QObject* data = m_data.at(index);
1505+ QObject* objectToSeparate(Q_NULLPTR);
1506+
1507+ if (auto menuGroup = qobject_cast<MenuGroup*>(data)) {
1508+ objectToSeparate = getFirstObject(menuGroup);
1509+ } else if (auto actionList = qobject_cast<ActionList*>(data)) {
1510+ objectToSeparate = actionList->list().count() > 0 ? actionList->list()[0] : 0;
1511+ } else {
1512+ objectToSeparate = data;
1513+ }
1514+
1515+ if (objectToSeparate && m_platformItems.contains(objectToSeparate)) {
1516+ m_platformItems.value(objectToSeparate)->setSeparator();
1517+ }
1518+ }
1519+
1520+ m_data.insert(m_data.count() > index ? index : m_data.count(), o);
1521+
1522+ // if an object changes, we need to remove and re-add it.
1523+ std::function<void()> refreshObject = [o, this]() {
1524+ int index = m_data.indexOf(o);
1525+ if (index >= 0) {
1526+ removeObject(o);
1527+ insertObject(index, o);
1528+ }
1529+ };
1530+
1531+ // Get All the menu item objects
1532+ QObjectList objects;
1533+ if (auto menuGroup = qobject_cast<MenuGroup*>(o)) {
1534+ Q_FOREACH(QObject* menuGroupObject, getActionsFromMenuGroup(menuGroup)) {
1535+ objects << menuGroupObject;
1536+ }
1537+ // connect to content changes
1538+ QObject::connect(menuGroup, &MenuGroup::changed, q, refreshObject);
1539+ } else if (auto actionList = qobject_cast<ActionList*>(o)) {
1540+ Q_FOREACH(UCAction* action, actionList->list()) {
1541+ objects << action;
1542+ }
1543+ // connect to content changes
1544+ QObject::connect(actionList, &ActionList::added, q, refreshObject);
1545+ QObject::connect(actionList, &ActionList::removed, q, refreshObject);
1546+ } else {
1547+ objects << o;
1548+ }
1549+
1550+ Q_FOREACH(QObject* platformObject, objects) {
1551+ // add to platform
1552+ auto platformWrapper = new PlatformItemWrapper(platformObject, q);
1553+ platformWrapper->insert(actualIndex++, insertSeparator);
1554+ if (platformWrapper->hasSeparator()) { // we also inserted an separator, need to increment for next position.
1555+ actualIndex++;
1556+ }
1557+ insertSeparator = false;
1558+ m_platformItems[platformObject] = platformWrapper;
1559+ // map the inserted item with the object which sources the platformItem info.
1560+ if (!m_dataPlatformObjectMap.contains(o, platformObject)) {
1561+ m_dataPlatformObjectMap.insertMulti(o, platformObject);
1562+ }
1563+
1564+ QObject::connect(platformObject, &QObject::destroyed, q, [o, this](QObject* platformObject) {
1565+ m_dataPlatformObjectMap.remove(o, platformObject);
1566+
1567+ if (m_platformItems.contains(platformObject)) {
1568+ PlatformItemWrapper* wrapper = m_platformItems.take(platformObject);
1569+ wrapper->remove();
1570+ delete wrapper;
1571+ }
1572+ });
1573+ }
1574+}
1575+
1576+void MenuPrivate::removeObject(QObject *o)
1577+{
1578+ Q_Q(Menu);
1579+ m_data.removeOne(o);
1580+ qCDebug(ucMenu).nospace() << "Menu::removeObject(" << o << ")";
1581+
1582+ if (m_platformMenu) {
1583+ if (auto menuGroup = qobject_cast<MenuGroup*>(o)) {
1584+ // disconnect from content changes
1585+ QObject::disconnect(menuGroup, &MenuGroup::changed, q, 0);
1586+ } else if (ActionList* actionList = qobject_cast<ActionList*>(o)) {
1587+ // disconnect from content changes
1588+ QObject::disconnect(actionList, &ActionList::added, q, 0);
1589+ QObject::disconnect(actionList, &ActionList::removed, q, 0);
1590+ }
1591+
1592+ QList<QObject*> platformObjects = m_dataPlatformObjectMap.values(o);
1593+ m_dataPlatformObjectMap.remove(o);
1594+
1595+ Q_FOREACH(QObject* platformObject, platformObjects) {
1596+ // remove from platform.
1597+ if (m_platformItems.contains(platformObject)) {
1598+ PlatformItemWrapper* wrapper = m_platformItems.take(platformObject);
1599+ wrapper->remove();
1600+ delete wrapper;
1601+ }
1602+ }
1603+ }
1604+}
1605+
1606+void MenuPrivate::_q_updateEnabled()
1607+{
1608+ Q_Q(Menu);
1609+
1610+ bool isEnabled = q->isEnabled();
1611+ if (m_platformMenu) { m_platformMenu->setEnabled(isEnabled); }
1612+}
1613+
1614+void MenuPrivate::_q_updateText()
1615+{
1616+ Q_Q(Menu);
1617+
1618+ QString text = q->text();
1619+ if (m_platformMenu) { m_platformMenu->setText(text); }
1620+}
1621+
1622+void MenuPrivate::_q_updateIcon()
1623+{
1624+ Q_Q(Menu);
1625+
1626+ QIcon icon;
1627+ if (!q->iconSource().isEmpty()) {
1628+ icon = QIcon(q->iconSource().path());
1629+ } else if (!q->iconName().isEmpty()) {
1630+ icon = QIcon::fromTheme(q->iconName());
1631+ }
1632+
1633+ if (m_platformMenu) { m_platformMenu->setIcon(icon); }
1634+}
1635+
1636+void MenuPrivate::_q_updateVisible()
1637+{
1638+ Q_Q(Menu);
1639+
1640+ bool visible = q->visible();
1641+ if (m_platformMenu) { m_platformMenu->setVisible(visible); }
1642+}
1643+
1644+void MenuPrivate::data_append(QQmlListProperty<QObject> *prop, QObject *o)
1645+{
1646+ Menu *q = qobject_cast<Menu *>(prop->object);
1647+ q->appendObject(o);
1648+}
1649+
1650+int MenuPrivate::data_count(QQmlListProperty<QObject> *prop)
1651+{
1652+ MenuPrivate *p = static_cast<MenuPrivate *>(prop->data);
1653+ return p->m_data.count();
1654+}
1655+
1656+QObject *MenuPrivate::data_at(QQmlListProperty<QObject> *prop, int index)
1657+{
1658+ MenuPrivate *p = static_cast<MenuPrivate *>(prop->data);
1659+ return p->m_data.value(index, Q_NULLPTR);
1660+}
1661+
1662+void MenuPrivate::data_clear(QQmlListProperty<QObject> *prop)
1663+{
1664+ MenuPrivate *p = static_cast<MenuPrivate *>(prop->data);
1665+ p->m_data.clear();
1666+}
1667+
1668+/*!
1669+ * \qmltype Menu
1670+ * \inqmlmodule Ubuntu.Components
1671+ * \ingroup ubuntu
1672+ * \brief Menu defines a context menu or submenu structure of a MenuBar
1673+ *
1674+ * Example usage:
1675+ * \qml
1676+ * import QtQuick 2.4
1677+ * import Ubuntu.Components 1.3
1678+ * Menu {
1679+ * text: "&File"
1680+ *
1681+ * MenuGroup {
1682+ * Action {
1683+ * text: "&New"
1684+ * shortcut: "Ctrl+N"
1685+ * }
1686+ *
1687+ * Action {
1688+ * text: "&Open"
1689+ * shortcut: "Ctrl+O"
1690+ * }
1691+ * }
1692+ *
1693+ * Menu {
1694+ * text: "Recent Files"
1695+ *
1696+ * ActionList {
1697+ * Action { text: "1.txt" }
1698+ * Action { text: "2.txt" }
1699+ * Action { text: "3.txt" }
1700+ * }
1701+ * }
1702+ *
1703+ * Action {
1704+ * action: Action {
1705+ * text: "E&xit"
1706+ * shortcut: "Ctrl+X"
1707+ * }
1708+ * }
1709+ * }
1710+ * \endqml
1711+ */
1712+Menu::Menu(QObject *parent)
1713+ : UCAction(parent)
1714+ , d_ptr(new MenuPrivate(this))
1715+{
1716+ Q_D(Menu);
1717+
1718+ connect(this, SIGNAL(enabledChanged()), this, SLOT(_q_updateEnabled()));
1719+ connect(this, SIGNAL(textChanged()), this, SLOT(_q_updateText()));
1720+ connect(this, SIGNAL(iconNameChanged()), this, SLOT(_q_updateIcon()));
1721+ connect(this, SIGNAL(iconSourceChanged()), this, SLOT(_q_updateIcon()));
1722+ connect(this, SIGNAL(visibleChanged()), this, SLOT(_q_updateVisible()));
1723+}
1724+
1725+Menu::~Menu()
1726+{
1727+}
1728+
1729+/*!
1730+ * \qmlproperty list<Object> Menu::data
1731+ * \default
1732+ * List of objects representing menu items within the menu.
1733+ *
1734+ * Currently supports Menu, Action, AcionList & MenuGroup objects.
1735+ * \note Item object which do not support platformItem will not be exported for native menus.
1736+ */
1737+QQmlListProperty<QObject> Menu::data()
1738+{
1739+ Q_D(Menu);
1740+ return QQmlListProperty<QObject>(this, d,
1741+ &MenuPrivate::data_append,
1742+ &MenuPrivate::data_count,
1743+ &MenuPrivate::data_at,
1744+ &MenuPrivate::data_clear);
1745+}
1746+
1747+/*!
1748+ * \qmlmethod Menu::appendObject(object o)
1749+ * Add a object tto the menu
1750+ */
1751+void Menu::appendObject(QObject *o)
1752+{
1753+ Q_D(Menu);
1754+
1755+ insertObject(d->m_data.count(), o);
1756+}
1757+
1758+/*!
1759+ * \qmlmethod Menu::insertObject(int index, object o)
1760+ * Inserts an item at the index in the menu.
1761+ *
1762+ * Currently supports Menu, Action, AcionList & MenuGroup objects.
1763+ * \note Item object which do not support platformItem will not be exported for native menus.
1764+ */
1765+void Menu::insertObject(int index, QObject *o)
1766+{
1767+ Q_D(Menu);
1768+ d->insertObject(index, o);
1769+}
1770+
1771+/*!
1772+ * \qmlmethod Menu::removeObject(object o)
1773+ * Removes the item from the menu.
1774+ */
1775+void Menu::removeObject(QObject *o)
1776+{
1777+ Q_D(Menu);
1778+ qCDebug(ucMenu) << "Menu::removeObject" << o;
1779+
1780+ d->removeObject(o);
1781+}
1782+
1783+QPlatformMenu* Menu::platformMenu() const
1784+{
1785+ Q_D(const Menu);
1786+ return d->m_platformMenu;
1787+}
1788+
1789+/*!
1790+ * \qmlmethod Menu::show(point point)
1791+ * Show the menu popup at the given point
1792+ */
1793+void Menu::show(const QPoint &point)
1794+{
1795+ Q_D(Menu);
1796+ qCDebug(ucMenu, "Menu::popup(%s, point(%d,%d))", qPrintable(text()), point.x(), point.y());
1797+
1798+ if (d->m_platformMenu) {
1799+ d->m_platformMenu->showPopup(findWindowForObject(this), QRect(point, QSize()), Q_NULLPTR);
1800+ }
1801+}
1802+
1803+/*!
1804+ * \qmlmethod Menu::dismiss()
1805+ * Dismiss and destroy the menu popup.
1806+ */
1807+void Menu::dismiss()
1808+{
1809+ Q_D(Menu);
1810+ qCDebug(ucMenu, "Menu::dismiss(%s)", qPrintable(text()));
1811+
1812+ if (d->m_platformMenu) {
1813+ d->m_platformMenu->dismiss();
1814+ }
1815+}
1816+
1817+
1818+PlatformItemWrapper::PlatformItemWrapper(QObject *target, Menu* menu)
1819+ : QObject(menu)
1820+ , m_target(target)
1821+ , m_menu(menu)
1822+ , m_platformItem(menu->platformMenu() ? menu->platformMenu()->createMenuItem() : Q_NULLPTR)
1823+ , m_platformItemSeparator(Q_NULLPTR)
1824+ , m_inserted(false)
1825+{
1826+ if (Menu* menu = qobject_cast<Menu*>(m_target)) {
1827+ if (m_platformItem) {
1828+ m_platformItem->setMenu(menu->platformMenu());
1829+ }
1830+
1831+ connect(menu, &Menu::visibleChanged, this, &PlatformItemWrapper::updateVisible);
1832+ connect(menu, &Menu::textChanged, this, &PlatformItemWrapper::updateText);
1833+ connect(menu, &Menu::enabledChanged, this, &PlatformItemWrapper::updateEnabled);
1834+ connect(menu, &Menu::iconSourceChanged, this, &PlatformItemWrapper::updateIcon);
1835+ connect(menu, &Menu::iconNameChanged, this, &PlatformItemWrapper::updateIcon);
1836+
1837+ } else if (UCAction* action = qobject_cast<UCAction*>(m_target)) {
1838+
1839+ connect(action, &UCAction::visibleChanged, this, &PlatformItemWrapper::updateVisible);
1840+ connect(action, &UCAction::textChanged, this, &PlatformItemWrapper::updateText);
1841+ connect(action, &UCAction::enabledChanged, this, &PlatformItemWrapper::updateEnabled);
1842+ connect(action, &UCAction::iconSourceChanged, this, &PlatformItemWrapper::updateIcon);
1843+ connect(action, &UCAction::iconNameChanged, this, &PlatformItemWrapper::updateIcon);
1844+ connect(action, &UCAction::shortcutChanged, this, &PlatformItemWrapper::updateShortcut);
1845+ connect(action, &UCAction::checkableChanged, this, &PlatformItemWrapper::updateCheck);
1846+ connect(action, &UCAction::toggled, this, &PlatformItemWrapper::updateCheck);
1847+
1848+ if (m_platformItem) {
1849+ // Connect to activate (with inversion for checkables)
1850+ connect(m_platformItem, &QPlatformMenuItem::activated, action, [action]() {
1851+ action->trigger();
1852+ });
1853+ }
1854+
1855+ }
1856+ syncPlatformItem();
1857+}
1858+
1859+PlatformItemWrapper::~PlatformItemWrapper()
1860+{
1861+ if (m_inserted && m_menu && m_menu->platformMenu()) {
1862+ m_menu->platformMenu()->removeMenuItem(m_platformItem);
1863+ if (m_platformItemSeparator) {
1864+ m_menu->platformMenu()->removeMenuItem(m_platformItemSeparator);
1865+ delete m_platformItemSeparator;
1866+ }
1867+ }
1868+ delete m_platformItem;
1869+}
1870+
1871+void PlatformItemWrapper::insert(int index, bool withSeparator)
1872+{
1873+ if (m_inserted) return;
1874+ qCDebug(ucMenu).nospace() << " PlatformItemWrapper::insert(menu=" << m_menu
1875+ << ", index=" << index
1876+ << ", object=" << m_target << ")";
1877+
1878+ auto platformMenu = m_menu->platformMenu();
1879+ if (!platformMenu) return;
1880+ if (!m_platformItem) return;
1881+
1882+ QPlatformMenuItem* before = platformMenu->menuItemAt(index);
1883+ platformMenu->insertMenuItem(m_platformItem, before);
1884+ m_inserted = true;
1885+
1886+ if (withSeparator) setSeparator();
1887+}
1888+
1889+void PlatformItemWrapper::remove()
1890+{
1891+ if (!m_inserted) return;
1892+ qCDebug(ucMenu).nospace() << " PlatformItemWrapper::remove(menu=" << m_menu
1893+ << ", object=" << m_target << ")";
1894+
1895+ auto platformMenu = m_menu->platformMenu();
1896+ if (!platformMenu) return;
1897+ if (!m_platformItem) return;
1898+
1899+ platformMenu->removeMenuItem(m_platformItem);
1900+ m_inserted = false;
1901+
1902+ if (m_platformItemSeparator) {
1903+ qCDebug(ucMenu).nospace() << " PlatformItemWrapper::removeSeparator(menu=" << m_menu
1904+ << ", object=" << m_target << ")";
1905+ platformMenu->removeMenuItem(m_platformItemSeparator);
1906+ delete m_platformItemSeparator;
1907+ m_platformItemSeparator = Q_NULLPTR;
1908+ }
1909+}
1910+
1911+void PlatformItemWrapper::setSeparator()
1912+{
1913+ // already created
1914+ if (m_platformItemSeparator) return;
1915+ // not inserted yet.
1916+ if (!m_inserted) return;
1917+
1918+ auto platformMenu = m_menu->platformMenu();
1919+ if (!platformMenu) return;
1920+
1921+ // insert separator before?
1922+ m_platformItemSeparator = platformMenu->createMenuItem();
1923+ if (m_platformItemSeparator) {
1924+ qCDebug(ucMenu).nospace() << " PlatformItemWrapper::setSeparator(menu=" << m_menu
1925+ << ", object=" << m_target << ")";
1926+
1927+ m_platformItemSeparator->setIsSeparator(true);
1928+ platformMenu->insertMenuItem(m_platformItemSeparator, m_platformItem);
1929+ }
1930+}
1931+
1932+void PlatformItemWrapper::updateVisible()
1933+{
1934+ if (Menu* menu = qobject_cast<Menu*>(m_target)) {
1935+ if (m_platformItem) m_platformItem->setVisible(menu->visible());
1936+ if (menu->platformMenu()) menu->platformMenu()->setVisible(menu->visible());
1937+ } else if (UCAction* action = qobject_cast<UCAction*>(m_target)) {
1938+ if (m_platformItem) m_platformItem->setVisible(action->visible());
1939+ }
1940+}
1941+
1942+void PlatformItemWrapper::updateEnabled()
1943+{
1944+ if (Menu* menu = qobject_cast<Menu*>(m_target)) {
1945+ if (m_platformItem) m_platformItem->setEnabled(menu->isEnabled());
1946+ if (menu->platformMenu()) menu->platformMenu()->setEnabled(menu->isEnabled());
1947+ } else if (UCAction* action = qobject_cast<UCAction*>(m_target)) {
1948+ if (m_platformItem) m_platformItem->setEnabled(action->isEnabled());
1949+ }
1950+}
1951+
1952+void PlatformItemWrapper::updateText()
1953+{
1954+ if (Menu* menu = qobject_cast<Menu*>(m_target)) {
1955+ if (m_platformItem) m_platformItem->setText(menu->text());
1956+ if (menu->platformMenu()) menu->platformMenu()->setText(menu->text());
1957+ } else if (UCAction* action = qobject_cast<UCAction*>(m_target)) {
1958+ if (m_platformItem) m_platformItem->setText(action->text());
1959+ }
1960+}
1961+
1962+void PlatformItemWrapper::updateIcon()
1963+{
1964+ QIcon icon;
1965+ if (Menu* menu = qobject_cast<Menu*>(m_target)) {
1966+
1967+ if (!menu->iconSource().isEmpty()) {
1968+ icon = QIcon(menu->iconSource().path());
1969+ } else if (!menu->iconName().isEmpty()) {
1970+ icon = QIcon::fromTheme(menu->iconName());
1971+ }
1972+ if (menu->platformMenu()) menu->platformMenu()->setIcon(icon);
1973+
1974+ } else if (UCAction* action = qobject_cast<UCAction*>(m_target)) {
1975+
1976+ if (!action->iconSource().isEmpty()) {
1977+ icon = QIcon(action->iconSource().path());
1978+ } else if (!action->iconName().isEmpty()) {
1979+ icon = QIcon::fromTheme(action->iconName());
1980+ }
1981+ }
1982+
1983+ if (m_platformItem) { m_platformItem->setIcon(icon); }
1984+}
1985+
1986+
1987+inline QKeySequence sequenceFromVariant(const QVariant& variant)
1988+{
1989+ if (variant.type() == QVariant::Int) {
1990+ return static_cast<QKeySequence::StandardKey>(variant.toInt());
1991+ }
1992+ if (variant.type() == QVariant::String) {
1993+ return QKeySequence::fromString(variant.toString());
1994+ }
1995+ return QKeySequence();
1996+}
1997+
1998+void PlatformItemWrapper::updateShortcut()
1999+{
2000+ if (!m_platformItem) return;
2001+
2002+ if (UCAction* action = qobject_cast<UCAction*>(m_target)) {
2003+ m_platformItem->setShortcut(sequenceFromVariant(action->shortcut()));
2004+ }
2005+}
2006+
2007+void PlatformItemWrapper::updateCheck()
2008+{
2009+ if (!m_platformItem) return;
2010+
2011+ if (UCAction* action = qobject_cast<UCAction*>(m_target)) {
2012+ bool checkable = action->isCheckable();
2013+ m_platformItem->setCheckable(checkable);
2014+ m_platformItem->setChecked(checkable && action->isChecked());
2015+ }
2016+}
2017+
2018+void PlatformItemWrapper::syncPlatformItem()
2019+{
2020+ updateVisible();
2021+ updateEnabled();
2022+ updateText();
2023+ updateIcon();
2024+ updateShortcut();
2025+ updateCheck();
2026+
2027+ if (m_menu->platformMenu() && m_platformItem) {
2028+ m_menu->platformMenu()->syncMenuItem(m_platformItem);
2029+ }
2030+}
2031+
2032+UT_NAMESPACE_END
2033+
2034+#include "moc_menu_p.cpp"
2035
2036=== added file 'src/Ubuntu/UbuntuToolkit/menu_p.h'
2037--- src/Ubuntu/UbuntuToolkit/menu_p.h 1970-01-01 00:00:00 +0000
2038+++ src/Ubuntu/UbuntuToolkit/menu_p.h 2016-09-06 13:08:47 +0000
2039@@ -0,0 +1,75 @@
2040+/*
2041+ * Copyright 2016 Canonical Ltd.
2042+ *
2043+ * This program is free software; you can redistribute it and/or modify
2044+ * it under the terms of the GNU Lesser General Public License as published by
2045+ * the Free Software Foundation; version 3.
2046+ *
2047+ * This program is distributed in the hope that it will be useful,
2048+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2049+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2050+ * GNU Lesser General Public License for more details.
2051+ *
2052+ * You should have received a copy of the GNU Lesser General Public License
2053+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2054+ *
2055+ */
2056+
2057+#ifndef MENU_P_H
2058+#define MENU_P_H
2059+
2060+#include <QQmlListProperty>
2061+#include <QLoggingCategory>
2062+#include <QPointer>
2063+#include <ubuntutoolkitglobal.h>
2064+
2065+#include "ucaction_p.h"
2066+
2067+Q_DECLARE_LOGGING_CATEGORY(ucMenu);
2068+
2069+class QPlatformMenu;
2070+class QPlatformMenuItem;
2071+class QQuickItem;
2072+
2073+UT_NAMESPACE_BEGIN
2074+
2075+class MenuPrivate;
2076+class MenuBar;
2077+class UCAction;
2078+class UBUNTUTOOLKIT_EXPORT Menu : public UCAction
2079+{
2080+ Q_OBJECT
2081+
2082+ Q_PROPERTY(QQmlListProperty<QObject> data READ data FINAL)
2083+ Q_CLASSINFO("DefaultProperty", "data")
2084+
2085+public:
2086+ explicit Menu(QObject *parent = 0);
2087+ ~Menu();
2088+
2089+ QQmlListProperty<QObject> data();
2090+
2091+ Q_INVOKABLE void appendObject(QObject* obj);
2092+ Q_INVOKABLE void insertObject(int index, QObject* obj);
2093+ Q_INVOKABLE void removeObject(QObject* obj);
2094+
2095+ QPlatformMenu *platformMenu() const;
2096+
2097+public Q_SLOTS:
2098+ void show(const QPoint& pt);
2099+ void dismiss();
2100+
2101+private:
2102+ Q_DISABLE_COPY(Menu)
2103+ Q_DECLARE_PRIVATE(Menu)
2104+ QScopedPointer<MenuPrivate> d_ptr;
2105+
2106+ Q_PRIVATE_SLOT(d_func(), void _q_updateEnabled())
2107+ Q_PRIVATE_SLOT(d_func(), void _q_updateText())
2108+ Q_PRIVATE_SLOT(d_func(), void _q_updateIcon())
2109+ Q_PRIVATE_SLOT(d_func(), void _q_updateVisible())
2110+};
2111+
2112+UT_NAMESPACE_END
2113+
2114+#endif // MENU_P_H
2115
2116=== added file 'src/Ubuntu/UbuntuToolkit/menu_p_p.h'
2117--- src/Ubuntu/UbuntuToolkit/menu_p_p.h 1970-01-01 00:00:00 +0000
2118+++ src/Ubuntu/UbuntuToolkit/menu_p_p.h 2016-09-06 13:08:47 +0000
2119@@ -0,0 +1,95 @@
2120+/*
2121+ * Copyright 2016 Canonical Ltd.
2122+ *
2123+ * This program is free software; you can redistribute it and/or modify
2124+ * it under the terms of the GNU Lesser General Public License as published by
2125+ * the Free Software Foundation; version 3.
2126+ *
2127+ * This program is distributed in the hope that it will be useful,
2128+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2129+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2130+ * GNU Lesser General Public License for more details.
2131+ *
2132+ * You should have received a copy of the GNU Lesser General Public License
2133+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2134+ *
2135+ */
2136+
2137+#ifndef MENU_P_P_H
2138+#define MENU_P_P_H
2139+
2140+#include "menu_p.h"
2141+#include <ubuntutoolkitglobal.h>
2142+
2143+class QObject;
2144+class QQmlComponent;
2145+
2146+UT_NAMESPACE_BEGIN
2147+
2148+class UCAction;
2149+class Menu;
2150+class PlatformItemWrapper;
2151+
2152+class MenuPrivate
2153+{
2154+ Q_DECLARE_PUBLIC(Menu)
2155+public:
2156+ MenuPrivate(Menu *qq);
2157+ virtual ~MenuPrivate();
2158+
2159+ void insertObject(int index, QObject *obj);
2160+ void removeObject(QObject *obj);
2161+
2162+ void _q_updateEnabled();
2163+ void _q_updateText();
2164+ void _q_updateIcon();
2165+ void _q_updateVisible();
2166+
2167+ static void data_append(QQmlListProperty<QObject> *prop, QObject *o);
2168+ static int data_count(QQmlListProperty<QObject> *prop);
2169+ static QObject *data_at(QQmlListProperty<QObject> *prop, int index);
2170+ static void data_clear(QQmlListProperty<QObject> *prop);
2171+
2172+ Menu* q_ptr;
2173+ QPlatformMenu* m_platformMenu;
2174+ UCAction* m_action;
2175+
2176+ QHash<QObject*, PlatformItemWrapper*> m_platformItems;
2177+ QMultiHash<QObject*, QObject*> m_dataPlatformObjectMap;
2178+ QVector<QObject*> m_data;
2179+};
2180+
2181+class PlatformItemWrapper : public QObject
2182+{
2183+ Q_OBJECT
2184+public:
2185+ PlatformItemWrapper(QObject *target, Menu* menu);
2186+ ~PlatformItemWrapper();
2187+
2188+ void insert(int index, bool withSeparator);
2189+ void remove();
2190+ void setSeparator();
2191+
2192+ bool hasSeparator() const { return m_platformItemSeparator != Q_NULLPTR; }
2193+
2194+public Q_SLOTS:
2195+ void updateVisible();
2196+ void updateEnabled();
2197+ void updateText();
2198+ void updateIcon();
2199+ void updateShortcut();
2200+ void updateCheck();
2201+
2202+private:
2203+ void syncPlatformItem();
2204+
2205+ QObject* m_target;
2206+ QPointer<Menu> m_menu;
2207+ QPlatformMenuItem* m_platformItem;
2208+ QPlatformMenuItem* m_platformItemSeparator;
2209+ bool m_inserted;
2210+};
2211+
2212+UT_NAMESPACE_END
2213+
2214+#endif // MENU_P_P_H
2215
2216=== added file 'src/Ubuntu/UbuntuToolkit/menubar.cpp'
2217--- src/Ubuntu/UbuntuToolkit/menubar.cpp 1970-01-01 00:00:00 +0000
2218+++ src/Ubuntu/UbuntuToolkit/menubar.cpp 2016-09-06 13:08:47 +0000
2219@@ -0,0 +1,340 @@
2220+/*
2221+ * Copyright 2016 Canonical Ltd.
2222+ *
2223+ * This program is free software; you can redistribute it and/or modify
2224+ * it under the terms of the GNU Lesser General Public License as published by
2225+ * the Free Software Foundation; version 3.
2226+ *
2227+ * This program is distributed in the hope that it will be useful,
2228+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2229+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2230+ * GNU Lesser General Public License for more details.
2231+ *
2232+ * You should have received a copy of the GNU Lesser General Public License
2233+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2234+ *
2235+ */
2236+
2237+#include "menubar_p.h"
2238+#include "menubar_p_p.h"
2239+
2240+// Qt
2241+#include <QQuickItem>
2242+#include <QQuickWindow>
2243+#include <QLoggingCategory>
2244+#include <private/qguiapplication_p.h>
2245+#include <QtGui/qpa/qplatformtheme.h>
2246+#include <QtGui/qpa/qplatformmenu.h>
2247+
2248+UT_NAMESPACE_BEGIN
2249+
2250+MenuBarPrivate::MenuBarPrivate(MenuBar *qq)
2251+ : q_ptr(qq)
2252+{
2253+ m_platformBar = QGuiApplicationPrivate::platformTheme()->createPlatformMenuBar();
2254+}
2255+
2256+MenuBarPrivate::~MenuBarPrivate()
2257+{
2258+ qDeleteAll(m_platformMenus);
2259+ m_platformMenus.clear();
2260+
2261+ delete m_platformBar;
2262+ m_platformBar = Q_NULLPTR;
2263+}
2264+
2265+void MenuBarPrivate::insertMenu(int index, Menu* menu)
2266+{
2267+ Q_Q(MenuBar);
2268+ Menu* prevMenu = m_menus.count() > index ? m_menus[index] : Q_NULLPTR;
2269+
2270+ m_menus.insert(index, menu);
2271+
2272+ // add to platform
2273+ if (m_platformBar && menu->platformMenu()) {
2274+ auto platformWrapper = new PlatformMenuWrapper(menu, q);
2275+ platformWrapper->insert(prevMenu ? prevMenu->platformMenu() : Q_NULLPTR);
2276+ m_platformMenus[menu] = platformWrapper;
2277+
2278+ QObject::connect(menu, &QObject::destroyed, q, [this](QObject* object) {
2279+ if (m_platformMenus.contains(object)) {
2280+ PlatformMenuWrapper* wrapper = m_platformMenus.take(object);
2281+ wrapper->remove();
2282+ delete wrapper;
2283+ }
2284+ });
2285+ }
2286+}
2287+
2288+void MenuBarPrivate::removeMenu(Menu *menu)
2289+{
2290+ m_menus.removeOne(menu);
2291+
2292+ if (m_platformBar) {
2293+ if (m_platformMenus.contains(menu)) {
2294+ PlatformMenuWrapper* wrapper = m_platformMenus.take(menu);
2295+ wrapper->remove();
2296+ delete wrapper;
2297+ }
2298+ }
2299+}
2300+
2301+void MenuBarPrivate::menu_append(QQmlListProperty<Menu> *prop, Menu *o)
2302+{
2303+ MenuBarPrivate *q = static_cast<MenuBarPrivate *>(prop->data);
2304+ // menubar is the menus parent
2305+ o->setParent(prop->object);
2306+ q->insertMenu(q->m_menus.count(), o);
2307+}
2308+
2309+int MenuBarPrivate::menu_count(QQmlListProperty<Menu> *prop)
2310+{
2311+ MenuBarPrivate *p = static_cast<MenuBarPrivate *>(prop->data);
2312+ return p->m_menus.count();
2313+}
2314+
2315+Menu *MenuBarPrivate::menu_at(QQmlListProperty<Menu> *prop, int index)
2316+{
2317+ MenuBarPrivate *p = static_cast<MenuBarPrivate *>(prop->data);
2318+ return p->m_menus.value(index);
2319+}
2320+
2321+void MenuBarPrivate::menu_clear(QQmlListProperty<Menu> *prop)
2322+{
2323+ MenuBarPrivate *p = static_cast<MenuBarPrivate *>(prop->data);
2324+ p->m_menus.clear();
2325+}
2326+
2327+/*!
2328+ * \qmltype MenuBar
2329+ * \inqmlmodule Ubuntu.Components 1.3
2330+ * \ingroup ubuntu
2331+ * \brief MenuBar defines an application menu bar structure
2332+ *
2333+ * Example usage:
2334+ * \qml
2335+ * import QtQuick 2.4
2336+ * import Ubuntu.Components 1.3
2337+ * MainView {
2338+ * MenuBar {
2339+ * Menu {
2340+ * text: "_File"
2341+ *
2342+ * MenuItem {
2343+ * text: "_New"
2344+ * shortcut: "Ctrl+N"
2345+ * }
2346+ *
2347+ * MenuItem {
2348+ * text: "_Open"
2349+ * shortcut: "Ctrl+O"
2350+ * }
2351+ *
2352+ * MenuSeparator {}
2353+ *
2354+ * MenuItem {
2355+ * action: exitAction
2356+ * }
2357+ * }
2358+ *
2359+ * Menu {
2360+ * text: "_Edit"
2361+ *
2362+ * MenuItem {
2363+ * text: "_Undo"
2364+ * iconSource: "image://theme/undo"
2365+ * }
2366+ * }
2367+ * Menu {
2368+ * text: "_Window"
2369+ *
2370+ * MenuItem {
2371+ * text: "Fullscreen"
2372+ * checkable: true
2373+ * checked: false
2374+ * }
2375+ * }
2376+ * }
2377+ * Action {
2378+ * id: boundAction
2379+ * text: "E_xit"
2380+ * onTriggered: {
2381+ * Qt.quit();
2382+ * }
2383+ * }
2384+ * }
2385+ * \endqml
2386+ */
2387+MenuBar::MenuBar(QObject *parent)
2388+ : QObject(parent)
2389+ , d_ptr(new MenuBarPrivate(this))
2390+{
2391+}
2392+
2393+MenuBar::~MenuBar()
2394+{
2395+}
2396+
2397+/*!
2398+ * \qmlmethod void MenuBar::appendMenu(Menu menu)
2399+ * Append a Menu to the MenuBar
2400+ */
2401+void MenuBar::appendMenu(Menu *menu)
2402+{
2403+ Q_D(MenuBar);
2404+ insertMenu(d->m_menus.count(), menu);
2405+}
2406+
2407+/*!
2408+ * \qmlmethod void MenuBar::insertMenu(int index, Menu menu)
2409+ * Insert a Menu to the MenuBar at the specified position
2410+ */
2411+void MenuBar::insertMenu(int index, Menu *menu)
2412+{
2413+ Q_D(MenuBar);
2414+ if (!menu) return;
2415+
2416+ d->insertMenu(index, menu);
2417+ Q_EMIT menusChanged();
2418+}
2419+
2420+/*!
2421+ * \qmlmethod void MenuBar::removeMenu(Menu menu)
2422+ * Remove a Menu from the MenuBar
2423+ */
2424+void MenuBar::removeMenu(Menu *menu)
2425+{
2426+ Q_D(MenuBar);
2427+ if (!menu) return;
2428+
2429+ d->removeMenu(menu);
2430+ Q_EMIT menusChanged();
2431+}
2432+/*!
2433+ * \qmlproperty list<Menu> MenuBar::menus
2434+ * \default
2435+ * List of Menus in this MenuBar.
2436+ */
2437+QQmlListProperty<Menu> MenuBar::menus()
2438+{
2439+ Q_D(MenuBar);
2440+ return QQmlListProperty<Menu>(this, d,
2441+ &MenuBarPrivate::menu_append,
2442+ &MenuBarPrivate::menu_count,
2443+ &MenuBarPrivate::menu_at,
2444+ &MenuBarPrivate::menu_clear);
2445+}
2446+
2447+QPlatformMenuBar *MenuBar::platformMenuBar() const
2448+{
2449+ Q_D(const MenuBar);
2450+ return d->m_platformBar;
2451+}
2452+
2453+void MenuBar::classBegin()
2454+{
2455+}
2456+
2457+void MenuBar::componentComplete()
2458+{
2459+ Q_D(MenuBar);
2460+
2461+ auto parentItem = qobject_cast<QQuickItem*>(parent());
2462+ if (parentItem && d->m_platformBar) {
2463+ d->m_platformBar->handleReparent(parentItem->window());
2464+ }
2465+}
2466+
2467+
2468+PlatformMenuWrapper::PlatformMenuWrapper(Menu *target, MenuBar* bar)
2469+ : QObject(bar)
2470+ , m_bar(bar)
2471+ , m_target(target)
2472+ , m_inserted(false)
2473+{
2474+ connect(m_target, &Menu::visibleChanged, this, &PlatformMenuWrapper::updateVisible);
2475+ connect(m_target, &Menu::textChanged, this, &PlatformMenuWrapper::updateText);
2476+ connect(m_target, &Menu::enabledChanged, this, &PlatformMenuWrapper::updateEnabled);
2477+ connect(m_target, &Menu::iconSourceChanged, this, &PlatformMenuWrapper::updateIcon);
2478+ connect(m_target, &Menu::iconNameChanged, this, &PlatformMenuWrapper::updateIcon);
2479+
2480+ syncPlatformMenu();
2481+}
2482+
2483+PlatformMenuWrapper::~PlatformMenuWrapper()
2484+{
2485+ if (m_inserted && m_bar && m_bar->platformMenuBar()) {
2486+ if (m_target && m_target->platformMenu()) {
2487+ m_bar->platformMenuBar()->removeMenu(m_target->platformMenu());
2488+ }
2489+ }
2490+}
2491+
2492+void PlatformMenuWrapper::insert(QPlatformMenu *before)
2493+{
2494+ if (m_inserted) return;
2495+ qCDebug(ucMenu).nospace() << " PlatformMenuWrapper::insert(bar=" << m_bar
2496+ << ", before=" << before
2497+ << ", menu=" << m_target << ")";
2498+
2499+ auto platformBar = m_bar->platformMenuBar();
2500+ if (!platformBar) return;
2501+ auto platformMenu = m_target->platformMenu();
2502+ if (!platformMenu) return;
2503+
2504+ platformBar->insertMenu(platformMenu, before);
2505+ m_inserted = true;
2506+}
2507+
2508+void PlatformMenuWrapper::remove()
2509+{
2510+ if (!m_inserted) return;
2511+ qCDebug(ucMenu).nospace() << " PlatformMenuWrapper::remove(bar=" << m_bar
2512+ << ", menu=" << m_target << ")";
2513+
2514+ auto platformBar = m_bar->platformMenuBar();
2515+ if (!platformBar) return;
2516+ auto platformMenu = m_target->platformMenu();
2517+ if (!platformMenu) return;
2518+
2519+ platformBar->removeMenu(platformMenu);
2520+ m_inserted = false;
2521+}
2522+
2523+void PlatformMenuWrapper::updateVisible()
2524+{
2525+ if (m_target->platformMenu()) m_target->platformMenu()->setVisible(m_target->visible());
2526+}
2527+
2528+void PlatformMenuWrapper::updateEnabled()
2529+{
2530+ if (m_target->platformMenu()) m_target->platformMenu()->setEnabled(m_target->isEnabled());
2531+}
2532+
2533+void PlatformMenuWrapper::updateText()
2534+{
2535+ if (m_target->platformMenu()) m_target->platformMenu()->setText(m_target->text());
2536+}
2537+
2538+void PlatformMenuWrapper::updateIcon()
2539+{
2540+ QIcon icon;
2541+ if (!m_target->iconSource().isEmpty()) {
2542+ icon = QIcon(m_target->iconSource().path());
2543+ } else if (!m_target->iconName().isEmpty()) {
2544+ icon = QIcon::fromTheme(m_target->iconName());
2545+ }
2546+ if (m_target->platformMenu()) m_target->platformMenu()->setIcon(icon);
2547+}
2548+
2549+void PlatformMenuWrapper::syncPlatformMenu()
2550+{
2551+ updateVisible();
2552+ updateEnabled();
2553+ updateText();
2554+ updateIcon();
2555+}
2556+
2557+UT_NAMESPACE_END
2558+
2559+#include "moc_menubar_p.cpp"
2560
2561=== added file 'src/Ubuntu/UbuntuToolkit/menubar_p.h'
2562--- src/Ubuntu/UbuntuToolkit/menubar_p.h 1970-01-01 00:00:00 +0000
2563+++ src/Ubuntu/UbuntuToolkit/menubar_p.h 2016-09-06 13:08:47 +0000
2564@@ -0,0 +1,65 @@
2565+/*
2566+ * Copyright 2016 Canonical Ltd.
2567+ *
2568+ * This program is free software; you can redistribute it and/or modify
2569+ * it under the terms of the GNU Lesser General Public License as published by
2570+ * the Free Software Foundation; version 3.
2571+ *
2572+ * This program is distributed in the hope that it will be useful,
2573+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2574+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2575+ * GNU Lesser General Public License for more details.
2576+ *
2577+ * You should have received a copy of the GNU Lesser General Public License
2578+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2579+ *
2580+ */
2581+
2582+#ifndef MENUBAR_P_H
2583+#define MENUBAR_P_H
2584+
2585+#include "menu_p.h"
2586+#include <ubuntutoolkitglobal.h>
2587+
2588+#include <QQmlParserStatus>
2589+
2590+class QPlatformMenuBar;
2591+
2592+UT_NAMESPACE_BEGIN
2593+
2594+class MenuBarPrivate;
2595+class UBUNTUTOOLKIT_EXPORT MenuBar : public QObject, public QQmlParserStatus
2596+{
2597+ Q_OBJECT
2598+ Q_INTERFACES(QQmlParserStatus)
2599+
2600+ Q_PROPERTY(QQmlListProperty<UT_PREPEND_NAMESPACE(Menu)> menus READ menus NOTIFY menusChanged FINAL)
2601+ Q_CLASSINFO("DefaultProperty", "menus")
2602+
2603+public:
2604+ explicit MenuBar(QObject *parent = 0);
2605+ ~MenuBar();
2606+
2607+ Q_INVOKABLE void appendMenu(Menu *menu);
2608+ Q_INVOKABLE void insertMenu(int index, Menu *menu);
2609+ Q_INVOKABLE void removeMenu(Menu *menu);
2610+
2611+ QQmlListProperty<Menu> menus();
2612+
2613+ QPlatformMenuBar *platformMenuBar() const;
2614+
2615+ void classBegin() Q_DECL_OVERRIDE;
2616+ void componentComplete() Q_DECL_OVERRIDE;
2617+
2618+Q_SIGNALS:
2619+ void menusChanged();
2620+
2621+private:
2622+ Q_DISABLE_COPY(MenuBar)
2623+ Q_DECLARE_PRIVATE(MenuBar)
2624+ QScopedPointer<MenuBarPrivate> d_ptr;
2625+};
2626+
2627+UT_NAMESPACE_END
2628+
2629+#endif // MENUBAR_P_H
2630
2631=== added file 'src/Ubuntu/UbuntuToolkit/menubar_p_p.h'
2632--- src/Ubuntu/UbuntuToolkit/menubar_p_p.h 1970-01-01 00:00:00 +0000
2633+++ src/Ubuntu/UbuntuToolkit/menubar_p_p.h 2016-09-06 13:08:47 +0000
2634@@ -0,0 +1,78 @@
2635+/*
2636+ * Copyright 2016 Canonical Ltd.
2637+ *
2638+ * This program is free software; you can redistribute it and/or modify
2639+ * it under the terms of the GNU Lesser General Public License as published by
2640+ * the Free Software Foundation; version 3.
2641+ *
2642+ * This program is distributed in the hope that it will be useful,
2643+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2644+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2645+ * GNU Lesser General Public License for more details.
2646+ *
2647+ * You should have received a copy of the GNU Lesser General Public License
2648+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2649+ *
2650+ */
2651+
2652+#ifndef MENUBAR_P_P_H
2653+#define MENUBAR_P_P_H
2654+
2655+#include "menubar_p.h"
2656+#include <ubuntutoolkitglobal.h>
2657+
2658+class QPlatformMenuBar;
2659+
2660+UT_NAMESPACE_BEGIN
2661+
2662+class PlatformMenuWrapper;
2663+
2664+class MenuBarPrivate
2665+{
2666+ Q_DECLARE_PUBLIC(MenuBar)
2667+public:
2668+ MenuBarPrivate(MenuBar *qq);
2669+ ~MenuBarPrivate();
2670+
2671+ void insertMenu(int index, Menu *menu);
2672+ void removeMenu(Menu *menu);
2673+
2674+ static void menu_append(QQmlListProperty<Menu> *prop, Menu *o);
2675+ static int menu_count(QQmlListProperty<Menu> *prop);
2676+ static Menu *menu_at(QQmlListProperty<Menu> *prop, int index);
2677+ static void menu_clear(QQmlListProperty<Menu> *prop);
2678+
2679+ MenuBar* q_ptr;
2680+ QPlatformMenuBar* m_platformBar;
2681+ QVector<Menu*> m_menus;
2682+ QHash<QObject*, PlatformMenuWrapper*> m_platformMenus;
2683+};
2684+
2685+class PlatformMenuWrapper : public QObject
2686+{
2687+ Q_OBJECT
2688+public:
2689+ PlatformMenuWrapper(Menu *target, MenuBar *bar);
2690+ ~PlatformMenuWrapper();
2691+
2692+ void insert(QPlatformMenu *before);
2693+ void remove();
2694+
2695+public Q_SLOTS:
2696+ void updateVisible();
2697+ void updateEnabled();
2698+ void updateText();
2699+ void updateIcon();
2700+
2701+private:
2702+ void syncPlatformMenu();
2703+
2704+ QPointer<MenuBar> m_bar;
2705+ QPointer<Menu> m_target;
2706+ bool m_inserted;
2707+};
2708+
2709+UT_NAMESPACE_END
2710+
2711+#endif // MENUBAR_P_P_H
2712+
2713
2714=== added file 'src/Ubuntu/UbuntuToolkit/menugroup.cpp'
2715--- src/Ubuntu/UbuntuToolkit/menugroup.cpp 1970-01-01 00:00:00 +0000
2716+++ src/Ubuntu/UbuntuToolkit/menugroup.cpp 2016-09-06 13:08:47 +0000
2717@@ -0,0 +1,164 @@
2718+/*
2719+ * Copyright 2016 Canonical Ltd.
2720+ *
2721+ * This program is free software; you can redistribute it and/or modify
2722+ * it under the terms of the GNU Lesser General Public License as published by
2723+ * the Free Software Foundation; version 3.
2724+ *
2725+ * This program is distributed in the hope that it will be useful,
2726+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2727+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2728+ * GNU Lesser General Public License for more details.
2729+ *
2730+ * You should have received a copy of the GNU Lesser General Public License
2731+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2732+ */
2733+
2734+#include "menugroup_p.h"
2735+#include "actionlist_p.h"
2736+#include "ucaction_p.h"
2737+
2738+UT_NAMESPACE_BEGIN
2739+
2740+/*!
2741+ * \qmltype MenuGroup
2742+ * \inqmlmodule Ubuntu.Components
2743+ * \ingroup ubuntu
2744+ * \brief Logical list of items for a menu.
2745+ *
2746+ * Example usage:
2747+ * \qml
2748+ * import QtQuick 2.4
2749+ * import Ubuntu.Components 1.3
2750+ * Menu {
2751+ * text: "Edit"
2752+ *
2753+ * MenuGroup {
2754+ * Action { text: "Undo" }
2755+ * Action { text: "Redo" }
2756+ * }
2757+ *
2758+ * MenuGroup {
2759+ * Action { text: "Cut" }
2760+ * ActionList {
2761+ * Action { text: "Copy" }
2762+ * Action { text: "Paste" }
2763+ * }
2764+ * }
2765+ *
2766+ * MenuGroup {
2767+ * Action { text: "Select All" }
2768+ * }
2769+ * }
2770+ * \endqml
2771+ */
2772+/*!
2773+ * \qmlsignal MenuGroup::added(Object action)
2774+ * Signal called when a action is added to the list
2775+ */
2776+
2777+/*!
2778+ * \qmlsignal MenuGroup::removed(Object action)
2779+ * Signal called when a action is removed from the list
2780+ */
2781+/*!
2782+ * \qmlsignal MenuGroup::changed()
2783+ * Signal called when the contents of the group change,
2784+ * including child content changes (eg. \l ActionList child add/remove)
2785+ */
2786+MenuGroup::MenuGroup(QObject *parent)
2787+ : QObject(parent)
2788+{
2789+}
2790+
2791+/*!
2792+ * \qmlmethod MenuGroup::addObject(Object object)
2793+ * Adds an Object to the list programatically.
2794+ */
2795+void MenuGroup::addObject(QObject *object)
2796+{
2797+ if (m_data.contains(object)) {
2798+ return;
2799+ }
2800+ m_data.push_back(object);
2801+
2802+ if (auto childGroup = qobject_cast<MenuGroup*>(object)) {
2803+ connect(childGroup, &MenuGroup::changed, this, &MenuGroup::changed);
2804+ } else if (auto actionList = qobject_cast<ActionList*>(object)) {
2805+ connect(actionList, &ActionList::added, this, &MenuGroup::changed);
2806+ connect(actionList, &ActionList::removed, this, &MenuGroup::changed);
2807+ }
2808+
2809+ Q_EMIT added(object);
2810+ Q_EMIT changed();
2811+}
2812+
2813+/*!
2814+ * \qmlmethod MenuGroup::removeObject(Object object)
2815+ * Removes an object from the list programatically.
2816+ */
2817+void MenuGroup::removeObject(QObject *object)
2818+{
2819+ if (!object) {
2820+ return;
2821+ }
2822+ if (m_data.removeOne(object)) {
2823+ disconnect(object, 0, this, 0);
2824+ Q_EMIT removed(object);
2825+ Q_EMIT changed();
2826+ }
2827+}
2828+
2829+/*!
2830+
2831+ * \qmlproperty list<Object> MenuGroup::data
2832+ * \default
2833+ * List of Objects in this MenuGroup
2834+ * Note that when you set this property, the children of the MenuGroup will be ignored,
2835+ * so do not set the list and define children.
2836+ */
2837+QQmlListProperty<QObject> MenuGroup::data()
2838+{
2839+ return QQmlListProperty<QObject>(this, 0, MenuGroup::append, MenuGroup::count, MenuGroup::at, MenuGroup::clear);
2840+}
2841+
2842+const QVector<QObject*> &MenuGroup::list() const
2843+{
2844+ return m_data;
2845+}
2846+
2847+void MenuGroup::append(QQmlListProperty<QObject> *list, QObject *object)
2848+{
2849+ MenuGroup *menuGroup = qobject_cast<MenuGroup*>(list->object);
2850+ if (menuGroup) {
2851+ menuGroup->addObject(object);
2852+ }
2853+}
2854+
2855+int MenuGroup::count(QQmlListProperty<QObject> *list)
2856+{
2857+ MenuGroup *menuGroup = qobject_cast<MenuGroup*>(list->object);
2858+ if (menuGroup) {
2859+ return menuGroup->m_data.count();
2860+ }
2861+ return 0;
2862+}
2863+
2864+QObject* MenuGroup::at(QQmlListProperty<QObject> *list, int index)
2865+{
2866+ MenuGroup *menuGroup = qobject_cast<MenuGroup*>(list->object);
2867+ if (menuGroup && index >=0 && menuGroup->m_data.count() > index) {
2868+ return menuGroup->m_data[index];
2869+ }
2870+ return Q_NULLPTR;
2871+}
2872+
2873+void MenuGroup::clear(QQmlListProperty<QObject> *list)
2874+{
2875+ MenuGroup *menuGroup = qobject_cast<MenuGroup*>(list->object);
2876+ if (menuGroup) {
2877+ menuGroup->m_data.clear();
2878+ }
2879+}
2880+
2881+UT_NAMESPACE_END
2882
2883=== added file 'src/Ubuntu/UbuntuToolkit/menugroup_p.h'
2884--- src/Ubuntu/UbuntuToolkit/menugroup_p.h 1970-01-01 00:00:00 +0000
2885+++ src/Ubuntu/UbuntuToolkit/menugroup_p.h 2016-09-06 13:08:47 +0000
2886@@ -0,0 +1,60 @@
2887+/*
2888+ * Copyright 2016 Canonical Ltd.
2889+ *
2890+ * This program is free software; you can redistribute it and/or modify
2891+ * it under the terms of the GNU Lesser General Public License as published by
2892+ * the Free Software Foundation; version 3.
2893+ *
2894+ * This program is distributed in the hope that it will be useful,
2895+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2896+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2897+ * GNU Lesser General Public License for more details.
2898+ *
2899+ * You should have received a copy of the GNU Lesser General Public License
2900+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2901+ */
2902+
2903+#ifndef MENUGROUP_P_H
2904+#define MENUGROUP_P_H
2905+
2906+#include <ubuntutoolkitglobal.h>
2907+
2908+#include <QObject>
2909+#include <QVector>
2910+#include <QQmlListProperty>
2911+
2912+UT_NAMESPACE_BEGIN
2913+
2914+class MenuGroup : public QObject
2915+{
2916+ Q_OBJECT
2917+ Q_PROPERTY(QQmlListProperty<QObject> data READ data)
2918+ Q_CLASSINFO("DefaultProperty", "data")
2919+public:
2920+ explicit MenuGroup(QObject *parent = 0);
2921+
2922+ QQmlListProperty<QObject> data();
2923+
2924+ const QVector<QObject*> &list() const;
2925+
2926+public Q_SLOTS:
2927+ void addObject(QObject *object);
2928+ void removeObject(QObject *object);
2929+
2930+Q_SIGNALS:
2931+ void added(QObject *object);
2932+ void removed(QObject *object);
2933+ void changed();
2934+
2935+protected:
2936+ QVector<QObject*> m_data;
2937+
2938+ static void append(QQmlListProperty<QObject> *list, QObject *action);
2939+ static int count(QQmlListProperty<QObject> *list);
2940+ static QObject* at(QQmlListProperty<QObject> *list, int);
2941+ static void clear(QQmlListProperty<QObject> *list);
2942+};
2943+
2944+UT_NAMESPACE_END
2945+
2946+#endif // MENUGROUP_P_H
2947
2948=== added file 'src/Ubuntu/UbuntuToolkit/privates/splitviewhandler.cpp'
2949--- src/Ubuntu/UbuntuToolkit/privates/splitviewhandler.cpp 1970-01-01 00:00:00 +0000
2950+++ src/Ubuntu/UbuntuToolkit/privates/splitviewhandler.cpp 2016-09-06 13:08:47 +0000
2951@@ -0,0 +1,137 @@
2952+/*
2953+ * Copyright 2016 Canonical Ltd.
2954+ *
2955+ * This program is free software; you can redistribute it and/or modify
2956+ * it under the terms of the GNU Lesser General Public License as published by
2957+ * the Free Software Foundation; version 3.
2958+ *
2959+ * This program is distributed in the hope that it will be useful,
2960+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2961+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2962+ * GNU Lesser General Public License for more details.
2963+ *
2964+ * You should have received a copy of the GNU Lesser General Public License
2965+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2966+ *
2967+ * Author: Zsombor Egri <zsombor.egri@canonical.com>
2968+ */
2969+
2970+#include "splitviewhandler_p_p.h"
2971+#include <QtQuick/private/qquickanchors_p.h>
2972+#include <QtQuick/private/qquickitem_p.h>
2973+#include <QtQuick/private/qquickevents_p_p.h>
2974+#include <QtQml/QQmlEngine>
2975+#include <QtQml/QQmlInfo>
2976+#include <splitview_p_p.h>
2977+
2978+UT_NAMESPACE_BEGIN
2979+
2980+SplitViewHandler::SplitViewHandler(QQuickItem *parent)
2981+ : QQuickMouseArea(parent)
2982+{
2983+ // for testing purposes
2984+ setObjectName("resize_handle");
2985+ setFlag(ItemHasContents);
2986+ setHoverEnabled(true);
2987+ setAcceptedButtons(Qt::LeftButton);
2988+
2989+ setCursorShape(Qt::SplitHCursor);
2990+ setParentItem(parent);
2991+
2992+ // attach the handler to the parent's rigth edge
2993+ QQuickItemPrivate *dParent = QQuickItemPrivate::get(parentItem());
2994+ QQuickAnchors *anchors = QQuickItemPrivate::get(this)->anchors();
2995+ anchors->setTop(dParent->top());
2996+ anchors->setBottom(dParent->bottom());
2997+ anchors->setLeft(dParent->right());
2998+
2999+ // capture mouse events
3000+ connect(this, SIGNAL(pressed(QQuickMouseEvent*)), this, SLOT(onPressed(QQuickMouseEvent*)));
3001+ connect(this, &SplitViewHandler::released, this, &SplitViewHandler::onReleased);
3002+ connect(this, &SplitViewHandler::positionChanged, this, &SplitViewHandler::onPositionChanged);
3003+}
3004+
3005+SplitViewHandler::~SplitViewHandler()
3006+{
3007+ if (spacing) {
3008+ disconnect(*spacing);
3009+ }
3010+ delete spacing;
3011+}
3012+
3013+void SplitViewHandler::connectToView(SplitView *view)
3014+{
3015+ this->view = view;
3016+ // grab the context of the parent
3017+ QQmlEngine::setContextForObject(this, qmlContext(view));
3018+
3019+ // bind SplitView spacing, use it to specify the resize handle
3020+ auto resizer = [view, this]() {
3021+ setWidth(view->spacing());
3022+ };
3023+ spacing = new QMetaObject::Connection;
3024+ *spacing = connect(view, &SplitView::spacingChanged, resizer);
3025+ setWidth(view->spacing());
3026+
3027+ // connect to receive handle delegate
3028+ connect(view, &SplitView::handleDelegateChanged,
3029+ this, &SplitViewHandler::onDelegateChanged);
3030+
3031+ onDelegateChanged();
3032+}
3033+
3034+void SplitViewHandler::onPressed(QQuickMouseEvent *event)
3035+{
3036+ prevPos = QPointF(event->x(), event->y());
3037+}
3038+
3039+void SplitViewHandler::onReleased(QQuickMouseEvent *event)
3040+{
3041+ Q_UNUSED(event);
3042+ prevPos = QPointF();
3043+}
3044+
3045+void SplitViewHandler::onPositionChanged(QQuickMouseEvent *event)
3046+{
3047+ if (!pressed()) {
3048+ return;
3049+ }
3050+ qreal dx = event->x() - prevPos.x();
3051+ SplitViewAttached *attached = SplitViewAttached::get(parentItem());
3052+ if (attached) {
3053+ attached->resize(dx);
3054+ }
3055+}
3056+
3057+void SplitViewHandler::onDelegateChanged()
3058+{
3059+ static bool warningShown = false;
3060+ // the child is an instance of the delegate
3061+ QList<QQuickItem*> children = childItems();
3062+ qDeleteAll(children);
3063+
3064+ // and set the new delegate - if any
3065+ if (SplitViewPrivate::get(view)->handleDelegate) {
3066+ QQmlContext *context = new QQmlContext(qmlContext(this), this);
3067+ context->setContextProperty("handle", QVariant::fromValue(this));
3068+ QObject *object = SplitViewPrivate::get(view)->handleDelegate->beginCreate(context);
3069+ if (object) {
3070+ QQuickItem *item = qobject_cast<QQuickItem*>(object);
3071+ if (!item) {
3072+ if (!warningShown) {
3073+ qmlInfo(view) << "handle delegate not an Item";
3074+ warningShown = true;
3075+ }
3076+ SplitViewPrivate::get(view)->handleDelegate->completeCreate();
3077+ delete object;
3078+ } else {
3079+ warningShown = false;
3080+ QQml_setParent_noEvent(item, this);
3081+ item->setParentItem(this);
3082+ SplitViewPrivate::get(view)->handleDelegate->completeCreate();
3083+ }
3084+ }
3085+ }
3086+}
3087+
3088+UT_NAMESPACE_END
3089
3090=== added file 'src/Ubuntu/UbuntuToolkit/privates/splitviewhandler_p_p.h'
3091--- src/Ubuntu/UbuntuToolkit/privates/splitviewhandler_p_p.h 1970-01-01 00:00:00 +0000
3092+++ src/Ubuntu/UbuntuToolkit/privates/splitviewhandler_p_p.h 2016-09-06 13:08:47 +0000
3093@@ -0,0 +1,52 @@
3094+/*
3095+ * Copyright 2016 Canonical Ltd.
3096+ *
3097+ * This program is free software; you can redistribute it and/or modify
3098+ * it under the terms of the GNU Lesser General Public License as published by
3099+ * the Free Software Foundation; version 3.
3100+ *
3101+ * This program is distributed in the hope that it will be useful,
3102+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3103+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3104+ * GNU Lesser General Public License for more details.
3105+ *
3106+ * You should have received a copy of the GNU Lesser General Public License
3107+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3108+ *
3109+ * Author: Zsombor Egri <zsombor.egri@canonical.com>
3110+ */
3111+
3112+#ifndef SPLITVIEWHANDLER_H
3113+#define SPLITVIEWHANDLER_H
3114+
3115+#include <QtQuick/QQuickItem>
3116+#include <QtCore/QPointer>
3117+#include <QtQuick/private/qquickmousearea_p.h>
3118+#include <ubuntutoolkitglobal.h>
3119+
3120+UT_NAMESPACE_BEGIN
3121+
3122+class SplitView;
3123+class SplitViewHandler : public QQuickMouseArea
3124+{
3125+ Q_OBJECT
3126+public:
3127+ explicit SplitViewHandler(QQuickItem *parent = 0);
3128+ ~SplitViewHandler();
3129+ void connectToView(SplitView *view);
3130+
3131+protected:
3132+ QPointF prevPos;
3133+ QPointer<SplitView> view;
3134+ QMetaObject::Connection *spacing{nullptr};
3135+
3136+private Q_SLOTS:
3137+ void onPressed(QQuickMouseEvent *event);
3138+ void onReleased(QQuickMouseEvent *event);
3139+ void onPositionChanged(QQuickMouseEvent *event);
3140+ void onDelegateChanged();
3141+};
3142+
3143+UT_NAMESPACE_END
3144+
3145+#endif // SPLITVIEWHANDLER_H
3146
3147=== modified file 'src/Ubuntu/UbuntuToolkit/quickutils.cpp'
3148--- src/Ubuntu/UbuntuToolkit/quickutils.cpp 2016-07-07 07:21:48 +0000
3149+++ src/Ubuntu/UbuntuToolkit/quickutils.cpp 2016-09-06 13:08:47 +0000
3150@@ -36,6 +36,7 @@
3151
3152 QuickUtils::QuickUtils(QObject *parent) :
3153 QObject(parent),
3154+ m_rootWindow(0),
3155 m_rootView(0),
3156 m_mouseAttached(false),
3157 m_keyboardAttached(false)
3158@@ -86,7 +87,7 @@
3159 // make sure we have the m_rootView updated
3160 lookupQuickView();
3161 if (!object) {
3162- return (m_rootView) ? m_rootView->rootObject() : 0;
3163+ return m_rootView ? m_rootView->rootObject() : (m_rootWindow ? m_rootWindow->contentItem() : 0);
3164 }
3165
3166 QQuickItem *item = qobject_cast<QQuickItem*>(object);
3167@@ -175,9 +176,10 @@
3168 */
3169 void QuickUtils::lookupQuickView()
3170 {
3171- if (m_rootView)
3172+ if (m_rootWindow)
3173 return;
3174 Q_FOREACH (QWindow *w, QGuiApplication::topLevelWindows()) {
3175+ m_rootWindow = qobject_cast<QQuickWindow*>(w);
3176 m_rootView = qobject_cast<QQuickView*>(w);
3177 if (m_rootView) {
3178 // connect in case we get the root object changed
3179
3180=== modified file 'src/Ubuntu/UbuntuToolkit/quickutils_p.h'
3181--- src/Ubuntu/UbuntuToolkit/quickutils_p.h 2016-07-07 07:21:48 +0000
3182+++ src/Ubuntu/UbuntuToolkit/quickutils_p.h 2016-09-06 13:08:47 +0000
3183@@ -85,6 +85,7 @@
3184
3185 private:
3186 explicit QuickUtils(QObject *parent = 0);
3187+ QPointer<QQuickWindow> m_rootWindow;
3188 QPointer<QQuickView> m_rootView;
3189 QStringList m_omitIM;
3190 bool m_mouseAttached;
3191
3192=== added file 'src/Ubuntu/UbuntuToolkit/splitview.cpp'
3193--- src/Ubuntu/UbuntuToolkit/splitview.cpp 1970-01-01 00:00:00 +0000
3194+++ src/Ubuntu/UbuntuToolkit/splitview.cpp 2016-09-06 13:08:47 +0000
3195@@ -0,0 +1,675 @@
3196+/*
3197+ * Copyright 2016 Canonical Ltd.
3198+ *
3199+ * This program is free software; you can redistribute it and/or modify
3200+ * it under the terms of the GNU Lesser General Public License as published by
3201+ * the Free Software Foundation; version 3.
3202+ *
3203+ * This program is distributed in the hope that it will be useful,
3204+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3205+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3206+ * GNU Lesser General Public License for more details.
3207+ *
3208+ * You should have received a copy of the GNU Lesser General Public License
3209+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3210+ *
3211+ * Author: Zsombor Egri <zsombor.egri@canonical.com>
3212+ */
3213+
3214+#include <QtQuick/private/qquickitem_p.h>
3215+#include <QtQuick/private/qquickanchors_p.h>
3216+#include <QtQml/QQmlInfo>
3217+
3218+#include "splitview_p.h"
3219+#include "splitview_p_p.h"
3220+#include "ucunits_p.h"
3221+#include "ucpagetreenode_p.h"
3222+
3223+#include "privates/splitviewhandler_p_p.h"
3224+
3225+#define DEFAULT_SPACING_DP 4
3226+
3227+UT_NAMESPACE_BEGIN
3228+
3229+/*!
3230+ * \qmltype SplitView
3231+ * \inqmlmodule Ubuntu.Components.Labs 1.0
3232+ * \inherits QQuickBasePositioner
3233+ * \ingroup ubuntu-labs
3234+ * \brief A view component with a flexible layout configuration setup.
3235+ *
3236+ * The component arranges the declared child elements horizontally based on an active
3237+ * column configuration layout. Child elements are considered to be views, and each view
3238+ * is identified with a column index, specified by the SplitView.column attached property.
3239+ * Views should not have width declared, because the width of each view is specified
3240+ * by the active layout's configuration (ViewColumn) and will overwrite the value
3241+ * specified by the view. On the other hand they should have a height specified, or
3242+ * they can be anchored to the top and bottom of the view. SplitView being a positioner,
3243+ * remember not to anchor horizontal anchor lines or anchor fill the columns.
3244+ *
3245+ * In order for a SplitView to show some content it must have at least one active layout
3246+ * present. Views which are not configured by the active layout will be hidden. Hidden
3247+ * views may be resized, therefore if the content is size sensitive (i.e. the amount
3248+ * shown differs depending on the space available), make sure the content of your view
3249+ * does take this into account.
3250+ * \code
3251+ * import QtQuick 2.4
3252+ * import Ubuntu.Components 1.3
3253+ * import Ubuntu.Components.Labs 1.0
3254+ *
3255+ * MainView {
3256+ * id: main
3257+ * width: units.gu(300)
3258+ * height: units.gu(80)
3259+ * SplitView {
3260+ * anchors.fill: parent
3261+ * layouts: [
3262+ * SplitViewLayout {
3263+ * when: main.width < units.gu(80)
3264+ * ViewColumn {
3265+ * fillWidth: true
3266+ * }
3267+ * },
3268+ * SplitViewLayout {
3269+ * when: main.width >= units.gu(80)
3270+ * ViewColumn {
3271+ * minimumWidth: units.gu(30)
3272+ * maximumWidth: units.gu(100)
3273+ * preferredWidth: units.gu(40)
3274+ * }
3275+ * ViewColumn {
3276+ * minimumWidth: units.gu(40)
3277+ * fillWidth: true
3278+ * }
3279+ * }
3280+ * ]
3281+ * }
3282+ *
3283+ * Page {
3284+ * id: column1
3285+ * height: parent.height
3286+ * }
3287+ * Page {
3288+ * id: column2
3289+ * height: parent.height
3290+ * }
3291+ * }
3292+ * \endcode
3293+ *
3294+ * The SplitView can be used with a Repeater in case the content of the view columns
3295+ * doesn't need to be preserved between layout changes. The example above with a
3296+ * Repeater would look as follows:
3297+ * \code
3298+ * import QtQuick 2.4
3299+ * import Ubuntu.Components 1.3
3300+ * import Ubuntu.Components.Labs 1.0
3301+ *
3302+ * MainView {
3303+ * id: main
3304+ * width: units.gu(300)
3305+ * height: units.gu(80)
3306+ * SplitView {
3307+ * id: view
3308+ * anchors.fill: parent
3309+ * layouts: [
3310+ * SplitViewLayout {
3311+ * when: main.width < units.gu(80)
3312+ * ViewColumn {
3313+ * fillWidth: true
3314+ * }
3315+ * },
3316+ * SplitViewLayout {
3317+ * when: main.width >= units.gu(80)
3318+ * ViewColumn {
3319+ * minimumWidth: units.gu(30)
3320+ * maximumWidth: units.gu(100)
3321+ * preferredWidth: units.gu(40)
3322+ * }
3323+ * ViewColumn {
3324+ * minimumWidth: units.gu(40)
3325+ * fillWidth: true
3326+ * }
3327+ * }
3328+ * ]
3329+ * }
3330+ *
3331+ * Repeater {
3332+ * model: view.activeLayout.columns
3333+ * Page {
3334+ * height: parent.height
3335+ * }
3336+ * }
3337+ * }
3338+ * \endcode
3339+ *
3340+ * \section2 Resizing
3341+ * SplitView provides the ability to resize view columns. Each column has an attached
3342+ * handle which provides the ability to resize the columns using a mouse or touch.
3343+ * Columns can be resized if the spacing property is set and the column configurations
3344+ * allow that (see \l spacing property).
3345+ *
3346+ * \section2 Attached properties
3347+ * SplitView provides a set of attached properties to each column view. Views can in
3348+ * this way have access to various values of the SplitView and configurations.
3349+ */
3350+
3351+/*!
3352+ * \qmlproperty real SplitView::spacing
3353+ * Spacing between view columns. A value bigger than 0 enables resizing of columns with
3354+ * a \l{ViewColumn::minimumWidth}{minimumWidth} lower than \l {ViewColumn::maximumWidth}{maximumWidth}.
3355+ * If spacing is 0 the columns cannot be resized.
3356+ * Defaults to 4 device pixels.
3357+ */
3358+void SplitView::setSpacing2(qreal spacing, bool reset)
3359+{
3360+ Q_D(SplitView);
3361+ if (reset && d->defaultSpacing) {
3362+ QObject::disconnect(*d->defaultSpacing);
3363+ delete d->defaultSpacing;
3364+ d->defaultSpacing = Q_NULLPTR;
3365+ }
3366+ if (qFuzzyCompare(this->spacing(), spacing)) {
3367+ return;
3368+ }
3369+ static_cast<QQuickBasePositioner*>(this)->setSpacing(spacing);
3370+}
3371+
3372+/******************************************************************************
3373+ * SplitViewAttached
3374+ */
3375+SplitViewAttached::SplitViewAttached(QObject *parent)
3376+ : QObject(*(new SplitViewAttachedPrivate), parent)
3377+{
3378+}
3379+
3380+SplitViewAttached *SplitViewAttached::get(QQuickItem *item)
3381+{
3382+ SplitViewAttached *attached = static_cast<SplitViewAttached*>(
3383+ qmlAttachedPropertiesObject<SplitView>(item, false));
3384+ return attached;
3385+}
3386+
3387+ViewColumn *SplitViewAttachedPrivate::getConfig(QQuickItem *attachee)
3388+{
3389+ SplitViewAttached *attached = SplitViewAttached::get(attachee);
3390+ if (!attached) {
3391+ return nullptr;
3392+ }
3393+ return get(attached)->config();
3394+}
3395+
3396+void SplitViewAttachedPrivate::configure(SplitView *view, int column)
3397+{
3398+ this->column = column;
3399+ splitView = view;
3400+ Q_EMIT q_func()->columnChanged();
3401+}
3402+
3403+void SplitViewAttached::resize(qreal delta)
3404+{
3405+ Q_D(SplitViewAttached);
3406+ ViewColumn *config = d->config();
3407+ if (config) {
3408+ config->resize(delta);
3409+ }
3410+}
3411+
3412+/*!
3413+ * \qmlattachedproperty ViewColumn SplitView::columnConfig
3414+ * The attached property holds the active layout's column configuration data.
3415+ * The value is null if there is no active configuration value provided for
3416+ * the column.
3417+ */
3418+UT_PREPEND_NAMESPACE(ViewColumn*) SplitViewAttachedPrivate::config()
3419+{
3420+ if (!splitView || column < 0) {
3421+ return nullptr;
3422+ }
3423+ SplitViewPrivate *d = SplitViewPrivate::get(splitView);
3424+ if (!d->activeLayout) {
3425+ return nullptr;
3426+ }
3427+ if (SplitViewLayoutPrivate::get(d->activeLayout)->columnData.size() <= column) {
3428+ return nullptr;
3429+ }
3430+ return SplitViewLayoutPrivate::get(d->activeLayout)->columnData[column];
3431+}
3432+
3433+/*!
3434+ * \qmlattachedproperty SplitView SplitView::view
3435+ * Contains the SplitView instance of the column.
3436+ */
3437+
3438+/*!
3439+ * \qmlattachedproperty int SplitView::column
3440+ * The property holds the column index the view is configured to.
3441+ */
3442+
3443+/******************************************************************************
3444+ * SplitView
3445+ */
3446+SplitViewPrivate::SplitViewPrivate(SplitView *qq)
3447+ : q_ptr(qq)
3448+{
3449+}
3450+
3451+SplitViewPrivate::~SplitViewPrivate()
3452+{
3453+}
3454+
3455+UT_PREPEND_NAMESPACE(SplitViewAttached) *SplitView::qmlAttachedProperties(QObject *owner)
3456+{
3457+ return new SplitViewAttached(owner);
3458+}
3459+
3460+// layouts property
3461+/*!
3462+ * \qmlproperty list<SplitViewLayout> SplitView::layouts
3463+ * The property holds the layout configurations declared for the given SplitView.
3464+ * \sa SplitViewLayout
3465+ */
3466+void SplitViewPrivate::layout_Append(QQmlListProperty<SplitViewLayout> *list, SplitViewLayout* layout)
3467+{
3468+ SplitView *view = static_cast<SplitView*>(list->object);
3469+ SplitViewPrivate *d = SplitViewPrivate::get(view);
3470+ d->columnLatouts.append(layout);
3471+ // parent layout to view
3472+ layout->setParent(view);
3473+ // capture layout activation
3474+ QObject::connect(layout, SIGNAL(whenChanged()), view, SLOT(changeLayout()), Qt::DirectConnection);
3475+ Q_EMIT view->layoutsChanged();
3476+}
3477+int SplitViewPrivate::layout_Count(QQmlListProperty<SplitViewLayout> *list)
3478+{
3479+ SplitView *view = static_cast<SplitView*>(list->object);
3480+ SplitViewPrivate *d = SplitViewPrivate::get(view);
3481+ return d->columnLatouts.size();
3482+}
3483+SplitViewLayout *SplitViewPrivate::layout_At(QQmlListProperty<SplitViewLayout> *list, int index)
3484+{
3485+ SplitView *view = static_cast<SplitView*>(list->object);
3486+ SplitViewPrivate *d = SplitViewPrivate::get(view);
3487+ return d->columnLatouts.at(index);
3488+}
3489+void SplitViewPrivate::layout_Clear(QQmlListProperty<SplitViewLayout> *list)
3490+{
3491+ SplitView *view = static_cast<SplitView*>(list->object);
3492+ SplitViewPrivate *d = SplitViewPrivate::get(view);
3493+ for (SplitViewLayout *layout : d->columnLatouts) {
3494+ // disconnect layout activation
3495+ QObject::disconnect(layout, SIGNAL(whenChanged()), view, SLOT(changeLayout()));
3496+ delete layout;
3497+ }
3498+ d->columnLatouts.clear();
3499+ d->activeLayout = nullptr;
3500+ Q_EMIT view->layoutsChanged();
3501+}
3502+QQmlListProperty<SplitViewLayout> SplitViewPrivate::layouts()
3503+{
3504+ Q_Q(SplitView);
3505+ return QQmlListProperty<SplitViewLayout>(q, (void*)&columnLatouts,
3506+ &layout_Append,
3507+ &layout_Count,
3508+ &layout_At,
3509+ &layout_Clear);
3510+}
3511+
3512+/*!
3513+ * \qmlproperty SplitViewLayout SplitView::activeLayout
3514+ * \readonly
3515+ * The property holds the active SplitViewLayout instance, or null is no layout
3516+ * is active.
3517+ */
3518+UT_PREPEND_NAMESPACE(SplitViewLayout) *SplitViewPrivate::getActiveLayout()
3519+{
3520+ return activeLayout;
3521+}
3522+
3523+// invoked when one of the SplitViewLayouts emits whenChanged()
3524+void SplitViewPrivate::changeLayout()
3525+{
3526+ // go through layouts and check who's the active one
3527+ SplitViewLayout *newActive = nullptr;
3528+ for (SplitViewLayout *layout : columnLatouts) {
3529+ if (SplitViewLayoutPrivate::get(layout)->when) {
3530+ newActive = layout;
3531+ break;
3532+ }
3533+ }
3534+ if (newActive == activeLayout) {
3535+ return;
3536+ }
3537+
3538+ // Q: should we reset the sizes of the previous layout?
3539+ // at least it feels right to preserve the last state of the layout...
3540+ activeLayout = newActive;
3541+
3542+ Q_EMIT q_func()->activeLayoutChanged();
3543+
3544+ updateLayout();
3545+ if (q_func()->sender()) {
3546+ // was called by a whenChanged() signal invocation
3547+ recalculateWidths(RecalculateAll);
3548+ }
3549+}
3550+
3551+void SplitViewPrivate::updateLayout()
3552+{
3553+ Q_Q(SplitView);
3554+ for (QQuickItem *child : q->childItems()) {
3555+ bool visible = true;
3556+ ViewColumn *columnConfig = SplitViewAttachedPrivate::getConfig(child);
3557+ if (!columnConfig) {
3558+ // no configuration for the column, hide it
3559+ visible = false;
3560+ } else {
3561+ ViewColumnPrivate *config = ViewColumnPrivate::get(columnConfig);
3562+ visible = activeLayout
3563+ && (config->column < SplitViewLayoutPrivate::get(activeLayout)->columnData.size());
3564+ }
3565+ dirty = dirty | (child->isVisible() != visible);
3566+ child->setVisible(visible);
3567+ }
3568+}
3569+
3570+void SplitViewPrivate::recalculateWidths(RelayoutOperation operation)
3571+{
3572+ if (!activeLayout || (!QQuickItemPrivate::get(q_func())->componentComplete && !dirty)) {
3573+ return;
3574+ }
3575+ Q_Q(SplitView);
3576+
3577+ // remove the spacing from the width
3578+ qreal fillWidth = q->width() - q->spacing() * (SplitViewLayoutPrivate::get(activeLayout)->columnData.size() - 1);
3579+ // stack of columns with fillWidth true
3580+ QList<QQuickItem*> fillStack;
3581+
3582+ for (QQuickItem *child : q->childItems()) {
3583+ ViewColumn *columnConfig = SplitViewAttachedPrivate::getConfig(child);
3584+ if (!columnConfig) {
3585+ continue;
3586+ }
3587+ ViewColumnPrivate *config = ViewColumnPrivate::get(columnConfig);
3588+
3589+ if (config->fillWidth && !config->resized) {
3590+ // add to the fillWidth stack
3591+ if (operation & CalculateFillWidth) {
3592+ fillStack << child;
3593+ }
3594+ } else {
3595+ if (operation & SetPreferredSize) {
3596+ child->setWidth(config->preferredWidth);
3597+ }
3598+ if (operation & CalculateFillWidth) {
3599+ fillWidth -= config->preferredWidth;
3600+ }
3601+ }
3602+ }
3603+
3604+ // split the width between the stacked fillWidth columns
3605+ if (fillStack.size() && (operation & CalculateFillWidth)) {
3606+ fillWidth /= fillStack.size();
3607+ for (QQuickItem *child : fillStack) {
3608+ // even though the column is fillWidth, it may have min and max specified;
3609+ // check if the size can be applied
3610+ ViewColumnPrivate *config = ViewColumnPrivate::get(SplitViewAttachedPrivate::getConfig(child));
3611+ config->setPreferredWidth(fillWidth, false);
3612+ // update preferredWidth so it can be used in case of resize
3613+ child->setWidth(config->preferredWidth);
3614+ }
3615+ }
3616+ dirty = false;
3617+}
3618+
3619+/*!
3620+ * \qmlproperty Component SplitView::handleDelegate
3621+ * The property holds the delegate to be shown for the column resizing handle.
3622+ * The delegate is for pure visual, mouse and touch handling is provided by the
3623+ * SplitView component itself. The component provides a context property called
3624+ * \e handle which embeds the visuals. This can be used to anchor the visuals
3625+ * to the resize handle. The thickness of the handle is driven by the \l spacing
3626+ * property.
3627+ * \code
3628+ * import QtQuick 2.4
3629+ * import Ubuntu.Components 1.3
3630+ * import Ubuntu.Components.Labs 1.0
3631+ *
3632+ * MainView {
3633+ * id: main
3634+ * width: units.gu(300)
3635+ * height: units.gu(80)
3636+ *
3637+ * SplitView {
3638+ * anchors.fill: parent
3639+ * handleDelegate: Rectangle {
3640+ * anchors {
3641+ * fill: parent
3642+ * leftMargin: units.dp(2)
3643+ * rightMargin: units.dp(2)
3644+ * topMargin: handle.height / 2 - units.gu(3)
3645+ * bottomMargin: handle.height / 2 - units.gu(3)
3646+ * }
3647+ * color: UbuntuColors.graphite
3648+ * scale: handle.containsMouse || handle.pressed ? 1.6 : 1.0
3649+ * Behavior on scale { UbuntuNumberAnimation {} }
3650+ * }
3651+ * layouts: [
3652+ * SplitViewLayout {
3653+ * when: main.width < units.gu(80)
3654+ * ViewColumn {
3655+ * fillWidth: true
3656+ * }
3657+ * },
3658+ * SplitViewLayout {
3659+ * when: main.width >= units.gu(80)
3660+ * ViewColumn {
3661+ * minimumWidth: units.gu(30)
3662+ * maximumWidth: units.gu(100)
3663+ * preferredWidth: units.gu(40)
3664+ * }
3665+ * ViewColumn {
3666+ * minimumWidth: units.gu(40)
3667+ * fillWidth: true
3668+ * }
3669+ * }
3670+ * ]
3671+ * }
3672+ *
3673+ * Page {
3674+ * id: column1
3675+ * height: parent.height
3676+ * }
3677+ * Page {
3678+ * id: column2
3679+ * height: parent.height
3680+ * }
3681+ * }
3682+ * \endcode
3683+ */
3684+
3685+SplitView::SplitView(QQuickItem *parent)
3686+ : QQuickBasePositioner(Horizontal, parent)
3687+ , d_ptr(new SplitViewPrivate(this))
3688+{
3689+ Q_D(SplitView);
3690+ d->init();
3691+}
3692+
3693+SplitView::SplitView(SplitViewPrivate &dd, QQuickItem *parent)
3694+ : QQuickBasePositioner(Vertical, parent)
3695+ , d_ptr(&dd)
3696+{
3697+ Q_D(SplitView);
3698+ d->init();
3699+}
3700+
3701+SplitView::~SplitView()
3702+{
3703+ Q_D(SplitView);
3704+ delete d;
3705+}
3706+
3707+void SplitViewPrivate::init()
3708+{
3709+ Q_Q(SplitView);
3710+ auto spacingHandle = [q]() {
3711+ q->setSpacing2(UCUnits::instance(q)->dp(DEFAULT_SPACING_DP), false);
3712+ };
3713+ defaultSpacing = new QMetaObject::Connection;
3714+ *defaultSpacing = QObject::connect(UCUnits::instance(q), &UCUnits::gridUnitChanged, spacingHandle);
3715+
3716+ // connect the spacingChanged signals
3717+ QObject::connect(q, SIGNAL(spacingChanged()), q, SIGNAL(spacingChanged2()));
3718+
3719+ // set the defaults
3720+ q->setSpacing2(UCUnits::instance(q)->dp(DEFAULT_SPACING_DP), false);
3721+}
3722+
3723+void SplitView::doPositioning(QSizeF *contentSize)
3724+{
3725+ // Inspired from QtQuick QQuickRow code
3726+ // FIXME: revisit the code once we move to Qt 5.6 as there were more properties added to positioner
3727+
3728+ // calculate the layout before we go into the positioning
3729+ d_func()->recalculateWidths(SplitViewPrivate::RecalculateAll);
3730+
3731+ //Precondition: All items in the positioned list have a valid item pointer and should be positioned
3732+ QQuickItemPrivate *d = QQuickItemPrivate::get(this);
3733+ qreal hoffset = 0;
3734+
3735+ QList<qreal> hoffsets;
3736+ for (int ii = 0; ii < positionedItems.count(); ++ii) {
3737+ PositionedItem &child = positionedItems[ii];
3738+
3739+ if (!d->effectiveLayoutMirror) {
3740+ positionItemX(hoffset, &child);
3741+ } else {
3742+ hoffsets << hoffset;
3743+ }
3744+
3745+ contentSize->setHeight(qMax(contentSize->height(), child.item->height()));
3746+
3747+ hoffset += child.item->width();
3748+ hoffset += spacing();
3749+ }
3750+
3751+ if (hoffset != 0) { //If we positioned any items, undo the extra spacing from the last item
3752+ hoffset -= spacing();
3753+ }
3754+ contentSize->setWidth(hoffset);
3755+
3756+ if (!d->effectiveLayoutMirror) {
3757+ return;
3758+ }
3759+
3760+ //Right to Left layout
3761+ qreal end = 0;
3762+ if (!widthValid()) {
3763+ end = contentSize->width();
3764+ } else {
3765+ end = width();
3766+ }
3767+
3768+ int acc = 0;
3769+ for (int ii = 0; ii < positionedItems.count(); ++ii) {
3770+ PositionedItem &child = positionedItems[ii];
3771+ hoffset = end - hoffsets[acc++] - child.item->width();
3772+ positionItemX(hoffset, &child);
3773+ }
3774+}
3775+
3776+void SplitView::reportConflictingAnchors()
3777+{
3778+ // Inspired from QtQuick QQuickColumn code
3779+ bool anchorConflict = false;
3780+ for (int ii = 0; ii < positionedItems.count(); ++ii) {
3781+ const PositionedItem &child = positionedItems.at(ii);
3782+ if (child.item) {
3783+ QQuickAnchors *anchors = QQuickItemPrivate::get(static_cast<QQuickItem *>(child.item))->_anchors;
3784+ if (anchors) {
3785+ // can we patch out the anchors (Page?)
3786+ if (qobject_cast<UCPageTreeNode*>(child.item)) {
3787+ // unset left anchor
3788+ anchors->resetLeft();
3789+ }
3790+ QQuickAnchors::Anchors usedAnchors = anchors->usedAnchors();
3791+ if (usedAnchors & QQuickAnchors::LeftAnchor ||
3792+ usedAnchors & QQuickAnchors::RightAnchor ||
3793+ usedAnchors & QQuickAnchors::HCenterAnchor ||
3794+ anchors->fill() || anchors->centerIn()) {
3795+ anchorConflict = true;
3796+ break;
3797+ }
3798+ }
3799+ }
3800+ }
3801+ if (anchorConflict) {
3802+ qmlInfo(this) << "Cannot specify left, right, horizontalCenter, fill or centerIn anchors for items inside SplitView."
3803+ << " SplitView will not function.";
3804+ }
3805+}
3806+
3807+void SplitView::componentComplete()
3808+{
3809+ Q_D(SplitView);
3810+ d->changeLayout();
3811+ QQuickBasePositioner::componentComplete();
3812+}
3813+
3814+void SplitView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
3815+{
3816+ QQuickBasePositioner::geometryChanged(newGeometry, oldGeometry);
3817+ // if we have fillWidths, recalculate those!
3818+ // call this on horizontal resize, vertical one will do its job
3819+ if (newGeometry.width() != oldGeometry.width()) {
3820+ Q_D(SplitView);
3821+ d->recalculateWidths(SplitViewPrivate::CalculateFillWidth);
3822+ }
3823+}
3824+
3825+void SplitView::itemChange(ItemChange change, const ItemChangeData &data)
3826+{
3827+ QQuickBasePositioner::itemChange(change, data);
3828+ // we must exclude Repeater
3829+ switch (change) {
3830+ case ItemChildAddedChange:
3831+ if (data.item && !data.item->inherits("QQuickRepeater")) {
3832+ // attach properties and configure
3833+ SplitViewAttached *attached = static_cast<SplitViewAttached*>(
3834+ qmlAttachedPropertiesObject<SplitView>(data.item, true));
3835+
3836+ Q_D(SplitView);
3837+ SplitViewAttachedPrivate::get(attached)->configure(this, d->viewCount++);
3838+
3839+ // attach the split handler to it
3840+ SplitViewHandler *handler = new SplitViewHandler(data.item);
3841+ handler->connectToView(this);
3842+ }
3843+ break;
3844+ case ItemChildRemovedChange:
3845+ if (data.item && !data.item->inherits("QQuickRepeater")) {
3846+ Q_D(SplitView);
3847+ d->viewCount--;
3848+ }
3849+ break;
3850+ default: // ommit the rest
3851+ break;
3852+ }
3853+}
3854+
3855+/******************************************************************
3856+ * properties
3857+ */
3858+
3859+void SplitViewPrivate::setHandle(QQmlComponent *delegate)
3860+{
3861+ if (handleDelegate == delegate) {
3862+ return;
3863+ }
3864+ handleDelegate = delegate;
3865+ Q_EMIT q_func()->handleDelegateChanged();
3866+}
3867+
3868+UT_NAMESPACE_END
3869+
3870+#include "moc_splitview_p.cpp"
3871
3872=== added file 'src/Ubuntu/UbuntuToolkit/splitview_p.h'
3873--- src/Ubuntu/UbuntuToolkit/splitview_p.h 1970-01-01 00:00:00 +0000
3874+++ src/Ubuntu/UbuntuToolkit/splitview_p.h 2016-09-06 13:08:47 +0000
3875@@ -0,0 +1,160 @@
3876+/*
3877+ * Copyright 2016 Canonical Ltd.
3878+ *
3879+ * This program is free software; you can redistribute it and/or modify
3880+ * it under the terms of the GNU Lesser General Public License as published by
3881+ * the Free Software Foundation; version 3.
3882+ *
3883+ * This program is distributed in the hope that it will be useful,
3884+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3885+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3886+ * GNU Lesser General Public License for more details.
3887+ *
3888+ * You should have received a copy of the GNU Lesser General Public License
3889+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3890+ *
3891+ * Author: Zsombor Egri <zsombor.egri@canonical.com>
3892+ */
3893+
3894+#ifndef SPLITVIEW_P_H
3895+#define SPLITVIEW_P_H
3896+
3897+#include <QtQuick/private/qquickpositioners_p.h>
3898+
3899+#include "ubuntutoolkitglobal.h"
3900+
3901+UT_NAMESPACE_BEGIN
3902+
3903+class ViewColumnPrivate;
3904+class ViewColumn : public QObject, public QQmlParserStatus
3905+{
3906+ Q_OBJECT
3907+ Q_INTERFACES(QQmlParserStatus)
3908+ Q_PRIVATE_PROPERTY(ViewColumn::d_func(), bool fillWidth MEMBER fillWidth WRITE setFillWidth NOTIFY fillWidthChanged)
3909+ Q_PRIVATE_PROPERTY(ViewColumn::d_func(), qreal minimumWidth MEMBER minimumWidth WRITE setMinimumWidth NOTIFY minimumWidthChanged)
3910+ Q_PRIVATE_PROPERTY(ViewColumn::d_func(), qreal maximumWidth MEMBER maximumWidth WRITE setMaximumWidth NOTIFY maximumWidthChanged)
3911+ Q_PRIVATE_PROPERTY(ViewColumn::d_func(), qreal preferredWidth MEMBER preferredWidth WRITE setPreferredWidth NOTIFY preferredWidthChanged)
3912+public:
3913+ explicit ViewColumn(QObject *parent = 0);
3914+
3915+ bool resize(qreal delta);
3916+
3917+Q_SIGNALS:
3918+ void minimumWidthChanged();
3919+ void maximumWidthChanged();
3920+ void preferredWidthChanged();
3921+ void fillWidthChanged();
3922+
3923+protected:
3924+ // from QQmlParserStatus
3925+ void classBegin() override {}
3926+ void componentComplete() override;
3927+private:
3928+ Q_DECLARE_PRIVATE(ViewColumn)
3929+};
3930+
3931+class SplitViewLayoutPrivate;
3932+class SplitViewLayout : public QObject
3933+{
3934+ Q_OBJECT
3935+
3936+ Q_PRIVATE_PROPERTY(SplitViewLayout::d_func(), bool when MEMBER when NOTIFY whenChanged)
3937+#ifdef Q_QDOC
3938+ Q_PRIVATE_PROPERTY(SplitViewLayout::d_func(), QQmlListProperty<ViewColumn> columns READ columns NOTIFY columnsChanged DESIGNABLE false)
3939+#else
3940+ Q_PRIVATE_PROPERTY(SplitViewLayout::d_func(), QQmlListProperty<UT_PREPEND_NAMESPACE(ViewColumn)> columns READ columns NOTIFY columnsChanged DESIGNABLE false)
3941+#endif
3942+ Q_CLASSINFO("DefaultProperty", "columns")
3943+public:
3944+ explicit SplitViewLayout(QObject *parent = 0);
3945+
3946+Q_SIGNALS:
3947+ void whenChanged();
3948+ void columnsChanged();
3949+
3950+private:
3951+ Q_DECLARE_PRIVATE(SplitViewLayout)
3952+};
3953+
3954+class SplitView;
3955+class SplitViewAttachedPrivate;
3956+class SplitViewAttached : public QObject
3957+{
3958+ Q_OBJECT
3959+ Q_PRIVATE_PROPERTY(SplitViewAttached::d_func(), int column READ getColumn NOTIFY columnChanged)
3960+#ifdef Q_QDOC
3961+ Q_PRIVATE_PROPERTY(SplitViewAttached::d_func(), SplitView* view READ view)
3962+ Q_PRIVATE_PROPERTY(SplitViewAttached::d_func(), ViewColumn* columnConfig READ config NOTIFY columnChanged)
3963+#else
3964+ Q_PRIVATE_PROPERTY(SplitViewAttached::d_func(), UT_PREPEND_NAMESPACE(SplitView*) view READ view)
3965+ Q_PRIVATE_PROPERTY(SplitViewAttached::d_func(), UT_PREPEND_NAMESPACE(ViewColumn*) columnConfig READ config NOTIFY columnChanged)
3966+#endif
3967+public:
3968+ explicit SplitViewAttached(QObject *parent = 0);
3969+
3970+ static SplitViewAttached *get(QQuickItem *item);
3971+
3972+ void resize(qreal delta);
3973+
3974+Q_SIGNALS:
3975+ void columnChanged();
3976+
3977+private:
3978+ Q_DECLARE_PRIVATE(SplitViewAttached)
3979+};
3980+
3981+class SplitViewPrivate;
3982+class SplitView : public QQuickBasePositioner
3983+{
3984+ Q_OBJECT
3985+#ifdef Q_QDOC
3986+ Q_PRIVATE_PROPERTY(SplitView::d_func(), QQmlListProperty<SplitViewLayout> layouts READ layouts NOTIFY layoutsChanged DESIGNABLE false)
3987+ Q_PRIVATE_PROPERTY(SplitView::d_func(), SplitViewLayout *activeLayout READ getActiveLayout NOTIFY activeLayoutChanged)
3988+#else
3989+ Q_PRIVATE_PROPERTY(SplitView::d_func(), QQmlListProperty<UT_PREPEND_NAMESPACE(SplitViewLayout)> layouts READ layouts NOTIFY layoutsChanged DESIGNABLE false)
3990+ Q_PRIVATE_PROPERTY(SplitView::d_func(), UT_PREPEND_NAMESPACE(SplitViewLayout) *activeLayout READ getActiveLayout NOTIFY activeLayoutChanged)
3991+#endif
3992+ Q_PRIVATE_PROPERTY(SplitView::d_func(), QQmlComponent *handleDelegate MEMBER handleDelegate WRITE setHandle NOTIFY handleDelegateChanged)
3993+ // overrides
3994+ Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing2 NOTIFY spacingChanged2)
3995+public:
3996+ explicit SplitView(QQuickItem *parent = 0);
3997+
3998+ static UT_PREPEND_NAMESPACE(SplitViewAttached) *qmlAttachedProperties(QObject*);
3999+
4000+Q_SIGNALS:
4001+ void layoutsChanged();
4002+ void activeLayoutChanged();
4003+ void handleDelegateChanged();
4004+ void spacingChanged2();
4005+
4006+protected:
4007+ SplitView(SplitViewPrivate &, QQuickItem *);
4008+ ~SplitView();
4009+
4010+ // property setters
4011+ void setSpacing2(qreal spacing, bool reset = true);
4012+
4013+ // from QQuickBasePositioner
4014+ void doPositioning(QSizeF *contentSize) override;
4015+ void reportConflictingAnchors() override;
4016+
4017+ // overrides
4018+ void componentComplete() override;
4019+ void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
4020+ void itemChange(ItemChange, const ItemChangeData &) override;
4021+private:
4022+ // QQuickBasePositionerPrivate is not an exported API, therefore we cannot derive from it
4023+ SplitViewPrivate* const d_ptr;
4024+ Q_DECLARE_PRIVATE_D(d_ptr, SplitView)
4025+ Q_PRIVATE_SLOT(d_func(), void changeLayout())
4026+};
4027+
4028+UT_NAMESPACE_END
4029+
4030+QML_DECLARE_TYPE(UT_PREPEND_NAMESPACE(ViewColumn))
4031+QML_DECLARE_TYPE(UT_PREPEND_NAMESPACE(SplitViewLayout))
4032+QML_DECLARE_TYPE(UT_PREPEND_NAMESPACE(SplitView))
4033+QML_DECLARE_TYPEINFO(UT_PREPEND_NAMESPACE(SplitView), QML_HAS_ATTACHED_PROPERTIES)
4034+
4035+#endif // SPLITVIEW_P_H
4036
4037=== added file 'src/Ubuntu/UbuntuToolkit/splitview_p_p.h'
4038--- src/Ubuntu/UbuntuToolkit/splitview_p_p.h 1970-01-01 00:00:00 +0000
4039+++ src/Ubuntu/UbuntuToolkit/splitview_p_p.h 2016-09-06 13:08:47 +0000
4040@@ -0,0 +1,154 @@
4041+/*
4042+ * Copyright 2016 Canonical Ltd.
4043+ *
4044+ * This program is free software; you can redistribute it and/or modify
4045+ * it under the terms of the GNU Lesser General Public License as published by
4046+ * the Free Software Foundation; version 3.
4047+ *
4048+ * This program is distributed in the hope that it will be useful,
4049+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4050+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4051+ * GNU Lesser General Public License for more details.
4052+ *
4053+ * You should have received a copy of the GNU Lesser General Public License
4054+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4055+ *
4056+ * Author: Zsombor Egri <zsombor.egri@canonical.com>
4057+ */
4058+
4059+#ifndef SPLITVIEW_P_P_H
4060+#define SPLITVIEW_P_P_H
4061+
4062+#include "splitview_p.h"
4063+#include <QtCore/private/qobject_p.h>
4064+
4065+UT_NAMESPACE_BEGIN
4066+
4067+class ViewColumnPrivate : public QObjectPrivate
4068+{
4069+ Q_DECLARE_PUBLIC(ViewColumn)
4070+
4071+public:
4072+ ViewColumnPrivate() {}
4073+
4074+ static ViewColumnPrivate *get(ViewColumn *q)
4075+ {
4076+ return q ? q->d_func() : nullptr;
4077+ }
4078+
4079+ void setMinimumWidth(qreal width);
4080+ void setMaximumWidth(qreal width);
4081+ void setPreferredWidth(qreal width, bool notify = true);
4082+ void setFillWidth(bool fill);
4083+
4084+ void recalculateLayoutContent();
4085+
4086+ qreal minimumWidth{0.0};
4087+ qreal maximumWidth{std::numeric_limits<qreal>::max()};
4088+ qreal preferredWidth{0.0};
4089+ int column{-1};
4090+ bool fillWidth{false};
4091+ bool resized{false};
4092+ bool completed{false};
4093+};
4094+
4095+class SplitViewLayoutPrivate : public QObjectPrivate
4096+{
4097+ Q_DECLARE_PUBLIC(SplitViewLayout)
4098+public:
4099+ SplitViewLayoutPrivate() {}
4100+ static SplitViewLayoutPrivate *get(SplitViewLayout *q)
4101+ {
4102+ return q->d_func();
4103+ }
4104+
4105+ QQmlListProperty<UT_PREPEND_NAMESPACE(ViewColumn)> columns();
4106+
4107+ QList<ViewColumn*> columnData;
4108+ bool when{false};
4109+
4110+private:
4111+ static void columns_Append(QQmlListProperty<ViewColumn> *, ViewColumn*);
4112+ static int columns_Count(QQmlListProperty<ViewColumn> *);
4113+ static ViewColumn *columns_At(QQmlListProperty<ViewColumn> *, int);
4114+ static void columns_Clear(QQmlListProperty<ViewColumn> *);
4115+};
4116+
4117+class SplitViewAttachedPrivate : public QObjectPrivate
4118+{
4119+ Q_DECLARE_PUBLIC(SplitViewAttached)
4120+public:
4121+ SplitViewAttachedPrivate() {}
4122+
4123+ static SplitViewAttachedPrivate *get(SplitViewAttached *q)
4124+ {
4125+ return q->d_func();
4126+ }
4127+ static ViewColumn *getConfig(QQuickItem *attachee);
4128+
4129+ UT_PREPEND_NAMESPACE(SplitView*) view() const
4130+ {
4131+ return splitView;
4132+ }
4133+ int getColumn() const
4134+ {
4135+ return column;
4136+ }
4137+ void configure(SplitView *view, int column);
4138+ UT_PREPEND_NAMESPACE(ViewColumn*) config();
4139+
4140+ SplitView *splitView{nullptr};
4141+ int column{-1};
4142+};
4143+
4144+class SplitViewPrivate
4145+{
4146+ SplitView *const q_ptr{nullptr};
4147+ Q_DECLARE_PUBLIC(SplitView)
4148+
4149+public:
4150+ enum RelayoutOperation {
4151+ SetPreferredSize = 0x01,
4152+ CalculateFillWidth = 0x02,
4153+ RecalculateAll = 0xFF
4154+ };
4155+
4156+ SplitViewPrivate(SplitView *qq);
4157+ virtual ~SplitViewPrivate();
4158+ void init();
4159+
4160+ static SplitViewPrivate *get(SplitView *q)
4161+ {
4162+ return q->d_func();
4163+ }
4164+
4165+ QQmlListProperty<QObject> data();
4166+ QQmlListProperty<UT_PREPEND_NAMESPACE(SplitViewLayout)> layouts();
4167+ UT_PREPEND_NAMESPACE(SplitViewLayout) *getActiveLayout();
4168+
4169+ void updateLayout();
4170+ void recalculateWidths(RelayoutOperation operation);
4171+ void setHandle(QQmlComponent *delegate);
4172+
4173+ // private slots
4174+ void changeLayout();
4175+
4176+ // members
4177+ QList<SplitViewLayout*> columnLatouts;
4178+ SplitViewLayout* activeLayout{nullptr};
4179+ QQmlComponent *handleDelegate{nullptr};
4180+ QMetaObject::Connection *defaultSpacing{nullptr};
4181+ int viewCount{0};
4182+ bool dirty{false};
4183+
4184+private:
4185+ static void layout_Append(QQmlListProperty<SplitViewLayout> *, SplitViewLayout*);
4186+ static int layout_Count(QQmlListProperty<SplitViewLayout> *);
4187+ static SplitViewLayout *layout_At(QQmlListProperty<SplitViewLayout> *, int);
4188+ static void layout_Clear(QQmlListProperty<SplitViewLayout> *);
4189+};
4190+
4191+UT_NAMESPACE_END
4192+
4193+#endif // SPLITVIEW_P_P_H
4194+
4195
4196=== added file 'src/Ubuntu/UbuntuToolkit/splitviewlayout.cpp'
4197--- src/Ubuntu/UbuntuToolkit/splitviewlayout.cpp 1970-01-01 00:00:00 +0000
4198+++ src/Ubuntu/UbuntuToolkit/splitviewlayout.cpp 2016-09-06 13:08:47 +0000
4199@@ -0,0 +1,252 @@
4200+/* Copyright 2016 Canonical Ltd.
4201+ *
4202+ * This program is free software; you can redistribute it and/or modify
4203+ * it under the terms of the GNU Lesser General Public License as published by
4204+ * the Free Software Foundation; version 3.
4205+ *
4206+ * This program is distributed in the hope that it will be useful,
4207+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4208+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4209+ * GNU Lesser General Public License for more details.
4210+ *
4211+ * You should have received a copy of the GNU Lesser General Public License
4212+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4213+ *
4214+ * Author: Zsombor Egri <zsombor.egri@canonical.com>
4215+ */
4216+
4217+#include <QtQuick/private/qquickitem_p.h>
4218+#include <QtQuick/private/qquickanchors_p.h>
4219+#include <QtQml/QQmlInfo>
4220+
4221+#include "splitview_p.h"
4222+#include "splitview_p_p.h"
4223+#include "ucmathutils_p.h"
4224+
4225+UT_NAMESPACE_BEGIN
4226+
4227+/******************************************************************************
4228+ * ViewColumn configuration object
4229+ */
4230+/*!
4231+ * \qmltype ViewColumn
4232+ * \inmodule Ubuntu.Components.Labs
4233+ * \ingroup ubuntu-labs
4234+ * \brief View column metrics configuration for SplitView.
4235+ *
4236+ * The component provides width metrics configuration for SplitView layout
4237+ * columns. The values are applied on columns by an active SplitViewLayout.
4238+ * On resizing, the values are preserved for the entire lifetime of the
4239+ * component, even when the active layout is changed into an other one.
4240+ * When changed back, the previous values will be used.
4241+ */
4242+ViewColumn::ViewColumn(QObject *parent)
4243+ : QObject(*(new ViewColumnPrivate), parent)
4244+{
4245+}
4246+
4247+void ViewColumn::componentComplete()
4248+{
4249+ // check preferredWidth, its value may need fixing
4250+ Q_D(ViewColumn);
4251+ d->setPreferredWidth(d->preferredWidth);
4252+ d->completed = true;
4253+}
4254+
4255+void ViewColumnPrivate::recalculateLayoutContent()
4256+{
4257+ SplitViewLayout *layout = qobject_cast<SplitViewLayout*>(parent);
4258+ if (!layout) {
4259+ qFatal("ViewColumn declared outside of SplitViewLayout");
4260+ }
4261+ SplitView *view = qobject_cast<SplitView*>(SplitViewLayoutPrivate::get(layout)->parent);
4262+ if (!view) {
4263+ qFatal("SplitViewLayout not used in any SplitView");
4264+ }
4265+
4266+ SplitViewPrivate *dView = SplitViewPrivate::get(view);
4267+ if (dView->activeLayout == layout) {
4268+ dView->recalculateWidths(SplitViewPrivate::RecalculateAll);
4269+ }
4270+}
4271+
4272+/*!
4273+ * \qmlproperty real ViewColumn::minimumWidth
4274+ * Specifies the minimum width of the column. The number must be a positive value
4275+ * and less or equal than the maximumWidth value.
4276+ */
4277+void ViewColumnPrivate::setMinimumWidth(qreal width)
4278+{
4279+ if (qFuzzyCompare(minimumWidth, width)) {
4280+ return;
4281+ }
4282+ if (width < 0.0) {
4283+ qmlInfo(q_func()) << "minimumWidth cannot be a negative value";
4284+ return;
4285+ }
4286+ if (width > maximumWidth) {
4287+ qmlInfo(q_func()) << "minimumWidth is greater than maximumWidth";
4288+ return;
4289+ }
4290+ minimumWidth = width;
4291+ Q_EMIT q_func()->minimumWidthChanged();
4292+ // clamp preferredWidth if needed
4293+ setPreferredWidth(preferredWidth);
4294+ recalculateLayoutContent();
4295+}
4296+
4297+/*!
4298+ * \qmlproperty real ViewColumn::maximumWidth
4299+ * Specifies the maximum width of the column. The number must be a positive value
4300+ * and bigger than the minimumWidth value.
4301+ */
4302+void ViewColumnPrivate::setMaximumWidth(qreal width)
4303+{
4304+ if (qFuzzyCompare(maximumWidth, width)) {
4305+ return;
4306+ }
4307+ if (width < 0.0) {
4308+ qmlInfo(q_func()) << "maximumWidth cannot be a negative value";
4309+ return;
4310+ }
4311+ if (width < minimumWidth) {
4312+ qmlInfo(q_func()) << "maximumWidth is smaller than minimumWidth";
4313+ return;
4314+ }
4315+ maximumWidth = width;
4316+ Q_EMIT q_func()->maximumWidthChanged();
4317+ setPreferredWidth(preferredWidth);
4318+ recalculateLayoutContent();
4319+}
4320+
4321+/*!
4322+ * \qmlproperty real ViewColumn::preferredWidth
4323+ * The property holds the preferred width of the column. The value must be situated
4324+ * in between minimumWidth and maximumWidth. In case fillWidth is set, the value will
4325+ * hold the actual width of the column, but setting its value will not affect the
4326+ * width of the column.
4327+ */
4328+void ViewColumnPrivate::setPreferredWidth(qreal width, bool notify)
4329+{
4330+ // clamp
4331+ width = UCMathUtils::clamp(width, minimumWidth, maximumWidth);
4332+ if (qFuzzyCompare(preferredWidth, width)) {
4333+ return;
4334+ }
4335+ preferredWidth = width;
4336+ if (notify) {
4337+ Q_EMIT q_func()->preferredWidthChanged();
4338+ recalculateLayoutContent();
4339+ }
4340+}
4341+
4342+/*!
4343+ * \qmlproperty bool ViewColumn::fillWidth
4344+ * If set, the column width will take the space available after all the other
4345+ * columns with non-fill width are configured. This means that if there is more
4346+ * than one column configured to fill width, the reminder width is split evenly
4347+ * between these columns. If all columns are configured to fill width, the colum
4348+ * widths will be split evenly between all the columns.
4349+ * \note When a column configured with fillWidth is resized, the properties will
4350+ * not be altered, but the fillWidth wioll no longer be taken into account. Instead,
4351+ * the preferredWidth will drive the width of that column from that point on.
4352+ */
4353+void ViewColumnPrivate::setFillWidth(bool fill)
4354+{
4355+ if (fill == fillWidth) {
4356+ return;
4357+ }
4358+ fillWidth = fill;
4359+ Q_EMIT q_func()->fillWidthChanged();
4360+ recalculateLayoutContent();
4361+}
4362+
4363+bool ViewColumn::resize(qreal delta)
4364+{
4365+ Q_D(ViewColumn);
4366+ // apply limits
4367+ qreal newWidth = d->preferredWidth + delta;
4368+ // clamp
4369+ newWidth = UCMathUtils::clamp(newWidth, d->minimumWidth, d->maximumWidth);
4370+ if (newWidth != d->preferredWidth) {
4371+ d->resized = true;
4372+ d->setPreferredWidth(newWidth);
4373+ return true;
4374+ }
4375+ return false;
4376+}
4377+
4378+/******************************************************************************
4379+ * SplitViewLayout layouts configuration
4380+ */
4381+/*!
4382+ * \qmltype SplitViewLayout
4383+ * \inmodule Ubuntu.Components.Labs
4384+ * \ingroup ubuntu-labs
4385+ * \brief Layout configuration for SplitView.
4386+ *
4387+ * SplitViewLayout defines a layout configuration and the condition when the layout is
4388+ * expected to be applied. In case multiple layout conditions evaluate to true, the first
4389+ * one in the list will be activated.
4390+ */
4391+SplitViewLayout::SplitViewLayout(QObject *parent)
4392+ : QObject(*(new SplitViewLayoutPrivate), parent)
4393+{
4394+}
4395+
4396+void SplitViewLayoutPrivate::columns_Append(QQmlListProperty<ViewColumn> *list, ViewColumn* data)
4397+{
4398+ SplitViewLayout *layout = static_cast<SplitViewLayout*>(list->object);
4399+ SplitViewLayoutPrivate *d = SplitViewLayoutPrivate::get(layout);
4400+ ViewColumnPrivate::get(data)->column = d->columnData.size();
4401+ // make sure ViewColumn is parented to the layout definition
4402+ data->setParent(layout);
4403+ d->columnData.append(data);
4404+ Q_EMIT layout->columnsChanged();
4405+}
4406+int SplitViewLayoutPrivate::columns_Count(QQmlListProperty<ViewColumn> *list)
4407+{
4408+ SplitViewLayout *layout = static_cast<SplitViewLayout*>(list->object);
4409+ SplitViewLayoutPrivate *d = SplitViewLayoutPrivate::get(layout);
4410+ return d->columnData.size();
4411+}
4412+ViewColumn *SplitViewLayoutPrivate::columns_At(QQmlListProperty<ViewColumn> *list, int index)
4413+{
4414+ SplitViewLayout *layout = static_cast<SplitViewLayout*>(list->object);
4415+ SplitViewLayoutPrivate *d = SplitViewLayoutPrivate::get(layout);
4416+ return d->columnData.at(index);
4417+}
4418+void SplitViewLayoutPrivate::columns_Clear(QQmlListProperty<ViewColumn> *list)
4419+{
4420+ SplitViewLayout *layout = static_cast<SplitViewLayout*>(list->object);
4421+ SplitViewLayoutPrivate *d = SplitViewLayoutPrivate::get(layout);
4422+ qDeleteAll(d->columnData);
4423+ d->columnData.clear();
4424+ Q_EMIT layout->columnsChanged();
4425+}
4426+
4427+/*!
4428+ * \qmlproperty list<ViewColumn> SplitViewLayout::columns
4429+ * \default
4430+ * The property holds the column configurations for the layout. If the view has more
4431+ * columns than the configuration specifies, the extra columns will be hidden (their
4432+ * visible property will be set to false!). Also, the hidden column sizes may change,
4433+ * therefore size-sensitive content must be aware of this.
4434+ */
4435+QQmlListProperty<UT_PREPEND_NAMESPACE(ViewColumn)> SplitViewLayoutPrivate::columns()
4436+{
4437+ Q_Q(SplitViewLayout);
4438+ return QQmlListProperty<UT_PREPEND_NAMESPACE(ViewColumn)>(q, &columnData,
4439+ &columns_Append,
4440+ &columns_Count,
4441+ &columns_At,
4442+ &columns_Clear);
4443+}
4444+
4445+/*!
4446+ * \qmlproperty bool SplitViewLayout::when
4447+ * Specifies the condition when to apply the layout. Usually holds a binding which
4448+ * evaluates to true or false to activate the layout.
4449+ */
4450+
4451+UT_NAMESPACE_END
4452
4453=== modified file 'src/Ubuntu/UbuntuToolkit/ubuntutoolkitmodule.cpp'
4454--- src/Ubuntu/UbuntuToolkit/ubuntutoolkitmodule.cpp 2016-08-06 00:55:32 +0000
4455+++ src/Ubuntu/UbuntuToolkit/ubuntutoolkitmodule.cpp 2016-09-06 13:08:47 +0000
4456@@ -89,6 +89,13 @@
4457 #include <privates/ucpagewrapper_p.h>
4458 #include <privates/appheaderbase_p.h>
4459 #include <privates/ucscrollbarutils_p.h>
4460+#include <actionlist_p.h>
4461+#include <exclusivegroup_p.h>
4462+#include <menu_p.h>
4463+#include <menubar_p.h>
4464+#include <menugroup_p.h>
4465+
4466+#include <splitview_p.h>
4467
4468 // styles
4469 #include <ucbottomedgestyle_p.h>
4470@@ -374,6 +381,8 @@
4471 qmlRegisterType<UCPageTreeNode>(uri, 1, 3, "PageTreeNode");
4472 qmlRegisterType<UCPopupContext>(uri, 1, 3, "PopupContext");
4473 qmlRegisterType<UCMainViewBase>(uri, 1, 3, "MainViewBase");
4474+ qmlRegisterType<ActionList>(uri, 1, 3, "ActionList");
4475+ qmlRegisterType<ExclusiveGroup>(uri, 1, 3, "ExclusiveGroup");
4476 }
4477
4478 void UbuntuToolkitModule::undefineModule()
4479@@ -404,15 +413,20 @@
4480 */
4481 void UbuntuLabsModule::initializeModule(QQmlEngine *engine, QQmlExtensionPlugin *plugin)
4482 {
4483- Q_UNUSED(engine);
4484 Q_UNUSED(plugin);
4485+
4486+ // qmlplugindump requires that quickutils is initialized.
4487+ QuickUtils::instance(engine);
4488 }
4489
4490 void UbuntuLabsModule::defineModule(const char *uri)
4491 {
4492- Q_UNUSED(uri);
4493- // a fake component so we can have the module types file created
4494- qmlRegisterType<QObject>(uri, 1, 0, "ZiObject");
4495+ qmlRegisterType<SplitView>(uri, 1, 0, "SplitView");
4496+ qmlRegisterType<SplitViewLayout>(uri, 1, 0, "SplitViewLayout");
4497+ qmlRegisterType<ViewColumn>(uri, 1, 0, "ViewColumn");
4498+ qmlRegisterType<Menu>(uri, 1, 0, "Menu");
4499+ qmlRegisterType<MenuBar>(uri, 1, 0, "MenuBar");
4500+ qmlRegisterType<MenuGroup>(uri, 1, 0, "MenuGroup");
4501 }
4502
4503 void UbuntuLabsModule::undefineModule()
4504
4505=== modified file 'src/Ubuntu/UbuntuToolkit/ucaction.cpp'
4506--- src/Ubuntu/UbuntuToolkit/ucaction.cpp 2016-07-07 07:21:48 +0000
4507+++ src/Ubuntu/UbuntuToolkit/ucaction.cpp 2016-09-06 13:08:47 +0000
4508@@ -17,6 +17,7 @@
4509 #include "ucaction_p.h"
4510 #include "quickutils_p.h"
4511 #include "ucactioncontext_p.h"
4512+#include "exclusivegroup_p.h"
4513
4514 #include <QtDebug>
4515 #include <QtQml/QQmlInfo>
4516@@ -149,6 +150,18 @@
4517 * text: "&Call"
4518 * }
4519 * \endqml
4520+ *
4521+ * \section2 Checkable property
4522+ * Since Ubuntu.Components 1.3 Action supports the checkable/checked properties.
4523+ * \qml
4524+ * Button {
4525+ * action: Action {
4526+ * checkable: true
4527+ * checked: false
4528+ * }
4529+ * color: action.checked ? UbuntuColor.green : UbuntuColor.red
4530+ * }
4531+ * \endqml
4532 */
4533
4534 /*!
4535@@ -160,6 +173,14 @@
4536 */
4537
4538 /*!
4539+ * \qmlsignal Action::toggled(bool value)
4540+ * Signal called when the action's checked property changes.
4541+ * \note The toggled signal should be used for checkable actions rather than the
4542+ * triggered signal.
4543+ * \sa Action::checkable, Action::checked, ExclusiveGroup
4544+ */
4545+
4546+/*!
4547 * \qmlproperty string Action::description
4548 * User visible secondary description for the action. Description is more verbose
4549 * than the \l text and should describe the Action with couple of words.
4550@@ -250,51 +271,17 @@
4551 * \endqml
4552 */
4553
4554-/*!
4555- * \qmlproperty enum Action::parameterType
4556- * Type of the parameter passed to \l trigger and \l triggered.
4557- * Type is an enumeration:
4558- * \list
4559- * \li \b Action.None: No paramater. (default)
4560- * \li \b Action.String: String parameter.
4561- * \li \b Action.Integer: Integer parameter.
4562- * \li \b Action.Bool: Boolean parameter.
4563- * \li \b Action.Real: Single precision floating point parameter.
4564- * \li \b Action.Object: The parameter is an object.
4565- * \endlist
4566- * \qml
4567- * Action {
4568- * id: action
4569- * parameterType: Action.String
4570- * onTriggered: {
4571- * // value arguments now contain strings
4572- * console.log(value);
4573- * }
4574- * Component.onCompleted: action.trigger("Hello World")
4575- * }
4576- * \endqml
4577- */
4578-
4579-/*!
4580- * \qmlproperty bool Action::enabled
4581- * If set to false the action can not be triggered. Components visualizing the
4582- * action migth either hide the action or make it insensitive. However visibility
4583- * can be controlled separately using the \l visible property.
4584- */
4585-
4586-/*!
4587- * \qmlproperty bool Action::visible
4588- * Specifies whether the action is visible to the user. Defaults to true.
4589- */
4590-
4591 UCAction::UCAction(QObject *parent)
4592 : QObject(parent)
4593+ , m_exclusiveGroup(Q_NULLPTR)
4594 , m_itemHint(Q_NULLPTR)
4595 , m_parameterType(None)
4596 , m_factoryIconSource(true)
4597 , m_enabled(true)
4598 , m_visible(true)
4599 , m_published(false)
4600+ , m_checkable(false)
4601+ , m_checked(false)
4602 {
4603 generateName();
4604 // FIXME: we need QInputDeviceInfo to detect the keyboard attechment
4605@@ -436,6 +423,163 @@
4606 Q_EMIT shortcutChanged();
4607 }
4608
4609+/*!
4610+ * \qmlproperty bool Action::visible
4611+ * Specifies whether the action is visible to the user. Defaults to true.
4612+ */
4613+void UCAction::setVisible(bool visible)
4614+{
4615+ if (m_visible == visible) {
4616+ return;
4617+ }
4618+ m_visible = visible;
4619+ Q_EMIT visibleChanged();
4620+}
4621+
4622+/*!
4623+ * \qmlproperty bool Action::enabled
4624+ * If set to false the action can not be triggered. Components visualizing the
4625+ * action migth either hide the action or make it insensitive. However visibility
4626+ * can be controlled separately using the \l visible property.
4627+ */
4628+void UCAction::setEnabled(bool enabled)
4629+{
4630+ if (m_enabled == enabled) {
4631+ return;
4632+ }
4633+ m_enabled = enabled;
4634+ Q_EMIT enabledChanged();
4635+
4636+}
4637+
4638+/*!
4639+ * \qmlproperty enum Action::parameterType
4640+ * Type of the parameter passed to \l trigger and \l triggered.
4641+ * Type is an enumeration:
4642+ * \list
4643+ * \li \b Action.None: No paramater. (default)
4644+ * \li \b Action.String: String parameter.
4645+ * \li \b Action.Integer: Integer parameter.
4646+ * \li \b Action.Bool: Boolean parameter.
4647+ * \li \b Action.Real: Single precision floating point parameter.
4648+ * \li \b Action.Object: The parameter is an object.
4649+ * \endlist
4650+ * \qml
4651+ * Action {
4652+ * id: action
4653+ * parameterType: Action.String
4654+ * onTriggered: {
4655+ * // value arguments now contain strings
4656+ * console.log(value);
4657+ * }
4658+ * Component.onCompleted: action.trigger("Hello World")
4659+ * }
4660+ * \endqml
4661+ */
4662+void UCAction::setParameterType(UCAction::Type type)
4663+{
4664+ if (m_parameterType == type) {
4665+ return;
4666+ }
4667+ m_parameterType = type;
4668+ Q_EMIT parameterTypeChanged();
4669+}
4670+
4671+/*!
4672+ * \qmlproperty bool Action::checkable
4673+ * \since Ubuntu.Components 1.3
4674+ * Whether the action can be checked. Defaults to false.
4675+ * \sa Action::checked, Action::toggled, ExclusiveGroup
4676+ */
4677+void UCAction::setCheckable(bool checkable)
4678+{
4679+ if (m_checkable == checkable) {
4680+ return;
4681+ }
4682+ m_checkable = checkable;
4683+ Q_EMIT checkableChanged();
4684+
4685+ // If the Action is already checked, assert the check state.
4686+ if (m_checked)
4687+ Q_EMIT toggled(m_checkable);
4688+}
4689+
4690+/*!
4691+ * \qmlproperty bool Action::checked
4692+ * \since Ubuntu.Components 1.3
4693+ * If the action is checkable, this property reflects its checked state. Defaults to false.
4694+ * Its value is also false while checkable is false.
4695+ * \sa Action::checkable, Action::toggled, ExclusiveGroup
4696+ */
4697+void UCAction::setChecked(bool checked)
4698+{
4699+ if (m_checked == checked) {
4700+ return;
4701+ }
4702+ m_checked = checked;
4703+
4704+ if (m_checkable) {
4705+ Q_EMIT toggled(checked);
4706+ }
4707+}
4708+
4709+/*!
4710+ * \qmlproperty ExclusiveGroup Action::exclusiveGroup
4711+ * \since Ubuntu.Components 1.3
4712+ * The \l ExclusiveGroup associated with this action.
4713+ * An exclusive group allows the \l checked property to belinked to other actions,
4714+ * as in radio controls.
4715+ * \qml
4716+ * Column {
4717+ * ExclusiveGroup {
4718+ * Action {
4719+ * id: action1
4720+ * checkable: true
4721+ * checked: true
4722+ * }
4723+ * Action {
4724+ * id: action2
4725+ * checkable: true
4726+ * }
4727+ * Action {
4728+ * id: action3
4729+ * checkable: true
4730+ * }
4731+ * }
4732+ *
4733+ * Button {
4734+ * action: action1
4735+ * color: action.checked ? UbuntuColor.green : UbuntuColor.red
4736+ * }
4737+ * Button {
4738+ * action: action2
4739+ * color: action.checked ? UbuntuColor.green : UbuntuColor.red
4740+ * }
4741+ * Button {
4742+ * action: action3
4743+ * color: action.checked ? UbuntuColor.green : UbuntuColor.grey
4744+ * }
4745+ * }
4746+ * \endqml
4747+ */
4748+void UCAction::setExclusiveGroup(ExclusiveGroup *exclusiveGroup)
4749+{
4750+ if (m_exclusiveGroup == exclusiveGroup) {
4751+ return;
4752+ }
4753+
4754+ if (m_exclusiveGroup) {
4755+ m_exclusiveGroup->unbindCheckable(this);
4756+ }
4757+
4758+ m_exclusiveGroup = exclusiveGroup;
4759+
4760+ if (m_exclusiveGroup) {
4761+ m_exclusiveGroup->bindCheckable(this);
4762+ }
4763+ Q_EMIT exclusiveGroupChanged();
4764+}
4765+
4766 bool UCAction::event(QEvent *event)
4767 {
4768 if (event->type() != QEvent::Shortcut)
4769@@ -475,6 +619,11 @@
4770 if (!m_enabled) {
4771 return;
4772 }
4773+
4774+ if (m_checkable && !(m_checked && m_exclusiveGroup)) {
4775+ setChecked(!m_checked);
4776+ }
4777+
4778 if (!isValidType(value.type())) {
4779 Q_EMIT triggered(QVariant());
4780 } else {
4781
4782=== modified file 'src/Ubuntu/UbuntuToolkit/ucaction_p.h'
4783--- src/Ubuntu/UbuntuToolkit/ucaction_p.h 2016-07-07 07:21:48 +0000
4784+++ src/Ubuntu/UbuntuToolkit/ucaction_p.h 2016-09-06 13:08:47 +0000
4785@@ -53,7 +53,7 @@
4786 }
4787 }
4788
4789-class UCActionAttached;
4790+class ExclusiveGroup;
4791 class UBUNTUTOOLKIT_EXPORT UCAction : public QObject
4792 {
4793 Q_OBJECT
4794@@ -62,19 +62,23 @@
4795 Q_ENUMS(Type)
4796 Q_PROPERTY(QString name MEMBER m_name WRITE setName NOTIFY nameChanged)
4797 Q_PROPERTY(QString text READ text WRITE setText RESET resetText NOTIFY textChanged)
4798- Q_PROPERTY(QString iconName MEMBER m_iconName WRITE setIconName NOTIFY iconNameChanged)
4799+ Q_PROPERTY(QString iconName READ iconName WRITE setIconName NOTIFY iconNameChanged)
4800 Q_PROPERTY(QString description MEMBER m_description NOTIFY descriptionChanged)
4801 Q_PROPERTY(QString keywords MEMBER m_keywords NOTIFY keywordsChanged)
4802- Q_PROPERTY(bool enabled MEMBER m_enabled NOTIFY enabledChanged)
4803- Q_PROPERTY(Type parameterType MEMBER m_parameterType NOTIFY parameterTypeChanged)
4804+ Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged)
4805+ Q_PROPERTY(Type parameterType READ parameterType WRITE setParameterType NOTIFY parameterTypeChanged)
4806+
4807+ Q_PROPERTY(bool checkable READ isCheckable WRITE setCheckable NOTIFY checkableChanged REVISION 1)
4808+ Q_PROPERTY(bool checked READ isChecked WRITE setChecked NOTIFY toggled REVISION 1)
4809+ Q_PROPERTY(ExclusiveGroup* exclusiveGroup READ exclusiveGroup WRITE setExclusiveGroup NOTIFY exclusiveGroupChanged REVISION 1)
4810
4811 // Toolkit Actions API
4812- Q_PROPERTY(QUrl iconSource MEMBER m_iconSource WRITE setIconSource NOTIFY iconSourceChanged)
4813- Q_PROPERTY(bool visible MEMBER m_visible NOTIFY visibleChanged)
4814+ Q_PROPERTY(QUrl iconSource READ iconSource WRITE setIconSource NOTIFY iconSourceChanged)
4815+ Q_PROPERTY(bool visible READ visible WRITE setVisible NOTIFY visibleChanged)
4816 Q_PROPERTY(QQmlComponent *itemHint MEMBER m_itemHint WRITE setItemHint)
4817
4818 // QtQuickControls.Action
4819- Q_PROPERTY(QVariant shortcut MEMBER m_shortcut WRITE setShortcut RESET resetShortcut NOTIFY shortcutChanged REVISION 1)
4820+ Q_PROPERTY(QVariant shortcut READ shortcut WRITE setShortcut RESET resetShortcut NOTIFY shortcutChanged REVISION 1)
4821 public:
4822 enum Type {
4823 None,
4824@@ -108,11 +112,26 @@
4825 QString text();
4826 void setText(const QString &text);
4827 void resetText();
4828+ QString iconName() const { return m_iconName; }
4829 void setIconName(const QString &name);
4830+ QUrl iconSource() const { return m_iconSource; }
4831 void setIconSource(const QUrl &url);
4832 void setItemHint(QQmlComponent *);
4833+ QVariant shortcut() const { return m_shortcut; }
4834 void setShortcut(const QVariant&);
4835 void resetShortcut();
4836+ bool visible() const { return m_visible; }
4837+ void setVisible(bool visible);
4838+ void setEnabled(bool enabled);
4839+ void setParameterType(Type type);
4840+ Type parameterType() const { return m_parameterType; }
4841+ void setCheckable(bool checkable);
4842+ bool isCheckable() const { return m_checkable; }
4843+ void setChecked(bool checked);
4844+ bool isChecked() const { return m_checkable && m_checked; }
4845+
4846+ ExclusiveGroup *exclusiveGroup() const { return m_exclusiveGroup; }
4847+ void setExclusiveGroup(ExclusiveGroup *exclusiveGroup);
4848
4849 Q_SIGNALS:
4850 void nameChanged();
4851@@ -125,13 +144,18 @@
4852 void iconSourceChanged();
4853 void visibleChanged();
4854 void shortcutChanged();
4855+ Q_REVISION(1) void checkableChanged();
4856+ Q_REVISION(1) void exclusiveGroupChanged();
4857+
4858 void triggered(const QVariant &value);
4859+ Q_REVISION(1) void toggled(bool value);
4860
4861 public Q_SLOTS:
4862 void trigger(const QVariant &value = QVariant());
4863
4864 private:
4865 QPODVector<QQuickItem*, 4> m_owningItems;
4866+ ExclusiveGroup *m_exclusiveGroup;
4867 QString m_name;
4868 QString m_text;
4869 QString m_iconName;
4870@@ -146,6 +170,8 @@
4871 bool m_enabled:1;
4872 bool m_visible:1;
4873 bool m_published:1;
4874+ bool m_checkable:1;
4875+ bool m_checked:1;
4876
4877 friend class UCActionContext;
4878 friend class UCActionItem;
4879
4880=== modified file 'src/Ubuntu/UbuntuToolkit/uclistitem.cpp'
4881--- src/Ubuntu/UbuntuToolkit/uclistitem.cpp 2016-07-14 07:41:06 +0000
4882+++ src/Ubuntu/UbuntuToolkit/uclistitem.cpp 2016-09-06 13:08:47 +0000
4883@@ -537,7 +537,7 @@
4884 * there will be no highlight happening if the click happens on the active component.
4885 * \qml
4886 * import QtQuick 2.4
4887- * import Ubuntu.Components 1.2
4888+ * import Ubuntu.Components 1.3
4889 *
4890 * MainView {
4891 * width: units.gu(40)
4892@@ -572,20 +572,29 @@
4893 * onClicked: console.log("clicked on ListItem with trailingActions set")
4894 * }
4895 * ListItem {
4896- * Label {
4897- * text: "onClicked implemented"
4898+ * // shall specify the height when Using ListItemLayout inside ListItem
4899+ * height: clickImplementedLayout.height + (divider.visible ? divider.height : 0)
4900+ * ListItemLayout {
4901+ * id: clickImplementedLayout
4902+ * title.text: "onClicked implemented"
4903 * }
4904 * onClicked: console.log("clicked on ListItem with onClicked implemented")
4905 * }
4906 * ListItem {
4907- * Label {
4908- * text: "onPressAndHold implemented"
4909+ * // shall specify the height when Using ListItemLayout inside ListItem
4910+ * height: pressHoldLayout.height + (divider.visible ? divider.height : 0)
4911+ * ListItemLayout {
4912+ * id: pressHoldLayout
4913+ * title.text: "onPressAndHold implemented"
4914 * }
4915 * onPressAndHold: console.log("long-pressed on ListItem with onPressAndHold implemented")
4916 * }
4917 * ListItem {
4918- * Label {
4919- * text: "No highlight"
4920+ * // shall specify the height when Using ListItemLayout inside ListItem
4921+ * height: noHighlightLayout.height + (divider.visible ? divider.height : 0)
4922+ * ListItemLayout {
4923+ * id: noHighlightLayout
4924+ * title.text: "No highlight"
4925 * }
4926 * }
4927 * }
4928@@ -671,7 +680,7 @@
4929 * to the view itself.
4930 * \qml
4931 * import QtQuick 2.4
4932- * import Ubuntu.Components 1.2
4933+ * import Ubuntu.Components 1.3
4934 *
4935 * Flickable {
4936 * width: units.gu(40)
4937@@ -735,7 +744,7 @@
4938 * Example of live drag implementation:
4939 * \qml
4940 * import QtQuick 2.4
4941- * import Ubuntu.Components 1.2
4942+ * import Ubuntu.Components 1.3
4943 *
4944 * ListView {
4945 * model: ListModel {
4946@@ -746,8 +755,11 @@
4947 * }
4948 * }
4949 * delegate: ListItem {
4950- * Label {
4951- * text: modelData
4952+ * // shall specify the height when Using ListItemLayout inside ListItem
4953+ * height: modelLayout.height + (divider.visible ? divider.height : 0)
4954+ * ListItemLayout {
4955+ * id: modelLayout
4956+ * title.text: modelData
4957 * }
4958 * color: dragMode ? "lightblue" : "lightgray"
4959 * onPressAndHold: ListView.view.ViewItems.dragMode =
4960@@ -769,7 +781,7 @@
4961 * Example of drag'n'drop implementation:
4962 * \qml
4963 * import QtQuick 2.4
4964- * import Ubuntu.Components 1.2
4965+ * import Ubuntu.Components 1.3
4966 *
4967 * ListView {
4968 * model: ListModel {
4969@@ -780,8 +792,11 @@
4970 * }
4971 * }
4972 * delegate: ListItem {
4973- * Label {
4974- * text: modelData
4975+ * // shall specify the height when Using ListItemLayout inside ListItem
4976+ * height: modelLayout.height + (divider.visible ? divider.height : 0)
4977+ * ListItemLayout {
4978+ * id: modelLayout
4979+ * title.text: modelData
4980 * }
4981 * color: dragMode ? "lightblue" : "lightgray"
4982 * onPressAndHold: ListView.view.ViewItems.dragMode =
4983@@ -815,13 +830,16 @@
4984 * can still be implemented with these kind of lists as well.
4985 * \qml
4986 * import QtQuick 2.4
4987- * import Ubuntu.Components 1.2
4988+ * import Ubuntu.Components 1.3
4989 *
4990 * ListView {
4991 * model: ["plum", "peach", "pomegrenade", "pear", "banana"]
4992 * delegate: ListItem {
4993- * Label {
4994- * text: modelData
4995+ * // shall specify the height when Using ListItemLayout inside ListItem
4996+ * height: modelLayout.height + (divider.visible ? divider.height : 0)
4997+ * ListItemLayout {
4998+ * id: modelLayout
4999+ * title.text: modelData
5000 * }
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to status/vote changes: