Merge lp:~bzoltan/ubuntu-ui-toolkit/more_1618581 into lp:ubuntu-ui-toolkit

Proposed by Zoltan Balogh
Status: Superseded
Proposed branch: lp:~bzoltan/ubuntu-ui-toolkit/more_1618581
Merge into: lp:ubuntu-ui-toolkit
Diff against target: 7457 lines (+5769/-506)
68 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 (+14/-14)
tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_header.py (+2/-1)
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:~bzoltan/ubuntu-ui-toolkit/more_1618581
Reviewer Review Type Date Requested Status
Ubuntu SDK team Pending
Review via email: mp+304454@code.launchpad.net

This proposal has been superseded by a proposal from 2016-08-31.

Commit message

Fix the header tests. Fixes LP: #1618581

Description of the change

Fix the header tests. Fixes LP: #1618581

To post a comment you must log in.

Unmerged revisions

2089. By Zoltan Balogh

Fix the header tests. Fixes LP: #Bug #1618581

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

Subscribers

People subscribed via source and target branches

to status/vote changes: