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

Proposed by Zsombor Egri on 2016-07-11
Status: Merged
Approved by: Zsombor Egri on 2016-08-26
Approved revision: 2057
Merged at revision: 2081
Proposed branch: lp:~zsombi/ubuntu-ui-toolkit/columnLayout
Merge into: lp:ubuntu-ui-toolkit/staging
Diff against target: 2250 lines (+2108/-7)
16 files modified
debian/control (+1/-0)
debian/control.gles (+1/-0)
documentation/overview.qdoc (+16/-0)
src/Ubuntu/Test/UbuntuTestCase13.qml (+1/-1)
src/Ubuntu/UbuntuToolkit/UbuntuToolkit.pro (+8/-2)
src/Ubuntu/UbuntuToolkit/privates/splitviewhandler.cpp (+137/-0)
src/Ubuntu/UbuntuToolkit/privates/splitviewhandler_p_p.h (+52/-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 (+5/-0)
src/Ubuntu/UbuntuToolkit/ucmathutils_p.h (+4/-4)
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:~zsombi/ubuntu-ui-toolkit/columnLayout
Reviewer Review Type Date Requested Status
ubuntu-sdk-build-bot continuous-integration Approve on 2016-08-26
Christian Dywan 2016-07-11 Approve on 2016-08-18
Review via email: mp+299679@code.launchpad.net

Commit Message

SplitView to Ubuntu.Components.Labs

Description of the Change

TODO:
- make Page to work with SplitView.

To post a comment you must log in.
Christian Dywan (kalikiana) wrote :

> + * In order a SplitView to show some content it must have at least one active layout
In order for a SplitView to show some content it must have at least one active layout

> + * shown differs on the space available), make sure the content of your view does take
shown differs depending on the available space), make sure the content of your view takes

+ * Page {
367 + * id: column1
368 + * height: parent.height
369 + * }

The height: requirement is not being explained and it's different to APL. I would expect the Page to fill the column by default. Is height: needed in that case? What if I do anchors.fill?

> + * can be used with a Repeater in case the content of the view columns
> + * doesn't need to be preserved between layout changes.

That's asking for trouble. It's basically saying "This will probably not do what you want, but try this anyway". We need to have an example that is safe to copy and doesn't lead to surprises like losing all your data when the columns change.

+ * Spacing between the view columns, also drives the ability to resize view columns.
> + * If the value is zero, the view cannot resize columns. If the value is > 0, the
> + * view can resize those columns which have their \l{ViewColumn::minimumWidth}{minimumWidth}
> + * is less than the \l {ViewColumn::maximumWidth}{maximumWidth}.

Spacing between view columns.

A value > 0 enables resizing of columns with a \l {ViewColumn::minimumWidth}{minimumWidth} lower than \l {ViewColumn::maximumWidth}{maximumWidth}.
If spacing is 0 the columns can't be resized.

> 1256 + QList<SplitViewLayout*> columnLatouts;
columnLayouts

> + // Q: should we reset the sizes of the previous layout?
> + // at least it feels right to preserve the last state of the layout...

We need to define how sizes are restored. Since the user of SplitView is not supposed to set width: it's probably up to the component. But that would mean the component also has to save and restore, as the app isn't expected to touch that. We don't know where and how a particular app saves its state so we can't really do that.

> +static qreal clamp(qreal min, qreal max, qreal v)

Why not use clamp() from our MathUtils? It's identical except for using the real type versus Qt's alias.

review: Needs Fixing
Zsombor Egri (zsombi) wrote :
Download full text (3.5 KiB)

> > + * In order a SplitView to show some content it must have at least one
> active layout
> In order for a SplitView to show some content it must have at least one active
> layout
>
> > + * shown differs on the space available), make sure the content of your
> view does take
> shown differs depending on the available space), make sure the content of your
> view takes
>
> + * Page {
> 367 + * id: column1
> 368 + * height: parent.height
> 369 + * }
>
> The height: requirement is not being explained and it's different to APL. I
> would expect the Page to fill the column by default. Is height: needed in that
> case? What if I do anchors.fill?

Actually Page causes trouble, because of its anchoring... I HATE THAT!... I need to figure out something so it works with Page.

The height is needed in a sense that - if you want - you can create a component which would vertically be centered, or on the top/bottom of the column. Not just a Page, but a simple Item. I would not set the height by default, I would avoid any automatic height setting so we don't repeat the Page problem... But definitely anchors.fill should also be avoided! So I will document this! Good catch!

>
> > + * can be used with a Repeater in case the content of the view columns
> > + * doesn't need to be preserved between layout changes.
>
> That's asking for trouble. It's basically saying "This will probably not do
> what you want, but try this anyway". We need to have an example that is safe
> to copy and doesn't lead to surprises like losing all your data when the
> columns change.

If you have a Repeater, that's what you get. Perhaps we don't mention that at all then... Anyways, I have a test for that so in case someone asks us, we can point them to that. And I'll have a test with Pages!!!

>
> + * Spacing between the view columns, also drives the ability to resize view
> columns.
> > + * If the value is zero, the view cannot resize columns. If the value is >
> 0, the
> > + * view can resize those columns which have their
> \l{ViewColumn::minimumWidth}{minimumWidth}
> > + * is less than the \l {ViewColumn::maximumWidth}{maximumWidth}.
>
>
> Spacing between view columns.
>
> A value > 0 enables resizing of columns with a \l
> {ViewColumn::minimumWidth}{minimumWidth} lower than \l
> {ViewColumn::maximumWidth}{maximumWidth}.
> If spacing is 0 the columns can't be resized.

Thanks :)

>
> > 1256 + QList<SplitViewLayout*> columnLatouts;
> columnLayouts
>
> > + // Q: should we reset the sizes of the previous layout?
> > + // at least it feels right to preserve the last state of the layout...
>
> We need to define how sizes are restored. Since the user of SplitView is not
> supposed to set width: it's probably up to the component. But that would mean
> the component also has to save and restore, as the app isn't expected to touch
> that. We don't know where and how a particular app saves its state so we can't
> really do that.

Actually when I wrote this Q I (almost) immediately answered it myself. So, now any manual (with mouse or touch) resizing of the columns will be preserved in the ViewColumns, and restores when the layout i...

Read more...

Christian Dywan (kalikiana) wrote :

Please make tst_splitview{_page}.13.qml manually testable, it makes review easier as well as verifying if/why anything breaks in the future: my suggestion would be to have testLoader.sourceComponent be set depending on a selected section cf. tests/unit/visual/tst_listitem_focus.13.qml. Maybe merge all of the 3 tests? I'm not sure if there's a reason they should be separate.

Also testHandle appears to be untested - it exists but it's never being used.

> "handle delegate not an Item"

Minor nitpick: could we avoid repeating this half a dozen times, like we do for eg. deprecation warnings?

review: Needs Fixing
Christian Dywan (kalikiana) wrote :

In file included from privates/splitviewhandler.cpp:19:0:
privates/splitviewhandler_p_p.h:39:25: error: field 'view' has incomplete type 'QPointer<SplitView>'
     QPointer<SplitView> view;
                         ^
privates/splitviewhandler.cpp: In member function 'void SplitViewHandler::onDelegateChanged()':
privates/splitviewhandler.cpp:121:33: error: 'qmlInfo' was not declared in this scope
                     qmlInfo(view) << "handle delegate not an Item";
                                 ^

Curiously this only fails to compile with gles.

Christian Dywan (kalikiana) wrote :

Thanks for making the tests manually testable and incorporating the handle.

review: Approve
2056. By Zsombor Egri on 2016-08-25

staging sync

2057. By Zsombor Egri on 2016-08-26

fix build issues on gles

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2016-08-18 16:51:24 +0000
3+++ debian/control 2016-08-26 05:41:49 +0000
4@@ -77,6 +77,7 @@
5 qml-module-qtquick-layouts,
6 qml-module-qtquick-window2,
7 qml-module-qtquick2,
8+ qml-module-ubuntu-components-labs,
9 qml-module-ubuntu-performancemetrics,
10 qtdeclarative5-unity-action-plugin (>= 1.1.0),
11 suru-icon-theme,
12
13=== modified file 'debian/control.gles'
14--- debian/control.gles 2016-07-28 10:54:13 +0000
15+++ debian/control.gles 2016-08-26 05:41:49 +0000
16@@ -103,6 +103,7 @@
17 qml-module-qtquick-layouts,
18 qml-module-qtquick-window2,
19 qml-module-qtquick2,
20+ qml-module-ubuntu-components-labs,
21 qml-module-ubuntu-performancemetrics-gles,
22 qtdeclarative5-unity-action-plugin (>= 1.1.0),
23 suru-icon-theme,
24
25=== modified file 'documentation/overview.qdoc'
26--- documentation/overview.qdoc 2016-07-27 15:40:52 +0000
27+++ documentation/overview.qdoc 2016-08-26 05:41:49 +0000
28@@ -129,6 +129,22 @@
29 \endcode
30 \annotatedlist ubuntu-services
31
32+ \part Performance Metrics
33+ Available through:
34+ \code
35+ import Ubuntu.PerformanceMetrics 1.0
36+ \endcode
37+ \annotatedlist ubuntu-performance-metrics
38+
39+ \part Labs
40+ The Labs module contains a set of components which have unstable API. Those
41+ should not be used in applications as their interface may change any time.
42+ Available through:
43+ \code
44+ import Ubuntu.Components.Labs 1.0
45+ \endcode
46+ \annotatedlist ubuntu-labs
47+
48 \part Test extensions
49 Available through:
50 \code
51
52=== modified file 'src/Ubuntu/Test/UbuntuTestCase13.qml'
53--- src/Ubuntu/Test/UbuntuTestCase13.qml 2016-03-15 13:17:53 +0000
54+++ src/Ubuntu/Test/UbuntuTestCase13.qml 2016-08-26 05:41:49 +0000
55@@ -15,7 +15,7 @@
56 */
57
58 import QtQuick 2.4
59-import QtTest 1.0
60+import QtTest 1.1
61 import Ubuntu.Components 1.3
62
63 /*!
64
65=== modified file 'src/Ubuntu/UbuntuToolkit/UbuntuToolkit.pro'
66--- src/Ubuntu/UbuntuToolkit/UbuntuToolkit.pro 2016-08-25 13:13:39 +0000
67+++ src/Ubuntu/UbuntuToolkit/UbuntuToolkit.pro 2016-08-26 05:41:49 +0000
68@@ -44,7 +44,10 @@
69 mousetouchadaptor_p.h \
70 mousetouchadaptor_p_p.h \
71 propertychange_p.h \
72- ubuntutoolkitmodule.h
73+ ubuntutoolkitmodule.h \
74+ splitview_p.h \
75+ splitview_p_p.h \
76+ privates/splitviewhandler_p_p.h
77
78 SOURCES += \
79 colorutils.cpp \
80@@ -52,7 +55,10 @@
81 asyncloader.cpp \
82 mousetouchadaptor.cpp \
83 propertychange.cpp \
84- ubuntutoolkitmodule.cpp
85+ ubuntutoolkitmodule.cpp \
86+ splitview.cpp \
87+ privates/splitviewhandler.cpp \
88+ splitviewlayout.cpp
89
90 HEADERS += \
91 uctheme_p.h \
92
93=== added file 'src/Ubuntu/UbuntuToolkit/privates/splitviewhandler.cpp'
94--- src/Ubuntu/UbuntuToolkit/privates/splitviewhandler.cpp 1970-01-01 00:00:00 +0000
95+++ src/Ubuntu/UbuntuToolkit/privates/splitviewhandler.cpp 2016-08-26 05:41:49 +0000
96@@ -0,0 +1,137 @@
97+/*
98+ * Copyright 2016 Canonical Ltd.
99+ *
100+ * This program is free software; you can redistribute it and/or modify
101+ * it under the terms of the GNU Lesser General Public License as published by
102+ * the Free Software Foundation; version 3.
103+ *
104+ * This program is distributed in the hope that it will be useful,
105+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
106+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
107+ * GNU Lesser General Public License for more details.
108+ *
109+ * You should have received a copy of the GNU Lesser General Public License
110+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
111+ *
112+ * Author: Zsombor Egri <zsombor.egri@canonical.com>
113+ */
114+
115+#include "splitviewhandler_p_p.h"
116+#include <QtQuick/private/qquickanchors_p.h>
117+#include <QtQuick/private/qquickitem_p.h>
118+#include <QtQuick/private/qquickevents_p_p.h>
119+#include <QtQml/QQmlEngine>
120+#include <QtQml/QQmlInfo>
121+#include <splitview_p_p.h>
122+
123+UT_NAMESPACE_BEGIN
124+
125+SplitViewHandler::SplitViewHandler(QQuickItem *parent)
126+ : QQuickMouseArea(parent)
127+{
128+ // for testing purposes
129+ setObjectName("resize_handle");
130+ setFlag(ItemHasContents);
131+ setHoverEnabled(true);
132+ setAcceptedButtons(Qt::LeftButton);
133+
134+ setCursorShape(Qt::SplitHCursor);
135+ setParentItem(parent);
136+
137+ // attach the handler to the parent's rigth edge
138+ QQuickItemPrivate *dParent = QQuickItemPrivate::get(parentItem());
139+ QQuickAnchors *anchors = QQuickItemPrivate::get(this)->anchors();
140+ anchors->setTop(dParent->top());
141+ anchors->setBottom(dParent->bottom());
142+ anchors->setLeft(dParent->right());
143+
144+ // capture mouse events
145+ connect(this, SIGNAL(pressed(QQuickMouseEvent*)), this, SLOT(onPressed(QQuickMouseEvent*)));
146+ connect(this, &SplitViewHandler::released, this, &SplitViewHandler::onReleased);
147+ connect(this, &SplitViewHandler::positionChanged, this, &SplitViewHandler::onPositionChanged);
148+}
149+
150+SplitViewHandler::~SplitViewHandler()
151+{
152+ if (spacing) {
153+ disconnect(*spacing);
154+ }
155+ delete spacing;
156+}
157+
158+void SplitViewHandler::connectToView(SplitView *view)
159+{
160+ this->view = view;
161+ // grab the context of the parent
162+ QQmlEngine::setContextForObject(this, qmlContext(view));
163+
164+ // bind SplitView spacing, use it to specify the resize handle
165+ auto resizer = [view, this]() {
166+ setWidth(view->spacing());
167+ };
168+ spacing = new QMetaObject::Connection;
169+ *spacing = connect(view, &SplitView::spacingChanged, resizer);
170+ setWidth(view->spacing());
171+
172+ // connect to receive handle delegate
173+ connect(view, &SplitView::handleDelegateChanged,
174+ this, &SplitViewHandler::onDelegateChanged);
175+
176+ onDelegateChanged();
177+}
178+
179+void SplitViewHandler::onPressed(QQuickMouseEvent *event)
180+{
181+ prevPos = QPointF(event->x(), event->y());
182+}
183+
184+void SplitViewHandler::onReleased(QQuickMouseEvent *event)
185+{
186+ Q_UNUSED(event);
187+ prevPos = QPointF();
188+}
189+
190+void SplitViewHandler::onPositionChanged(QQuickMouseEvent *event)
191+{
192+ if (!pressed()) {
193+ return;
194+ }
195+ qreal dx = event->x() - prevPos.x();
196+ SplitViewAttached *attached = SplitViewAttached::get(parentItem());
197+ if (attached) {
198+ attached->resize(dx);
199+ }
200+}
201+
202+void SplitViewHandler::onDelegateChanged()
203+{
204+ static bool warningShown = false;
205+ // the child is an instance of the delegate
206+ QList<QQuickItem*> children = childItems();
207+ qDeleteAll(children);
208+
209+ // and set the new delegate - if any
210+ if (SplitViewPrivate::get(view)->handleDelegate) {
211+ QQmlContext *context = new QQmlContext(qmlContext(this), this);
212+ context->setContextProperty("handle", QVariant::fromValue(this));
213+ QObject *object = SplitViewPrivate::get(view)->handleDelegate->beginCreate(context);
214+ if (object) {
215+ QQuickItem *item = qobject_cast<QQuickItem*>(object);
216+ if (!item) {
217+ if (!warningShown) {
218+ qmlInfo(view) << "handle delegate not an Item";
219+ warningShown = true;
220+ }
221+ SplitViewPrivate::get(view)->handleDelegate->completeCreate();
222+ delete object;
223+ } else {
224+ warningShown = false;
225+ QQml_setParent_noEvent(item, this);
226+ item->setParentItem(this);
227+ SplitViewPrivate::get(view)->handleDelegate->completeCreate();
228+ }
229+ }
230+ }
231+}
232+
233+UT_NAMESPACE_END
234
235=== added file 'src/Ubuntu/UbuntuToolkit/privates/splitviewhandler_p_p.h'
236--- src/Ubuntu/UbuntuToolkit/privates/splitviewhandler_p_p.h 1970-01-01 00:00:00 +0000
237+++ src/Ubuntu/UbuntuToolkit/privates/splitviewhandler_p_p.h 2016-08-26 05:41:49 +0000
238@@ -0,0 +1,52 @@
239+/*
240+ * Copyright 2016 Canonical Ltd.
241+ *
242+ * This program is free software; you can redistribute it and/or modify
243+ * it under the terms of the GNU Lesser General Public License as published by
244+ * the Free Software Foundation; version 3.
245+ *
246+ * This program is distributed in the hope that it will be useful,
247+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
248+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
249+ * GNU Lesser General Public License for more details.
250+ *
251+ * You should have received a copy of the GNU Lesser General Public License
252+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
253+ *
254+ * Author: Zsombor Egri <zsombor.egri@canonical.com>
255+ */
256+
257+#ifndef SPLITVIEWHANDLER_H
258+#define SPLITVIEWHANDLER_H
259+
260+#include <QtQuick/QQuickItem>
261+#include <QtCore/QPointer>
262+#include <QtQuick/private/qquickmousearea_p.h>
263+#include <ubuntutoolkitglobal.h>
264+
265+UT_NAMESPACE_BEGIN
266+
267+class SplitView;
268+class SplitViewHandler : public QQuickMouseArea
269+{
270+ Q_OBJECT
271+public:
272+ explicit SplitViewHandler(QQuickItem *parent = 0);
273+ ~SplitViewHandler();
274+ void connectToView(SplitView *view);
275+
276+protected:
277+ QPointF prevPos;
278+ QPointer<SplitView> view;
279+ QMetaObject::Connection *spacing{nullptr};
280+
281+private Q_SLOTS:
282+ void onPressed(QQuickMouseEvent *event);
283+ void onReleased(QQuickMouseEvent *event);
284+ void onPositionChanged(QQuickMouseEvent *event);
285+ void onDelegateChanged();
286+};
287+
288+UT_NAMESPACE_END
289+
290+#endif // SPLITVIEWHANDLER_H
291
292=== added file 'src/Ubuntu/UbuntuToolkit/splitview.cpp'
293--- src/Ubuntu/UbuntuToolkit/splitview.cpp 1970-01-01 00:00:00 +0000
294+++ src/Ubuntu/UbuntuToolkit/splitview.cpp 2016-08-26 05:41:49 +0000
295@@ -0,0 +1,675 @@
296+/*
297+ * Copyright 2016 Canonical Ltd.
298+ *
299+ * This program is free software; you can redistribute it and/or modify
300+ * it under the terms of the GNU Lesser General Public License as published by
301+ * the Free Software Foundation; version 3.
302+ *
303+ * This program is distributed in the hope that it will be useful,
304+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
305+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
306+ * GNU Lesser General Public License for more details.
307+ *
308+ * You should have received a copy of the GNU Lesser General Public License
309+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
310+ *
311+ * Author: Zsombor Egri <zsombor.egri@canonical.com>
312+ */
313+
314+#include <QtQuick/private/qquickitem_p.h>
315+#include <QtQuick/private/qquickanchors_p.h>
316+#include <QtQml/QQmlInfo>
317+
318+#include "splitview_p.h"
319+#include "splitview_p_p.h"
320+#include "ucunits_p.h"
321+#include "ucpagetreenode_p.h"
322+
323+#include "privates/splitviewhandler_p_p.h"
324+
325+#define DEFAULT_SPACING_DP 4
326+
327+UT_NAMESPACE_BEGIN
328+
329+/*!
330+ * \qmltype SplitView
331+ * \inqmlmodule Ubuntu.Components.Labs 1.0
332+ * \inherits QQuickBasePositioner
333+ * \ingroup ubuntu-labs
334+ * \brief A view component with a flexible layout configuration setup.
335+ *
336+ * The component arranges the declared child elements horizontally based on an active
337+ * column configuration layout. Child elements are considered to be views, and each view
338+ * is identified with a column index, specified by the SplitView.column attached property.
339+ * Views should not have width declared, because the width of each view is specified
340+ * by the active layout's configuration (ViewColumn) and will overwrite the value
341+ * specified by the view. On the other hand they should have a height specified, or
342+ * they can be anchored to the top and bottom of the view. SplitView being a positioner,
343+ * remember not to anchor horizontal anchor lines or anchor fill the columns.
344+ *
345+ * In order for a SplitView to show some content it must have at least one active layout
346+ * present. Views which are not configured by the active layout will be hidden. Hidden
347+ * views may be resized, therefore if the content is size sensitive (i.e. the amount
348+ * shown differs depending on the space available), make sure the content of your view
349+ * does take this into account.
350+ * \code
351+ * import QtQuick 2.4
352+ * import Ubuntu.Components 1.3
353+ * import Ubuntu.Components.Labs 1.0
354+ *
355+ * MainView {
356+ * id: main
357+ * width: units.gu(300)
358+ * height: units.gu(80)
359+ * SplitView {
360+ * anchors.fill: parent
361+ * layouts: [
362+ * SplitViewLayout {
363+ * when: main.width < units.gu(80)
364+ * ViewColumn {
365+ * fillWidth: true
366+ * }
367+ * },
368+ * SplitViewLayout {
369+ * when: main.width >= units.gu(80)
370+ * ViewColumn {
371+ * minimumWidth: units.gu(30)
372+ * maximumWidth: units.gu(100)
373+ * preferredWidth: units.gu(40)
374+ * }
375+ * ViewColumn {
376+ * minimumWidth: units.gu(40)
377+ * fillWidth: true
378+ * }
379+ * }
380+ * ]
381+ * }
382+ *
383+ * Page {
384+ * id: column1
385+ * height: parent.height
386+ * }
387+ * Page {
388+ * id: column2
389+ * height: parent.height
390+ * }
391+ * }
392+ * \endcode
393+ *
394+ * The SplitView can be used with a Repeater in case the content of the view columns
395+ * doesn't need to be preserved between layout changes. The example above with a
396+ * Repeater would look as follows:
397+ * \code
398+ * import QtQuick 2.4
399+ * import Ubuntu.Components 1.3
400+ * import Ubuntu.Components.Labs 1.0
401+ *
402+ * MainView {
403+ * id: main
404+ * width: units.gu(300)
405+ * height: units.gu(80)
406+ * SplitView {
407+ * id: view
408+ * anchors.fill: parent
409+ * layouts: [
410+ * SplitViewLayout {
411+ * when: main.width < units.gu(80)
412+ * ViewColumn {
413+ * fillWidth: true
414+ * }
415+ * },
416+ * SplitViewLayout {
417+ * when: main.width >= units.gu(80)
418+ * ViewColumn {
419+ * minimumWidth: units.gu(30)
420+ * maximumWidth: units.gu(100)
421+ * preferredWidth: units.gu(40)
422+ * }
423+ * ViewColumn {
424+ * minimumWidth: units.gu(40)
425+ * fillWidth: true
426+ * }
427+ * }
428+ * ]
429+ * }
430+ *
431+ * Repeater {
432+ * model: view.activeLayout.columns
433+ * Page {
434+ * height: parent.height
435+ * }
436+ * }
437+ * }
438+ * \endcode
439+ *
440+ * \section2 Resizing
441+ * SplitView provides the ability to resize view columns. Each column has an attached
442+ * handle which provides the ability to resize the columns using a mouse or touch.
443+ * Columns can be resized if the spacing property is set and the column configurations
444+ * allow that (see \l spacing property).
445+ *
446+ * \section2 Attached properties
447+ * SplitView provides a set of attached properties to each column view. Views can in
448+ * this way have access to various values of the SplitView and configurations.
449+ */
450+
451+/*!
452+ * \qmlproperty real SplitView::spacing
453+ * Spacing between view columns. A value bigger than 0 enables resizing of columns with
454+ * a \l{ViewColumn::minimumWidth}{minimumWidth} lower than \l {ViewColumn::maximumWidth}{maximumWidth}.
455+ * If spacing is 0 the columns cannot be resized.
456+ * Defaults to 4 device pixels.
457+ */
458+void SplitView::setSpacing2(qreal spacing, bool reset)
459+{
460+ Q_D(SplitView);
461+ if (reset && d->defaultSpacing) {
462+ QObject::disconnect(*d->defaultSpacing);
463+ delete d->defaultSpacing;
464+ d->defaultSpacing = Q_NULLPTR;
465+ }
466+ if (qFuzzyCompare(this->spacing(), spacing)) {
467+ return;
468+ }
469+ static_cast<QQuickBasePositioner*>(this)->setSpacing(spacing);
470+}
471+
472+/******************************************************************************
473+ * SplitViewAttached
474+ */
475+SplitViewAttached::SplitViewAttached(QObject *parent)
476+ : QObject(*(new SplitViewAttachedPrivate), parent)
477+{
478+}
479+
480+SplitViewAttached *SplitViewAttached::get(QQuickItem *item)
481+{
482+ SplitViewAttached *attached = static_cast<SplitViewAttached*>(
483+ qmlAttachedPropertiesObject<SplitView>(item, false));
484+ return attached;
485+}
486+
487+ViewColumn *SplitViewAttachedPrivate::getConfig(QQuickItem *attachee)
488+{
489+ SplitViewAttached *attached = SplitViewAttached::get(attachee);
490+ if (!attached) {
491+ return nullptr;
492+ }
493+ return get(attached)->config();
494+}
495+
496+void SplitViewAttachedPrivate::configure(SplitView *view, int column)
497+{
498+ this->column = column;
499+ splitView = view;
500+ Q_EMIT q_func()->columnChanged();
501+}
502+
503+void SplitViewAttached::resize(qreal delta)
504+{
505+ Q_D(SplitViewAttached);
506+ ViewColumn *config = d->config();
507+ if (config) {
508+ config->resize(delta);
509+ }
510+}
511+
512+/*!
513+ * \qmlattachedproperty ViewColumn SplitView::columnConfig
514+ * The attached property holds the active layout's column configuration data.
515+ * The value is null if there is no active configuration value provided for
516+ * the column.
517+ */
518+UT_PREPEND_NAMESPACE(ViewColumn*) SplitViewAttachedPrivate::config()
519+{
520+ if (!splitView || column < 0) {
521+ return nullptr;
522+ }
523+ SplitViewPrivate *d = SplitViewPrivate::get(splitView);
524+ if (!d->activeLayout) {
525+ return nullptr;
526+ }
527+ if (SplitViewLayoutPrivate::get(d->activeLayout)->columnData.size() <= column) {
528+ return nullptr;
529+ }
530+ return SplitViewLayoutPrivate::get(d->activeLayout)->columnData[column];
531+}
532+
533+/*!
534+ * \qmlattachedproperty SplitView SplitView::view
535+ * Contains the SplitView instance of the column.
536+ */
537+
538+/*!
539+ * \qmlattachedproperty int SplitView::column
540+ * The property holds the column index the view is configured to.
541+ */
542+
543+/******************************************************************************
544+ * SplitView
545+ */
546+SplitViewPrivate::SplitViewPrivate(SplitView *qq)
547+ : q_ptr(qq)
548+{
549+}
550+
551+SplitViewPrivate::~SplitViewPrivate()
552+{
553+}
554+
555+UT_PREPEND_NAMESPACE(SplitViewAttached) *SplitView::qmlAttachedProperties(QObject *owner)
556+{
557+ return new SplitViewAttached(owner);
558+}
559+
560+// layouts property
561+/*!
562+ * \qmlproperty list<SplitViewLayout> SplitView::layouts
563+ * The property holds the layout configurations declared for the given SplitView.
564+ * \sa SplitViewLayout
565+ */
566+void SplitViewPrivate::layout_Append(QQmlListProperty<SplitViewLayout> *list, SplitViewLayout* layout)
567+{
568+ SplitView *view = static_cast<SplitView*>(list->object);
569+ SplitViewPrivate *d = SplitViewPrivate::get(view);
570+ d->columnLatouts.append(layout);
571+ // parent layout to view
572+ layout->setParent(view);
573+ // capture layout activation
574+ QObject::connect(layout, SIGNAL(whenChanged()), view, SLOT(changeLayout()), Qt::DirectConnection);
575+ Q_EMIT view->layoutsChanged();
576+}
577+int SplitViewPrivate::layout_Count(QQmlListProperty<SplitViewLayout> *list)
578+{
579+ SplitView *view = static_cast<SplitView*>(list->object);
580+ SplitViewPrivate *d = SplitViewPrivate::get(view);
581+ return d->columnLatouts.size();
582+}
583+SplitViewLayout *SplitViewPrivate::layout_At(QQmlListProperty<SplitViewLayout> *list, int index)
584+{
585+ SplitView *view = static_cast<SplitView*>(list->object);
586+ SplitViewPrivate *d = SplitViewPrivate::get(view);
587+ return d->columnLatouts.at(index);
588+}
589+void SplitViewPrivate::layout_Clear(QQmlListProperty<SplitViewLayout> *list)
590+{
591+ SplitView *view = static_cast<SplitView*>(list->object);
592+ SplitViewPrivate *d = SplitViewPrivate::get(view);
593+ for (SplitViewLayout *layout : d->columnLatouts) {
594+ // disconnect layout activation
595+ QObject::disconnect(layout, SIGNAL(whenChanged()), view, SLOT(changeLayout()));
596+ delete layout;
597+ }
598+ d->columnLatouts.clear();
599+ d->activeLayout = nullptr;
600+ Q_EMIT view->layoutsChanged();
601+}
602+QQmlListProperty<SplitViewLayout> SplitViewPrivate::layouts()
603+{
604+ Q_Q(SplitView);
605+ return QQmlListProperty<SplitViewLayout>(q, (void*)&columnLatouts,
606+ &layout_Append,
607+ &layout_Count,
608+ &layout_At,
609+ &layout_Clear);
610+}
611+
612+/*!
613+ * \qmlproperty SplitViewLayout SplitView::activeLayout
614+ * \readonly
615+ * The property holds the active SplitViewLayout instance, or null is no layout
616+ * is active.
617+ */
618+UT_PREPEND_NAMESPACE(SplitViewLayout) *SplitViewPrivate::getActiveLayout()
619+{
620+ return activeLayout;
621+}
622+
623+// invoked when one of the SplitViewLayouts emits whenChanged()
624+void SplitViewPrivate::changeLayout()
625+{
626+ // go through layouts and check who's the active one
627+ SplitViewLayout *newActive = nullptr;
628+ for (SplitViewLayout *layout : columnLatouts) {
629+ if (SplitViewLayoutPrivate::get(layout)->when) {
630+ newActive = layout;
631+ break;
632+ }
633+ }
634+ if (newActive == activeLayout) {
635+ return;
636+ }
637+
638+ // Q: should we reset the sizes of the previous layout?
639+ // at least it feels right to preserve the last state of the layout...
640+ activeLayout = newActive;
641+
642+ Q_EMIT q_func()->activeLayoutChanged();
643+
644+ updateLayout();
645+ if (q_func()->sender()) {
646+ // was called by a whenChanged() signal invocation
647+ recalculateWidths(RecalculateAll);
648+ }
649+}
650+
651+void SplitViewPrivate::updateLayout()
652+{
653+ Q_Q(SplitView);
654+ for (QQuickItem *child : q->childItems()) {
655+ bool visible = true;
656+ ViewColumn *columnConfig = SplitViewAttachedPrivate::getConfig(child);
657+ if (!columnConfig) {
658+ // no configuration for the column, hide it
659+ visible = false;
660+ } else {
661+ ViewColumnPrivate *config = ViewColumnPrivate::get(columnConfig);
662+ visible = activeLayout
663+ && (config->column < SplitViewLayoutPrivate::get(activeLayout)->columnData.size());
664+ }
665+ dirty = dirty | (child->isVisible() != visible);
666+ child->setVisible(visible);
667+ }
668+}
669+
670+void SplitViewPrivate::recalculateWidths(RelayoutOperation operation)
671+{
672+ if (!activeLayout || (!QQuickItemPrivate::get(q_func())->componentComplete && !dirty)) {
673+ return;
674+ }
675+ Q_Q(SplitView);
676+
677+ // remove the spacing from the width
678+ qreal fillWidth = q->width() - q->spacing() * (SplitViewLayoutPrivate::get(activeLayout)->columnData.size() - 1);
679+ // stack of columns with fillWidth true
680+ QList<QQuickItem*> fillStack;
681+
682+ for (QQuickItem *child : q->childItems()) {
683+ ViewColumn *columnConfig = SplitViewAttachedPrivate::getConfig(child);
684+ if (!columnConfig) {
685+ continue;
686+ }
687+ ViewColumnPrivate *config = ViewColumnPrivate::get(columnConfig);
688+
689+ if (config->fillWidth && !config->resized) {
690+ // add to the fillWidth stack
691+ if (operation & CalculateFillWidth) {
692+ fillStack << child;
693+ }
694+ } else {
695+ if (operation & SetPreferredSize) {
696+ child->setWidth(config->preferredWidth);
697+ }
698+ if (operation & CalculateFillWidth) {
699+ fillWidth -= config->preferredWidth;
700+ }
701+ }
702+ }
703+
704+ // split the width between the stacked fillWidth columns
705+ if (fillStack.size() && (operation & CalculateFillWidth)) {
706+ fillWidth /= fillStack.size();
707+ for (QQuickItem *child : fillStack) {
708+ // even though the column is fillWidth, it may have min and max specified;
709+ // check if the size can be applied
710+ ViewColumnPrivate *config = ViewColumnPrivate::get(SplitViewAttachedPrivate::getConfig(child));
711+ config->setPreferredWidth(fillWidth, false);
712+ // update preferredWidth so it can be used in case of resize
713+ child->setWidth(config->preferredWidth);
714+ }
715+ }
716+ dirty = false;
717+}
718+
719+/*!
720+ * \qmlproperty Component SplitView::handleDelegate
721+ * The property holds the delegate to be shown for the column resizing handle.
722+ * The delegate is for pure visual, mouse and touch handling is provided by the
723+ * SplitView component itself. The component provides a context property called
724+ * \e handle which embeds the visuals. This can be used to anchor the visuals
725+ * to the resize handle. The thickness of the handle is driven by the \l spacing
726+ * property.
727+ * \code
728+ * import QtQuick 2.4
729+ * import Ubuntu.Components 1.3
730+ * import Ubuntu.Components.Labs 1.0
731+ *
732+ * MainView {
733+ * id: main
734+ * width: units.gu(300)
735+ * height: units.gu(80)
736+ *
737+ * SplitView {
738+ * anchors.fill: parent
739+ * handleDelegate: Rectangle {
740+ * anchors {
741+ * fill: parent
742+ * leftMargin: units.dp(2)
743+ * rightMargin: units.dp(2)
744+ * topMargin: handle.height / 2 - units.gu(3)
745+ * bottomMargin: handle.height / 2 - units.gu(3)
746+ * }
747+ * color: UbuntuColors.graphite
748+ * scale: handle.containsMouse || handle.pressed ? 1.6 : 1.0
749+ * Behavior on scale { UbuntuNumberAnimation {} }
750+ * }
751+ * layouts: [
752+ * SplitViewLayout {
753+ * when: main.width < units.gu(80)
754+ * ViewColumn {
755+ * fillWidth: true
756+ * }
757+ * },
758+ * SplitViewLayout {
759+ * when: main.width >= units.gu(80)
760+ * ViewColumn {
761+ * minimumWidth: units.gu(30)
762+ * maximumWidth: units.gu(100)
763+ * preferredWidth: units.gu(40)
764+ * }
765+ * ViewColumn {
766+ * minimumWidth: units.gu(40)
767+ * fillWidth: true
768+ * }
769+ * }
770+ * ]
771+ * }
772+ *
773+ * Page {
774+ * id: column1
775+ * height: parent.height
776+ * }
777+ * Page {
778+ * id: column2
779+ * height: parent.height
780+ * }
781+ * }
782+ * \endcode
783+ */
784+
785+SplitView::SplitView(QQuickItem *parent)
786+ : QQuickBasePositioner(Horizontal, parent)
787+ , d_ptr(new SplitViewPrivate(this))
788+{
789+ Q_D(SplitView);
790+ d->init();
791+}
792+
793+SplitView::SplitView(SplitViewPrivate &dd, QQuickItem *parent)
794+ : QQuickBasePositioner(Vertical, parent)
795+ , d_ptr(&dd)
796+{
797+ Q_D(SplitView);
798+ d->init();
799+}
800+
801+SplitView::~SplitView()
802+{
803+ Q_D(SplitView);
804+ delete d;
805+}
806+
807+void SplitViewPrivate::init()
808+{
809+ Q_Q(SplitView);
810+ auto spacingHandle = [q]() {
811+ q->setSpacing2(UCUnits::instance(q)->dp(DEFAULT_SPACING_DP), false);
812+ };
813+ defaultSpacing = new QMetaObject::Connection;
814+ *defaultSpacing = QObject::connect(UCUnits::instance(q), &UCUnits::gridUnitChanged, spacingHandle);
815+
816+ // connect the spacingChanged signals
817+ QObject::connect(q, SIGNAL(spacingChanged()), q, SIGNAL(spacingChanged2()));
818+
819+ // set the defaults
820+ q->setSpacing2(UCUnits::instance(q)->dp(DEFAULT_SPACING_DP), false);
821+}
822+
823+void SplitView::doPositioning(QSizeF *contentSize)
824+{
825+ // Inspired from QtQuick QQuickRow code
826+ // FIXME: revisit the code once we move to Qt 5.6 as there were more properties added to positioner
827+
828+ // calculate the layout before we go into the positioning
829+ d_func()->recalculateWidths(SplitViewPrivate::RecalculateAll);
830+
831+ //Precondition: All items in the positioned list have a valid item pointer and should be positioned
832+ QQuickItemPrivate *d = QQuickItemPrivate::get(this);
833+ qreal hoffset = 0;
834+
835+ QList<qreal> hoffsets;
836+ for (int ii = 0; ii < positionedItems.count(); ++ii) {
837+ PositionedItem &child = positionedItems[ii];
838+
839+ if (!d->effectiveLayoutMirror) {
840+ positionItemX(hoffset, &child);
841+ } else {
842+ hoffsets << hoffset;
843+ }
844+
845+ contentSize->setHeight(qMax(contentSize->height(), child.item->height()));
846+
847+ hoffset += child.item->width();
848+ hoffset += spacing();
849+ }
850+
851+ if (hoffset != 0) { //If we positioned any items, undo the extra spacing from the last item
852+ hoffset -= spacing();
853+ }
854+ contentSize->setWidth(hoffset);
855+
856+ if (!d->effectiveLayoutMirror) {
857+ return;
858+ }
859+
860+ //Right to Left layout
861+ qreal end = 0;
862+ if (!widthValid()) {
863+ end = contentSize->width();
864+ } else {
865+ end = width();
866+ }
867+
868+ int acc = 0;
869+ for (int ii = 0; ii < positionedItems.count(); ++ii) {
870+ PositionedItem &child = positionedItems[ii];
871+ hoffset = end - hoffsets[acc++] - child.item->width();
872+ positionItemX(hoffset, &child);
873+ }
874+}
875+
876+void SplitView::reportConflictingAnchors()
877+{
878+ // Inspired from QtQuick QQuickColumn code
879+ bool anchorConflict = false;
880+ for (int ii = 0; ii < positionedItems.count(); ++ii) {
881+ const PositionedItem &child = positionedItems.at(ii);
882+ if (child.item) {
883+ QQuickAnchors *anchors = QQuickItemPrivate::get(static_cast<QQuickItem *>(child.item))->_anchors;
884+ if (anchors) {
885+ // can we patch out the anchors (Page?)
886+ if (qobject_cast<UCPageTreeNode*>(child.item)) {
887+ // unset left anchor
888+ anchors->resetLeft();
889+ }
890+ QQuickAnchors::Anchors usedAnchors = anchors->usedAnchors();
891+ if (usedAnchors & QQuickAnchors::LeftAnchor ||
892+ usedAnchors & QQuickAnchors::RightAnchor ||
893+ usedAnchors & QQuickAnchors::HCenterAnchor ||
894+ anchors->fill() || anchors->centerIn()) {
895+ anchorConflict = true;
896+ break;
897+ }
898+ }
899+ }
900+ }
901+ if (anchorConflict) {
902+ qmlInfo(this) << "Cannot specify left, right, horizontalCenter, fill or centerIn anchors for items inside SplitView."
903+ << " SplitView will not function.";
904+ }
905+}
906+
907+void SplitView::componentComplete()
908+{
909+ Q_D(SplitView);
910+ d->changeLayout();
911+ QQuickBasePositioner::componentComplete();
912+}
913+
914+void SplitView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
915+{
916+ QQuickBasePositioner::geometryChanged(newGeometry, oldGeometry);
917+ // if we have fillWidths, recalculate those!
918+ // call this on horizontal resize, vertical one will do its job
919+ if (newGeometry.width() != oldGeometry.width()) {
920+ Q_D(SplitView);
921+ d->recalculateWidths(SplitViewPrivate::CalculateFillWidth);
922+ }
923+}
924+
925+void SplitView::itemChange(ItemChange change, const ItemChangeData &data)
926+{
927+ QQuickBasePositioner::itemChange(change, data);
928+ // we must exclude Repeater
929+ switch (change) {
930+ case ItemChildAddedChange:
931+ if (data.item && !data.item->inherits("QQuickRepeater")) {
932+ // attach properties and configure
933+ SplitViewAttached *attached = static_cast<SplitViewAttached*>(
934+ qmlAttachedPropertiesObject<SplitView>(data.item, true));
935+
936+ Q_D(SplitView);
937+ SplitViewAttachedPrivate::get(attached)->configure(this, d->viewCount++);
938+
939+ // attach the split handler to it
940+ SplitViewHandler *handler = new SplitViewHandler(data.item);
941+ handler->connectToView(this);
942+ }
943+ break;
944+ case ItemChildRemovedChange:
945+ if (data.item && !data.item->inherits("QQuickRepeater")) {
946+ Q_D(SplitView);
947+ d->viewCount--;
948+ }
949+ break;
950+ default: // ommit the rest
951+ break;
952+ }
953+}
954+
955+/******************************************************************
956+ * properties
957+ */
958+
959+void SplitViewPrivate::setHandle(QQmlComponent *delegate)
960+{
961+ if (handleDelegate == delegate) {
962+ return;
963+ }
964+ handleDelegate = delegate;
965+ Q_EMIT q_func()->handleDelegateChanged();
966+}
967+
968+UT_NAMESPACE_END
969+
970+#include "moc_splitview_p.cpp"
971
972=== added file 'src/Ubuntu/UbuntuToolkit/splitview_p.h'
973--- src/Ubuntu/UbuntuToolkit/splitview_p.h 1970-01-01 00:00:00 +0000
974+++ src/Ubuntu/UbuntuToolkit/splitview_p.h 2016-08-26 05:41:49 +0000
975@@ -0,0 +1,160 @@
976+/*
977+ * Copyright 2016 Canonical Ltd.
978+ *
979+ * This program is free software; you can redistribute it and/or modify
980+ * it under the terms of the GNU Lesser General Public License as published by
981+ * the Free Software Foundation; version 3.
982+ *
983+ * This program is distributed in the hope that it will be useful,
984+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
985+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
986+ * GNU Lesser General Public License for more details.
987+ *
988+ * You should have received a copy of the GNU Lesser General Public License
989+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
990+ *
991+ * Author: Zsombor Egri <zsombor.egri@canonical.com>
992+ */
993+
994+#ifndef SPLITVIEW_P_H
995+#define SPLITVIEW_P_H
996+
997+#include <QtQuick/private/qquickpositioners_p.h>
998+
999+#include "ubuntutoolkitglobal.h"
1000+
1001+UT_NAMESPACE_BEGIN
1002+
1003+class ViewColumnPrivate;
1004+class ViewColumn : public QObject, public QQmlParserStatus
1005+{
1006+ Q_OBJECT
1007+ Q_INTERFACES(QQmlParserStatus)
1008+ Q_PRIVATE_PROPERTY(ViewColumn::d_func(), bool fillWidth MEMBER fillWidth WRITE setFillWidth NOTIFY fillWidthChanged)
1009+ Q_PRIVATE_PROPERTY(ViewColumn::d_func(), qreal minimumWidth MEMBER minimumWidth WRITE setMinimumWidth NOTIFY minimumWidthChanged)
1010+ Q_PRIVATE_PROPERTY(ViewColumn::d_func(), qreal maximumWidth MEMBER maximumWidth WRITE setMaximumWidth NOTIFY maximumWidthChanged)
1011+ Q_PRIVATE_PROPERTY(ViewColumn::d_func(), qreal preferredWidth MEMBER preferredWidth WRITE setPreferredWidth NOTIFY preferredWidthChanged)
1012+public:
1013+ explicit ViewColumn(QObject *parent = 0);
1014+
1015+ bool resize(qreal delta);
1016+
1017+Q_SIGNALS:
1018+ void minimumWidthChanged();
1019+ void maximumWidthChanged();
1020+ void preferredWidthChanged();
1021+ void fillWidthChanged();
1022+
1023+protected:
1024+ // from QQmlParserStatus
1025+ void classBegin() override {}
1026+ void componentComplete() override;
1027+private:
1028+ Q_DECLARE_PRIVATE(ViewColumn)
1029+};
1030+
1031+class SplitViewLayoutPrivate;
1032+class SplitViewLayout : public QObject
1033+{
1034+ Q_OBJECT
1035+
1036+ Q_PRIVATE_PROPERTY(SplitViewLayout::d_func(), bool when MEMBER when NOTIFY whenChanged)
1037+#ifdef Q_QDOC
1038+ Q_PRIVATE_PROPERTY(SplitViewLayout::d_func(), QQmlListProperty<ViewColumn> columns READ columns NOTIFY columnsChanged DESIGNABLE false)
1039+#else
1040+ Q_PRIVATE_PROPERTY(SplitViewLayout::d_func(), QQmlListProperty<UT_PREPEND_NAMESPACE(ViewColumn)> columns READ columns NOTIFY columnsChanged DESIGNABLE false)
1041+#endif
1042+ Q_CLASSINFO("DefaultProperty", "columns")
1043+public:
1044+ explicit SplitViewLayout(QObject *parent = 0);
1045+
1046+Q_SIGNALS:
1047+ void whenChanged();
1048+ void columnsChanged();
1049+
1050+private:
1051+ Q_DECLARE_PRIVATE(SplitViewLayout)
1052+};
1053+
1054+class SplitView;
1055+class SplitViewAttachedPrivate;
1056+class SplitViewAttached : public QObject
1057+{
1058+ Q_OBJECT
1059+ Q_PRIVATE_PROPERTY(SplitViewAttached::d_func(), int column READ getColumn NOTIFY columnChanged)
1060+#ifdef Q_QDOC
1061+ Q_PRIVATE_PROPERTY(SplitViewAttached::d_func(), SplitView* view READ view)
1062+ Q_PRIVATE_PROPERTY(SplitViewAttached::d_func(), ViewColumn* columnConfig READ config NOTIFY columnChanged)
1063+#else
1064+ Q_PRIVATE_PROPERTY(SplitViewAttached::d_func(), UT_PREPEND_NAMESPACE(SplitView*) view READ view)
1065+ Q_PRIVATE_PROPERTY(SplitViewAttached::d_func(), UT_PREPEND_NAMESPACE(ViewColumn*) columnConfig READ config NOTIFY columnChanged)
1066+#endif
1067+public:
1068+ explicit SplitViewAttached(QObject *parent = 0);
1069+
1070+ static SplitViewAttached *get(QQuickItem *item);
1071+
1072+ void resize(qreal delta);
1073+
1074+Q_SIGNALS:
1075+ void columnChanged();
1076+
1077+private:
1078+ Q_DECLARE_PRIVATE(SplitViewAttached)
1079+};
1080+
1081+class SplitViewPrivate;
1082+class SplitView : public QQuickBasePositioner
1083+{
1084+ Q_OBJECT
1085+#ifdef Q_QDOC
1086+ Q_PRIVATE_PROPERTY(SplitView::d_func(), QQmlListProperty<SplitViewLayout> layouts READ layouts NOTIFY layoutsChanged DESIGNABLE false)
1087+ Q_PRIVATE_PROPERTY(SplitView::d_func(), SplitViewLayout *activeLayout READ getActiveLayout NOTIFY activeLayoutChanged)
1088+#else
1089+ Q_PRIVATE_PROPERTY(SplitView::d_func(), QQmlListProperty<UT_PREPEND_NAMESPACE(SplitViewLayout)> layouts READ layouts NOTIFY layoutsChanged DESIGNABLE false)
1090+ Q_PRIVATE_PROPERTY(SplitView::d_func(), UT_PREPEND_NAMESPACE(SplitViewLayout) *activeLayout READ getActiveLayout NOTIFY activeLayoutChanged)
1091+#endif
1092+ Q_PRIVATE_PROPERTY(SplitView::d_func(), QQmlComponent *handleDelegate MEMBER handleDelegate WRITE setHandle NOTIFY handleDelegateChanged)
1093+ // overrides
1094+ Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing2 NOTIFY spacingChanged2)
1095+public:
1096+ explicit SplitView(QQuickItem *parent = 0);
1097+
1098+ static UT_PREPEND_NAMESPACE(SplitViewAttached) *qmlAttachedProperties(QObject*);
1099+
1100+Q_SIGNALS:
1101+ void layoutsChanged();
1102+ void activeLayoutChanged();
1103+ void handleDelegateChanged();
1104+ void spacingChanged2();
1105+
1106+protected:
1107+ SplitView(SplitViewPrivate &, QQuickItem *);
1108+ ~SplitView();
1109+
1110+ // property setters
1111+ void setSpacing2(qreal spacing, bool reset = true);
1112+
1113+ // from QQuickBasePositioner
1114+ void doPositioning(QSizeF *contentSize) override;
1115+ void reportConflictingAnchors() override;
1116+
1117+ // overrides
1118+ void componentComplete() override;
1119+ void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
1120+ void itemChange(ItemChange, const ItemChangeData &) override;
1121+private:
1122+ // QQuickBasePositionerPrivate is not an exported API, therefore we cannot derive from it
1123+ SplitViewPrivate* const d_ptr;
1124+ Q_DECLARE_PRIVATE_D(d_ptr, SplitView)
1125+ Q_PRIVATE_SLOT(d_func(), void changeLayout())
1126+};
1127+
1128+UT_NAMESPACE_END
1129+
1130+QML_DECLARE_TYPE(UT_PREPEND_NAMESPACE(ViewColumn))
1131+QML_DECLARE_TYPE(UT_PREPEND_NAMESPACE(SplitViewLayout))
1132+QML_DECLARE_TYPE(UT_PREPEND_NAMESPACE(SplitView))
1133+QML_DECLARE_TYPEINFO(UT_PREPEND_NAMESPACE(SplitView), QML_HAS_ATTACHED_PROPERTIES)
1134+
1135+#endif // SPLITVIEW_P_H
1136
1137=== added file 'src/Ubuntu/UbuntuToolkit/splitview_p_p.h'
1138--- src/Ubuntu/UbuntuToolkit/splitview_p_p.h 1970-01-01 00:00:00 +0000
1139+++ src/Ubuntu/UbuntuToolkit/splitview_p_p.h 2016-08-26 05:41:49 +0000
1140@@ -0,0 +1,154 @@
1141+/*
1142+ * Copyright 2016 Canonical Ltd.
1143+ *
1144+ * This program is free software; you can redistribute it and/or modify
1145+ * it under the terms of the GNU Lesser General Public License as published by
1146+ * the Free Software Foundation; version 3.
1147+ *
1148+ * This program is distributed in the hope that it will be useful,
1149+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1150+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1151+ * GNU Lesser General Public License for more details.
1152+ *
1153+ * You should have received a copy of the GNU Lesser General Public License
1154+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1155+ *
1156+ * Author: Zsombor Egri <zsombor.egri@canonical.com>
1157+ */
1158+
1159+#ifndef SPLITVIEW_P_P_H
1160+#define SPLITVIEW_P_P_H
1161+
1162+#include "splitview_p.h"
1163+#include <QtCore/private/qobject_p.h>
1164+
1165+UT_NAMESPACE_BEGIN
1166+
1167+class ViewColumnPrivate : public QObjectPrivate
1168+{
1169+ Q_DECLARE_PUBLIC(ViewColumn)
1170+
1171+public:
1172+ ViewColumnPrivate() {}
1173+
1174+ static ViewColumnPrivate *get(ViewColumn *q)
1175+ {
1176+ return q ? q->d_func() : nullptr;
1177+ }
1178+
1179+ void setMinimumWidth(qreal width);
1180+ void setMaximumWidth(qreal width);
1181+ void setPreferredWidth(qreal width, bool notify = true);
1182+ void setFillWidth(bool fill);
1183+
1184+ void recalculateLayoutContent();
1185+
1186+ qreal minimumWidth{0.0};
1187+ qreal maximumWidth{std::numeric_limits<qreal>::max()};
1188+ qreal preferredWidth{0.0};
1189+ int column{-1};
1190+ bool fillWidth{false};
1191+ bool resized{false};
1192+ bool completed{false};
1193+};
1194+
1195+class SplitViewLayoutPrivate : public QObjectPrivate
1196+{
1197+ Q_DECLARE_PUBLIC(SplitViewLayout)
1198+public:
1199+ SplitViewLayoutPrivate() {}
1200+ static SplitViewLayoutPrivate *get(SplitViewLayout *q)
1201+ {
1202+ return q->d_func();
1203+ }
1204+
1205+ QQmlListProperty<UT_PREPEND_NAMESPACE(ViewColumn)> columns();
1206+
1207+ QList<ViewColumn*> columnData;
1208+ bool when{false};
1209+
1210+private:
1211+ static void columns_Append(QQmlListProperty<ViewColumn> *, ViewColumn*);
1212+ static int columns_Count(QQmlListProperty<ViewColumn> *);
1213+ static ViewColumn *columns_At(QQmlListProperty<ViewColumn> *, int);
1214+ static void columns_Clear(QQmlListProperty<ViewColumn> *);
1215+};
1216+
1217+class SplitViewAttachedPrivate : public QObjectPrivate
1218+{
1219+ Q_DECLARE_PUBLIC(SplitViewAttached)
1220+public:
1221+ SplitViewAttachedPrivate() {}
1222+
1223+ static SplitViewAttachedPrivate *get(SplitViewAttached *q)
1224+ {
1225+ return q->d_func();
1226+ }
1227+ static ViewColumn *getConfig(QQuickItem *attachee);
1228+
1229+ UT_PREPEND_NAMESPACE(SplitView*) view() const
1230+ {
1231+ return splitView;
1232+ }
1233+ int getColumn() const
1234+ {
1235+ return column;
1236+ }
1237+ void configure(SplitView *view, int column);
1238+ UT_PREPEND_NAMESPACE(ViewColumn*) config();
1239+
1240+ SplitView *splitView{nullptr};
1241+ int column{-1};
1242+};
1243+
1244+class SplitViewPrivate
1245+{
1246+ SplitView *const q_ptr{nullptr};
1247+ Q_DECLARE_PUBLIC(SplitView)
1248+
1249+public:
1250+ enum RelayoutOperation {
1251+ SetPreferredSize = 0x01,
1252+ CalculateFillWidth = 0x02,
1253+ RecalculateAll = 0xFF
1254+ };
1255+
1256+ SplitViewPrivate(SplitView *qq);
1257+ virtual ~SplitViewPrivate();
1258+ void init();
1259+
1260+ static SplitViewPrivate *get(SplitView *q)
1261+ {
1262+ return q->d_func();
1263+ }
1264+
1265+ QQmlListProperty<QObject> data();
1266+ QQmlListProperty<UT_PREPEND_NAMESPACE(SplitViewLayout)> layouts();
1267+ UT_PREPEND_NAMESPACE(SplitViewLayout) *getActiveLayout();
1268+
1269+ void updateLayout();
1270+ void recalculateWidths(RelayoutOperation operation);
1271+ void setHandle(QQmlComponent *delegate);
1272+
1273+ // private slots
1274+ void changeLayout();
1275+
1276+ // members
1277+ QList<SplitViewLayout*> columnLatouts;
1278+ SplitViewLayout* activeLayout{nullptr};
1279+ QQmlComponent *handleDelegate{nullptr};
1280+ QMetaObject::Connection *defaultSpacing{nullptr};
1281+ int viewCount{0};
1282+ bool dirty{false};
1283+
1284+private:
1285+ static void layout_Append(QQmlListProperty<SplitViewLayout> *, SplitViewLayout*);
1286+ static int layout_Count(QQmlListProperty<SplitViewLayout> *);
1287+ static SplitViewLayout *layout_At(QQmlListProperty<SplitViewLayout> *, int);
1288+ static void layout_Clear(QQmlListProperty<SplitViewLayout> *);
1289+};
1290+
1291+UT_NAMESPACE_END
1292+
1293+#endif // SPLITVIEW_P_P_H
1294+
1295
1296=== added file 'src/Ubuntu/UbuntuToolkit/splitviewlayout.cpp'
1297--- src/Ubuntu/UbuntuToolkit/splitviewlayout.cpp 1970-01-01 00:00:00 +0000
1298+++ src/Ubuntu/UbuntuToolkit/splitviewlayout.cpp 2016-08-26 05:41:49 +0000
1299@@ -0,0 +1,252 @@
1300+/* Copyright 2016 Canonical Ltd.
1301+ *
1302+ * This program is free software; you can redistribute it and/or modify
1303+ * it under the terms of the GNU Lesser General Public License as published by
1304+ * the Free Software Foundation; version 3.
1305+ *
1306+ * This program is distributed in the hope that it will be useful,
1307+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1308+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1309+ * GNU Lesser General Public License for more details.
1310+ *
1311+ * You should have received a copy of the GNU Lesser General Public License
1312+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1313+ *
1314+ * Author: Zsombor Egri <zsombor.egri@canonical.com>
1315+ */
1316+
1317+#include <QtQuick/private/qquickitem_p.h>
1318+#include <QtQuick/private/qquickanchors_p.h>
1319+#include <QtQml/QQmlInfo>
1320+
1321+#include "splitview_p.h"
1322+#include "splitview_p_p.h"
1323+#include "ucmathutils_p.h"
1324+
1325+UT_NAMESPACE_BEGIN
1326+
1327+/******************************************************************************
1328+ * ViewColumn configuration object
1329+ */
1330+/*!
1331+ * \qmltype ViewColumn
1332+ * \inmodule Ubuntu.Components.Labs
1333+ * \ingroup ubuntu-labs
1334+ * \brief View column metrics configuration for SplitView.
1335+ *
1336+ * The component provides width metrics configuration for SplitView layout
1337+ * columns. The values are applied on columns by an active SplitViewLayout.
1338+ * On resizing, the values are preserved for the entire lifetime of the
1339+ * component, even when the active layout is changed into an other one.
1340+ * When changed back, the previous values will be used.
1341+ */
1342+ViewColumn::ViewColumn(QObject *parent)
1343+ : QObject(*(new ViewColumnPrivate), parent)
1344+{
1345+}
1346+
1347+void ViewColumn::componentComplete()
1348+{
1349+ // check preferredWidth, its value may need fixing
1350+ Q_D(ViewColumn);
1351+ d->setPreferredWidth(d->preferredWidth);
1352+ d->completed = true;
1353+}
1354+
1355+void ViewColumnPrivate::recalculateLayoutContent()
1356+{
1357+ SplitViewLayout *layout = qobject_cast<SplitViewLayout*>(parent);
1358+ if (!layout) {
1359+ qFatal("ViewColumn declared outside of SplitViewLayout");
1360+ }
1361+ SplitView *view = qobject_cast<SplitView*>(SplitViewLayoutPrivate::get(layout)->parent);
1362+ if (!view) {
1363+ qFatal("SplitViewLayout not used in any SplitView");
1364+ }
1365+
1366+ SplitViewPrivate *dView = SplitViewPrivate::get(view);
1367+ if (dView->activeLayout == layout) {
1368+ dView->recalculateWidths(SplitViewPrivate::RecalculateAll);
1369+ }
1370+}
1371+
1372+/*!
1373+ * \qmlproperty real ViewColumn::minimumWidth
1374+ * Specifies the minimum width of the column. The number must be a positive value
1375+ * and less or equal than the maximumWidth value.
1376+ */
1377+void ViewColumnPrivate::setMinimumWidth(qreal width)
1378+{
1379+ if (qFuzzyCompare(minimumWidth, width)) {
1380+ return;
1381+ }
1382+ if (width < 0.0) {
1383+ qmlInfo(q_func()) << "minimumWidth cannot be a negative value";
1384+ return;
1385+ }
1386+ if (width > maximumWidth) {
1387+ qmlInfo(q_func()) << "minimumWidth is greater than maximumWidth";
1388+ return;
1389+ }
1390+ minimumWidth = width;
1391+ Q_EMIT q_func()->minimumWidthChanged();
1392+ // clamp preferredWidth if needed
1393+ setPreferredWidth(preferredWidth);
1394+ recalculateLayoutContent();
1395+}
1396+
1397+/*!
1398+ * \qmlproperty real ViewColumn::maximumWidth
1399+ * Specifies the maximum width of the column. The number must be a positive value
1400+ * and bigger than the minimumWidth value.
1401+ */
1402+void ViewColumnPrivate::setMaximumWidth(qreal width)
1403+{
1404+ if (qFuzzyCompare(maximumWidth, width)) {
1405+ return;
1406+ }
1407+ if (width < 0.0) {
1408+ qmlInfo(q_func()) << "maximumWidth cannot be a negative value";
1409+ return;
1410+ }
1411+ if (width < minimumWidth) {
1412+ qmlInfo(q_func()) << "maximumWidth is smaller than minimumWidth";
1413+ return;
1414+ }
1415+ maximumWidth = width;
1416+ Q_EMIT q_func()->maximumWidthChanged();
1417+ setPreferredWidth(preferredWidth);
1418+ recalculateLayoutContent();
1419+}
1420+
1421+/*!
1422+ * \qmlproperty real ViewColumn::preferredWidth
1423+ * The property holds the preferred width of the column. The value must be situated
1424+ * in between minimumWidth and maximumWidth. In case fillWidth is set, the value will
1425+ * hold the actual width of the column, but setting its value will not affect the
1426+ * width of the column.
1427+ */
1428+void ViewColumnPrivate::setPreferredWidth(qreal width, bool notify)
1429+{
1430+ // clamp
1431+ width = UCMathUtils::clamp(width, minimumWidth, maximumWidth);
1432+ if (qFuzzyCompare(preferredWidth, width)) {
1433+ return;
1434+ }
1435+ preferredWidth = width;
1436+ if (notify) {
1437+ Q_EMIT q_func()->preferredWidthChanged();
1438+ recalculateLayoutContent();
1439+ }
1440+}
1441+
1442+/*!
1443+ * \qmlproperty bool ViewColumn::fillWidth
1444+ * If set, the column width will take the space available after all the other
1445+ * columns with non-fill width are configured. This means that if there is more
1446+ * than one column configured to fill width, the reminder width is split evenly
1447+ * between these columns. If all columns are configured to fill width, the colum
1448+ * widths will be split evenly between all the columns.
1449+ * \note When a column configured with fillWidth is resized, the properties will
1450+ * not be altered, but the fillWidth wioll no longer be taken into account. Instead,
1451+ * the preferredWidth will drive the width of that column from that point on.
1452+ */
1453+void ViewColumnPrivate::setFillWidth(bool fill)
1454+{
1455+ if (fill == fillWidth) {
1456+ return;
1457+ }
1458+ fillWidth = fill;
1459+ Q_EMIT q_func()->fillWidthChanged();
1460+ recalculateLayoutContent();
1461+}
1462+
1463+bool ViewColumn::resize(qreal delta)
1464+{
1465+ Q_D(ViewColumn);
1466+ // apply limits
1467+ qreal newWidth = d->preferredWidth + delta;
1468+ // clamp
1469+ newWidth = UCMathUtils::clamp(newWidth, d->minimumWidth, d->maximumWidth);
1470+ if (newWidth != d->preferredWidth) {
1471+ d->resized = true;
1472+ d->setPreferredWidth(newWidth);
1473+ return true;
1474+ }
1475+ return false;
1476+}
1477+
1478+/******************************************************************************
1479+ * SplitViewLayout layouts configuration
1480+ */
1481+/*!
1482+ * \qmltype SplitViewLayout
1483+ * \inmodule Ubuntu.Components.Labs
1484+ * \ingroup ubuntu-labs
1485+ * \brief Layout configuration for SplitView.
1486+ *
1487+ * SplitViewLayout defines a layout configuration and the condition when the layout is
1488+ * expected to be applied. In case multiple layout conditions evaluate to true, the first
1489+ * one in the list will be activated.
1490+ */
1491+SplitViewLayout::SplitViewLayout(QObject *parent)
1492+ : QObject(*(new SplitViewLayoutPrivate), parent)
1493+{
1494+}
1495+
1496+void SplitViewLayoutPrivate::columns_Append(QQmlListProperty<ViewColumn> *list, ViewColumn* data)
1497+{
1498+ SplitViewLayout *layout = static_cast<SplitViewLayout*>(list->object);
1499+ SplitViewLayoutPrivate *d = SplitViewLayoutPrivate::get(layout);
1500+ ViewColumnPrivate::get(data)->column = d->columnData.size();
1501+ // make sure ViewColumn is parented to the layout definition
1502+ data->setParent(layout);
1503+ d->columnData.append(data);
1504+ Q_EMIT layout->columnsChanged();
1505+}
1506+int SplitViewLayoutPrivate::columns_Count(QQmlListProperty<ViewColumn> *list)
1507+{
1508+ SplitViewLayout *layout = static_cast<SplitViewLayout*>(list->object);
1509+ SplitViewLayoutPrivate *d = SplitViewLayoutPrivate::get(layout);
1510+ return d->columnData.size();
1511+}
1512+ViewColumn *SplitViewLayoutPrivate::columns_At(QQmlListProperty<ViewColumn> *list, int index)
1513+{
1514+ SplitViewLayout *layout = static_cast<SplitViewLayout*>(list->object);
1515+ SplitViewLayoutPrivate *d = SplitViewLayoutPrivate::get(layout);
1516+ return d->columnData.at(index);
1517+}
1518+void SplitViewLayoutPrivate::columns_Clear(QQmlListProperty<ViewColumn> *list)
1519+{
1520+ SplitViewLayout *layout = static_cast<SplitViewLayout*>(list->object);
1521+ SplitViewLayoutPrivate *d = SplitViewLayoutPrivate::get(layout);
1522+ qDeleteAll(d->columnData);
1523+ d->columnData.clear();
1524+ Q_EMIT layout->columnsChanged();
1525+}
1526+
1527+/*!
1528+ * \qmlproperty list<ViewColumn> SplitViewLayout::columns
1529+ * \default
1530+ * The property holds the column configurations for the layout. If the view has more
1531+ * columns than the configuration specifies, the extra columns will be hidden (their
1532+ * visible property will be set to false!). Also, the hidden column sizes may change,
1533+ * therefore size-sensitive content must be aware of this.
1534+ */
1535+QQmlListProperty<UT_PREPEND_NAMESPACE(ViewColumn)> SplitViewLayoutPrivate::columns()
1536+{
1537+ Q_Q(SplitViewLayout);
1538+ return QQmlListProperty<UT_PREPEND_NAMESPACE(ViewColumn)>(q, &columnData,
1539+ &columns_Append,
1540+ &columns_Count,
1541+ &columns_At,
1542+ &columns_Clear);
1543+}
1544+
1545+/*!
1546+ * \qmlproperty bool SplitViewLayout::when
1547+ * Specifies the condition when to apply the layout. Usually holds a binding which
1548+ * evaluates to true or false to activate the layout.
1549+ */
1550+
1551+UT_NAMESPACE_END
1552
1553=== modified file 'src/Ubuntu/UbuntuToolkit/ubuntutoolkitmodule.cpp'
1554--- src/Ubuntu/UbuntuToolkit/ubuntutoolkitmodule.cpp 2016-08-25 13:13:39 +0000
1555+++ src/Ubuntu/UbuntuToolkit/ubuntutoolkitmodule.cpp 2016-08-26 05:41:49 +0000
1556@@ -92,6 +92,8 @@
1557 #include <actionlist_p.h>
1558 #include <exclusivegroup_p.h>
1559
1560+#include <splitview_p.h>
1561+
1562 // styles
1563 #include <ucbottomedgestyle_p.h>
1564
1565@@ -417,6 +419,9 @@
1566 Q_UNUSED(uri);
1567 // a fake component so we can have the module types file created
1568 qmlRegisterType<QObject>(uri, 1, 0, "ZiObject");
1569+ qmlRegisterType<SplitView>(uri, 1, 0, "SplitView");
1570+ qmlRegisterType<SplitViewLayout>(uri, 1, 0, "SplitViewLayout");
1571+ qmlRegisterType<ViewColumn>(uri, 1, 0, "ViewColumn");
1572 }
1573
1574 void UbuntuLabsModule::undefineModule()
1575
1576=== modified file 'src/Ubuntu/UbuntuToolkit/ucmathutils_p.h'
1577--- src/Ubuntu/UbuntuToolkit/ucmathutils_p.h 2016-07-07 07:21:48 +0000
1578+++ src/Ubuntu/UbuntuToolkit/ucmathutils_p.h 2016-08-26 05:41:49 +0000
1579@@ -32,10 +32,10 @@
1580 public:
1581 explicit UCMathUtils(QObject *parent = 0);
1582
1583- Q_INVOKABLE double clamp(double x, double min, double max);
1584- Q_INVOKABLE double lerp(double delta, double from, double to);
1585- Q_INVOKABLE double projectValue(double x, double xmin, double xmax, double ymin, double ymax);
1586- Q_INVOKABLE double clampAndProject(double x, double xmin, double xmax, double ymin, double ymax);
1587+ Q_INVOKABLE static double clamp(double x, double min, double max);
1588+ Q_INVOKABLE static double lerp(double delta, double from, double to);
1589+ Q_INVOKABLE static double projectValue(double x, double xmin, double xmax, double ymin, double ymax);
1590+ Q_INVOKABLE static double clampAndProject(double x, double xmin, double xmax, double ymin, double ymax);
1591 };
1592
1593 UT_NAMESPACE_END
1594
1595=== added file 'tests/unit/visual/tst_splitview.13.qml'
1596--- tests/unit/visual/tst_splitview.13.qml 1970-01-01 00:00:00 +0000
1597+++ tests/unit/visual/tst_splitview.13.qml 2016-08-26 05:41:49 +0000
1598@@ -0,0 +1,383 @@
1599+/*
1600+ * Copyright 2016 Canonical Ltd.
1601+ *
1602+ * This program is free software; you can redistribute it and/or modify
1603+ * it under the terms of the GNU Lesser General Public License as published by
1604+ * the Free Software Foundation; version 3.
1605+ *
1606+ * This program is distributed in the hope that it will be useful,
1607+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1608+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1609+ * GNU Lesser General Public License for more details.
1610+ *
1611+ * You should have received a copy of the GNU Lesser General Public License
1612+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1613+ */
1614+
1615+import QtQuick 2.4
1616+import QtTest 1.1
1617+import Ubuntu.Test 1.3
1618+import Ubuntu.Components 1.3
1619+import Ubuntu.Components.Labs 1.0
1620+
1621+Item {
1622+ id: main
1623+ width: units.gu(200)
1624+ height: units.gu(70)
1625+
1626+ Component {
1627+ id: testHandle
1628+ Rectangle {
1629+ radius: units.gu(1)
1630+ anchors {
1631+ fill: handle
1632+ leftMargin: units.dp(1)
1633+ rightMargin: units.dp(1)
1634+ topMargin: handle.height / 2 - units.gu(3)
1635+ bottomMargin: handle.height / 2 - units.gu(3)
1636+ }
1637+ color: UbuntuColors.graphite
1638+ scale: handle.containsMouse || handle.pressed ? 1.6 : 1.0
1639+ Behavior on scale { UbuntuNumberAnimation {} }
1640+ }
1641+ }
1642+
1643+ Component {
1644+ id: testLayout
1645+ SplitView {
1646+ id: layout
1647+ anchors.fill: parent
1648+ property int when: 1
1649+ handleDelegate: testHandle
1650+
1651+ layouts: [
1652+ SplitViewLayout {
1653+ id: mainLayout
1654+ when: layout.when == 1
1655+ ViewColumn {
1656+ preferredWidth: units.gu(10)
1657+ maximumWidth: units.gu(100)
1658+ }
1659+ ViewColumn {
1660+ fillWidth: true
1661+ minimumWidth: units.gu(10)
1662+ maximumWidth: units.gu(150)
1663+ }
1664+ ViewColumn {
1665+ preferredWidth: units.gu(15)
1666+ }
1667+ ViewColumn {
1668+ fillWidth: true
1669+ minimumWidth: units.gu(15)
1670+ }
1671+ },
1672+ SplitViewLayout {
1673+ when: layout.when == 2
1674+ ViewColumn {
1675+ preferredWidth: units.gu(30)
1676+ minimumWidth: units.gu(30)
1677+ }
1678+ ViewColumn {
1679+ fillWidth: true
1680+ minimumWidth: units.gu(40)
1681+ }
1682+ }
1683+ ]
1684+
1685+ Rectangle {
1686+ objectName: "column" + SplitView.column
1687+ color: UbuntuColors.red
1688+ height: parent.height
1689+ width: units.gu(90)
1690+ Label { text: parent.width }
1691+ }
1692+ Rectangle {
1693+ objectName: "column" + SplitView.column
1694+ color: UbuntuColors.green
1695+ height: parent.height
1696+ Label { text: parent.width }
1697+ }
1698+ Rectangle {
1699+ objectName: "column" + SplitView.column
1700+ color: UbuntuColors.blue
1701+ height: parent.height
1702+ Label { text: parent.width }
1703+ }
1704+ Rectangle {
1705+ objectName: "column" + SplitView.column
1706+ color: UbuntuColors.ash
1707+ height: parent.height
1708+ Label { text: parent.width }
1709+ }
1710+ Rectangle {
1711+ objectName: "column" + SplitView.column
1712+ color: "pink"
1713+ height: parent.height
1714+ width: units.gu(30)
1715+ Label { text: parent.width }
1716+ }
1717+ }
1718+ }
1719+
1720+ Component {
1721+ id: defaults
1722+ SplitView {
1723+ }
1724+ }
1725+
1726+ Sections {
1727+ id: sections
1728+ actions: [
1729+ Action {
1730+ text: "4 columns"
1731+ onTriggered: testLoader.item.when = 1
1732+ },
1733+ Action {
1734+ text: "2 columns"
1735+ onTriggered: testLoader.item.when = 2
1736+ }
1737+ ]
1738+ }
1739+
1740+ Loader {
1741+ id: testLoader
1742+ anchors {
1743+ fill: parent
1744+ topMargin: sections.height
1745+ }
1746+ sourceComponent: testLayout
1747+ }
1748+
1749+ UbuntuTestCase {
1750+ when: windowShown
1751+
1752+ readonly property real defaultSpacing: units.dp(4)
1753+
1754+ function loadTest(testCase) {
1755+ testLoader.asynchronous = false;
1756+ testLoader.sourceComponent = testCase;
1757+ tryCompare(testLoader, "status", Loader.Ready)
1758+ verify(testLoader.item);
1759+ waitForRendering(testLoader.item);
1760+ return testLoader.item;
1761+ }
1762+
1763+ SignalSpy {
1764+ id: resizeSpy
1765+ signalName: "preferredWidthChanged"
1766+ }
1767+
1768+ function cleanup() {
1769+ testLoader.sourceComponent = null;
1770+ resizeSpy.target = null;
1771+ resizeSpy.clear();
1772+ }
1773+
1774+ function initTestCase() {
1775+ // defaults
1776+ var defs = loadTest(defaults);
1777+
1778+ compare(defs.spacing, defaultSpacing);
1779+ compare(defs.layouts.length, 0);
1780+ compare(defs.activeLayout, null);
1781+ compare(defs.handleDelegate, null);
1782+
1783+ // cleanup is not called after initTestCase()
1784+ testLoader.sourceComponent = null;
1785+ }
1786+
1787+ function test_visible_columns_data() {
1788+ return [
1789+ {tag: "4 columns from 5", when: 1, visible: [true, true, true, true, false]},
1790+ {tag: "2 columns from 5", when: 2, visible: [true, true, false, false, false]},
1791+ {tag: "0 columns from 5", when: 0, visible: [false, false, false, false, false]},
1792+ ];
1793+ }
1794+ function test_visible_columns(data) {
1795+ var test = loadTest(testLayout);
1796+ test.when = data.when;
1797+ for (var i in data.visible) {
1798+ compare(findChild(test, "column" + i).visible, data.visible[i]);
1799+ }
1800+ }
1801+
1802+ function test_column_width_data() {
1803+ var fillWidth4 = (main.width - units.gu(10) - units.gu(15) - 3 * defaultSpacing) / 2;
1804+ var fillWidth2 = main.width - units.gu(30) - defaultSpacing;
1805+ return [
1806+ {tag: "4 columns from 5", when: 1, width: [units.gu(10), fillWidth4, units.gu(15), fillWidth4, units.gu(30)]},
1807+ // the columns from index 2 onwards use the 4-column configuration values
1808+ {tag: "2 columns from 5", when: 2, width: [units.gu(30), fillWidth2, units.gu(15), fillWidth4, units.gu(30)]},
1809+ {tag: "0 columns from 5", when: 2, width: [units.gu(30), fillWidth2, units.gu(15), fillWidth4, units.gu(30)]},
1810+ ];
1811+ }
1812+ function test_column_width(data) {
1813+ var test = loadTest(testLayout);
1814+ test.when = data.when;
1815+ for (var i in data.width) {
1816+ compare(findChild(test, "column" + i).width, data.width[i], "column width at index " + i + " differs");
1817+ }
1818+ }
1819+
1820+ function test_column_minmax_data() {
1821+ var fillWidth4 = (main.width - units.gu(10) - units.gu(15)) / 2;
1822+ var fillWidth2 = main.width - units.gu(30);
1823+ return [
1824+ {tag: "4 columns from 5", when: 1
1825+ , min: [0, units.gu(10), 0, units.gu(15), undefined]
1826+ , max: [units.gu(100), units.gu(150), Number.MAX_VALUE, Number.MAX_VALUE, undefined]
1827+ },
1828+ {tag: "2 columns from 5", when: 2
1829+ , min: [units.gu(30), units.gu(40), undefined, undefined, undefined]
1830+ , max: [Number.MAX_VALUE, Number.MAX_VALUE, undefined, undefined, undefined]
1831+ },
1832+ {tag: "0 columns from 5", when: 0
1833+ , min: [undefined, undefined, undefined, undefined, undefined]
1834+ , max: [undefined, undefined, undefined, undefined, undefined]
1835+ },
1836+ ];
1837+ }
1838+ function test_column_minmax(data) {
1839+ var test = loadTest(testLayout);
1840+ test.when = data.when;
1841+ for (var i in data.min) {
1842+ if (data.min[i] == undefined) {
1843+ // the attached config should be null
1844+ verify(!findChild(test, "column" + i).SplitView.columnConfig);
1845+ } else {
1846+ compare(findChild(test, "column" + i).SplitView.columnConfig.minimumWidth, data.min[i], "column minimumWidth at index " + i + " differs");
1847+ compare(findChild(test, "column" + i).SplitView.columnConfig.maximumWidth, data.max[i], "column maximumWidth at index " + i + " differs");
1848+ }
1849+ }
1850+ }
1851+
1852+ // resize
1853+ function test_resize_column() {
1854+ var test = loadTest(testLayout);
1855+ // move the mouse to the resizer handler
1856+ var column0 = findChild(test, "column0");
1857+ verify(column0);
1858+ resizeSpy.target = column0.SplitView.columnConfig;
1859+ // aim to the resize handle
1860+ mouseDrag(column0, column0.width + test.spacing/2, column0.height / 2, units.gu(10), 0);
1861+ resizeSpy.wait();
1862+ }
1863+ function test_resize_below_minimum() {
1864+ var test = loadTest(testLayout);
1865+ // move the mouse to the resizer handler
1866+ var column1 = findChild(test, "column1");
1867+ verify(column1);
1868+ resizeSpy.target = column1.SplitView.columnConfig;
1869+ // aim to the resize handle, and move large enough so the resize is long enough
1870+ mouseDrag(column1, column1.width + test.spacing/2, column1.height / 2, -main.width, 0);
1871+ resizeSpy.wait();
1872+ compare(column1.width, column1.SplitView.columnConfig.minimumWidth);
1873+ }
1874+
1875+ // failure guards
1876+ Component {
1877+ id: badHandle
1878+ QtObject {}
1879+ }
1880+ SplitView {
1881+ id: bad
1882+ layouts: SplitViewLayout {
1883+ when: true
1884+ ViewColumn {
1885+ preferredWidth: units.gu(10)
1886+ }
1887+ ViewColumn {
1888+ preferredWidth: units.gu(10)
1889+ }
1890+ }
1891+ Rectangle {
1892+ height: bad.height
1893+ }
1894+ }
1895+ function test_invalid_handle() {
1896+ ignoreWarning(warningFormat(282, 9, "QML SplitView: handle delegate not an Item"));
1897+ bad.handleDelegate = badHandle;
1898+ }
1899+
1900+ function test_minimum_greater_than_maximum_value() {
1901+ ignoreWarning(warningFormat(57, 21, "QML ViewColumn: minimumWidth is greater than maximumWidth"));
1902+ var test = loadTest(testLayout);
1903+ test.layouts[0].columns[0].minimumWidth = test.layouts[0].columns[0].maximumWidth + 1;
1904+ }
1905+
1906+ function test_invalid_minimum_value() {
1907+ ignoreWarning(warningFormat(57, 21, "QML ViewColumn: minimumWidth cannot be a negative value"));
1908+ var test = loadTest(testLayout);
1909+ test.layouts[0].columns[0].minimumWidth = -1;
1910+ }
1911+
1912+ function test_maximum_smaller_than_minimum_value() {
1913+ ignoreWarning(warningFormat(61, 21, "QML ViewColumn: maximumWidth is smaller than minimumWidth"));
1914+ var test = loadTest(testLayout);
1915+ test.layouts[0].columns[1].maximumWidth = test.layouts[0].columns[1].minimumWidth - 1;
1916+ }
1917+
1918+ function test_invalid_maximum_value() {
1919+ ignoreWarning(warningFormat(61, 21, "QML ViewColumn: maximumWidth cannot be a negative value"));
1920+ var test = loadTest(testLayout);
1921+ test.layouts[0].columns[1].maximumWidth = -1;
1922+ }
1923+
1924+ function test_preferred_width_clamps_to_minimum() {
1925+ var test = loadTest(testLayout);
1926+ // turn fillWidth off
1927+ test.layouts[0].columns[1].fillWidth = false;
1928+ // minimum
1929+ test.layouts[0].columns[1].preferredWidth = test.layouts[0].columns[1].minimumWidth - 1;
1930+ compare(test.layouts[0].columns[1].preferredWidth, test.layouts[0].columns[1].minimumWidth);
1931+ }
1932+
1933+ function test_preferred_width_clamps_to_maximum() {
1934+ var test = loadTest(testLayout);
1935+ // turn fillWidth off
1936+ test.layouts[0].columns[1].fillWidth = false;
1937+ // clamps to max
1938+ test.layouts[0].columns[1].preferredWidth = test.layouts[0].columns[1].maximumWidth + 1;
1939+ compare(test.layouts[0].columns[1].preferredWidth, test.layouts[0].columns[1].maximumWidth);
1940+ }
1941+
1942+ function test_changing_minimum_clamps_preferredWidth() {
1943+ var test = loadTest(testLayout);
1944+ var config = test.layouts[0].columns[0];
1945+ verify(config.preferredWidth < config.maximumWidth - 10);
1946+ config.minimumWidth = config.maximumWidth - 10;
1947+ compare(config.preferredWidth, config.minimumWidth);
1948+ }
1949+
1950+ function test_changing_maximum_clamps_preferredWidth() {
1951+ var test = loadTest(testLayout);
1952+ var config = test.layouts[0].columns[0];
1953+ config.maximumWidth = config.preferredWidth - 10;
1954+ compare(config.preferredWidth, config.maximumWidth);
1955+ }
1956+
1957+ // test layout change keeps value
1958+ function test_resize_and_change_layouts() {
1959+ var test = loadTest(testLayout);
1960+ // move the mouse to the resizer handler
1961+ var column0 = findChild(test, "column0");
1962+ verify(column0);
1963+ resizeSpy.target = column0.SplitView.columnConfig;
1964+ // aim to the resize handle
1965+ mouseDrag(column0, column0.width + test.spacing/2, column0.height / 2, units.gu(10), 0);
1966+ resizeSpy.wait();
1967+
1968+ var prevWidth = column0.width;
1969+
1970+ // change layout
1971+ test.when = 2;
1972+ waitForRendering(test);
1973+ verify(prevWidth != column0.width);
1974+
1975+ // restore the previous layout
1976+ test.when = 1;
1977+ waitForRendering(test);
1978+ compare(prevWidth, column0.width);
1979+ }
1980+ }
1981+}
1982
1983=== added file 'tests/unit/visual/tst_splitview_page.13.qml'
1984--- tests/unit/visual/tst_splitview_page.13.qml 1970-01-01 00:00:00 +0000
1985+++ tests/unit/visual/tst_splitview_page.13.qml 2016-08-26 05:41:49 +0000
1986@@ -0,0 +1,143 @@
1987+/*
1988+ * Copyright 2016 Canonical Ltd.
1989+ *
1990+ * This program is free software; you can redistribute it and/or modify
1991+ * it under the terms of the GNU Lesser General Public License as published by
1992+ * the Free Software Foundation; version 3.
1993+ *
1994+ * This program is distributed in the hope that it will be useful,
1995+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1996+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1997+ * GNU Lesser General Public License for more details.
1998+ *
1999+ * You should have received a copy of the GNU Lesser General Public License
2000+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2001+ */
2002+
2003+import QtQuick 2.4
2004+import Ubuntu.Test 1.3
2005+import Ubuntu.Components 1.3
2006+import Ubuntu.Components.Labs 1.0
2007+
2008+Item {
2009+ id: main
2010+ width: units.gu(200)
2011+ height: units.gu(70)
2012+
2013+ Component {
2014+ id: testLayout
2015+ SplitView {
2016+ id: splitView
2017+ anchors.fill: parent
2018+ property int columns: 4
2019+
2020+ layouts: [
2021+ SplitViewLayout {
2022+ id: mainLayout
2023+ when: splitView.columns == 4
2024+ ViewColumn {
2025+ preferredWidth: units.gu(40)
2026+ maximumWidth: units.gu(100)
2027+ }
2028+ ViewColumn {
2029+ fillWidth: true
2030+ minimumWidth: units.gu(10)
2031+ maximumWidth: units.gu(150)
2032+ }
2033+ ViewColumn {
2034+ preferredWidth: units.gu(50)
2035+ }
2036+ ViewColumn {
2037+ fillWidth: true
2038+ minimumWidth: units.gu(15)
2039+ }
2040+ },
2041+ SplitViewLayout {
2042+ when: splitView.columns == 2
2043+ ViewColumn {
2044+ preferredWidth: units.gu(10)
2045+ minimumWidth: units.gu(30)
2046+ }
2047+ ViewColumn {
2048+ fillWidth: true
2049+ minimumWidth: units.gu(40)
2050+ }
2051+ }
2052+ ]
2053+
2054+ Repeater {
2055+ objectName: "ignored"
2056+ model: splitView.columns
2057+ Page {
2058+ objectName: "column" + index
2059+ height: splitView.height
2060+ header: PageHeader {
2061+ title: "Column #" + index
2062+ }
2063+
2064+ Rectangle {
2065+ color: UbuntuColors.red
2066+ anchors.fill: parent
2067+ }
2068+ }
2069+ }
2070+ }
2071+ }
2072+
2073+ Sections {
2074+ id: sections
2075+ actions: [
2076+ Action {
2077+ text: "4 columns"
2078+ onTriggered: testLoader.item.columns = 4
2079+ },
2080+ Action {
2081+ text: "2 columns"
2082+ onTriggered: testLoader.item.columns = 2
2083+ }
2084+ ]
2085+ }
2086+
2087+ Loader {
2088+ id: testLoader
2089+ anchors {
2090+ fill: parent
2091+ topMargin: sections.height
2092+ }
2093+ asynchronous: false
2094+ sourceComponent: testLayout
2095+ }
2096+
2097+ UbuntuTestCase {
2098+ when: windowShown
2099+
2100+ function cleanup() {
2101+ testLoader.sourceComponent = null;
2102+ }
2103+
2104+ function loadTest(testCase) {
2105+ testLoader.asynchronous = false;
2106+ testLoader.sourceComponent = testCase;
2107+ tryCompare(testLoader, "status", Loader.Ready)
2108+ verify(testLoader.item);
2109+ waitForRendering(testLoader.item);
2110+ return testLoader.item;
2111+ }
2112+
2113+ function test_patched_page_anchoring() {
2114+ var test = loadTest(testLayout);
2115+ // we cannot set expectFail() on ignoreWarning, therefore we must check
2116+ // if the Pages are having proper widths and different x values set
2117+ var prevX = -1;
2118+ for (var i in test.children) {
2119+ var child = test.children[i];
2120+ if (child.objectName != "ignored") {
2121+ compare(child.objectName, "column" + i);
2122+ verify(child.width > 0, "column width not set");
2123+ verify(prevX < child.x, "column not positioned properly");
2124+ prevX = child.x;
2125+ }
2126+ }
2127+ }
2128+ }
2129+}
2130
2131=== added file 'tests/unit/visual/tst_splitview_repeater.13.qml'
2132--- tests/unit/visual/tst_splitview_repeater.13.qml 1970-01-01 00:00:00 +0000
2133+++ tests/unit/visual/tst_splitview_repeater.13.qml 2016-08-26 05:41:49 +0000
2134@@ -0,0 +1,116 @@
2135+/*
2136+ * Copyright 2016 Canonical Ltd.
2137+ *
2138+ * This program is free software; you can redistribute it and/or modify
2139+ * it under the terms of the GNU Lesser General Public License as published by
2140+ * the Free Software Foundation; version 3.
2141+ *
2142+ * This program is distributed in the hope that it will be useful,
2143+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2144+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2145+ * GNU Lesser General Public License for more details.
2146+ *
2147+ * You should have received a copy of the GNU Lesser General Public License
2148+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2149+ */
2150+
2151+import QtQuick 2.4
2152+import Ubuntu.Test 1.3
2153+import Ubuntu.Components 1.3
2154+import Ubuntu.Components.Labs 1.0
2155+
2156+Item {
2157+ id: main
2158+ width: units.gu(200)
2159+ height: units.gu(70)
2160+
2161+ Sections {
2162+ id: sections
2163+ actions: [
2164+ Action {
2165+ text: "4 columns"
2166+ onTriggered: splitView.columns = 4
2167+ },
2168+ Action {
2169+ text: "2 columns"
2170+ onTriggered: splitView.columns = 2
2171+ }
2172+ ]
2173+ }
2174+
2175+ SplitView {
2176+ id: splitView
2177+ anchors {
2178+ fill: parent
2179+ topMargin: sections.height
2180+ }
2181+ property int columns: 4
2182+
2183+ layouts: [
2184+ SplitViewLayout {
2185+ id: mainLayout
2186+ when: splitView.columns == 4
2187+ ViewColumn {
2188+ preferredWidth: units.gu(40)
2189+ maximumWidth: units.gu(100)
2190+ }
2191+ ViewColumn {
2192+ fillWidth: true
2193+ minimumWidth: units.gu(10)
2194+ maximumWidth: units.gu(150)
2195+ }
2196+ ViewColumn {
2197+ preferredWidth: units.gu(50)
2198+ }
2199+ ViewColumn {
2200+ fillWidth: true
2201+ minimumWidth: units.gu(15)
2202+ }
2203+ },
2204+ SplitViewLayout {
2205+ when: splitView.columns == 2
2206+ ViewColumn {
2207+ preferredWidth: units.gu(10)
2208+ minimumWidth: units.gu(30)
2209+ }
2210+ ViewColumn {
2211+ fillWidth: true
2212+ minimumWidth: units.gu(40)
2213+ }
2214+ }
2215+ ]
2216+
2217+ Repeater {
2218+ objectName: "ignored"
2219+ model: splitView.columns
2220+ Rectangle {
2221+ objectName: "column" + index
2222+ color: UbuntuColors.red
2223+ height: splitView.height
2224+ }
2225+ }
2226+ }
2227+
2228+ UbuntuTestCase {
2229+ when: windowShown
2230+
2231+ function cleanup() {
2232+ splitView.columns = 0;
2233+ }
2234+
2235+ function test_children_data() {
2236+ return [
2237+ {tag: "4 columns", columns: 4, childCount: 5, children: ["column0", "column1", "column2", "column3", "ignored"]},
2238+ {tag: "2 columns", columns: 2, childCount: 3, children: ["column0", "column1", "ignored"]},
2239+ {tag: "0 columns", columns: 0, childCount: 1, children: ["ignored"]},
2240+ ];
2241+ }
2242+ function test_children(data) {
2243+ splitView.columns = data.columns;
2244+ compare(splitView.children.length, data.childCount);
2245+ for (var i in splitView.children) {
2246+ compare(splitView.children[i].objectName, data.children[i]);
2247+ }
2248+ }
2249+ }
2250+}

Subscribers

People subscribed via source and target branches