2
* Copyright 2013 Canonical Ltd.
4
* This program is free software; you can redistribute it and/or modify
5
* it under the terms of the GNU Lesser General Public License as published by
6
* the Free Software Foundation; version 3.
8
* This program is distributed in the hope that it will be useful,
9
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
* GNU Lesser General Public License for more details.
13
* You should have received a copy of the GNU Lesser General Public License
14
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16
* Author: Zsombor Egri <zsombor.egri@canonical.com>
19
#include "ullayouts.h"
20
#include "ullayouts_p.h"
21
#include "ulitemlayout.h"
22
#include "ulconditionallayout.h"
23
#include "propertychanges_p.h"
24
#include <QtQml/QQmlInfo>
25
#include <QtQuick/private/qquickitem_p.h>
27
ULLayoutsPrivate::ULLayoutsPrivate(ULLayouts *qq)
28
: QQmlIncubator(Asynchronous)
30
, currentLayoutItem(0)
31
, previousLayoutItem(0)
32
, contentItem(new QQuickItem)
33
, currentLayoutIndex(-1)
36
// hidden container for the components that are not laid out
37
// any component not subject of layout is reparented into this component
38
contentItem->setParent(qq);
39
contentItem->setParentItem(qq);
43
/******************************************************************************
44
* QQmlListProperty functions
46
void ULLayoutsPrivate::append_layout(QQmlListProperty<ULConditionalLayout> *list, ULConditionalLayout *layout)
48
ULLayouts *_this = static_cast<ULLayouts*>(list->object);
50
layout->setParent(_this);
51
_this->d_ptr->layouts.append(layout);
55
int ULLayoutsPrivate::count_layouts(QQmlListProperty<ULConditionalLayout> *list)
57
ULLayouts *_this = static_cast<ULLayouts*>(list->object);
58
return _this->d_ptr->layouts.count();
61
ULConditionalLayout *ULLayoutsPrivate::at_layout(QQmlListProperty<ULConditionalLayout> *list, int index)
63
ULLayouts *_this = static_cast<ULLayouts*>(list->object);
64
return _this->d_ptr->layouts.at(index);
67
void ULLayoutsPrivate::clear_layouts(QQmlListProperty<ULConditionalLayout> *list)
69
ULLayouts *_this = static_cast<ULLayouts*>(list->object);
70
_this->d_ptr->layouts.clear();
74
/******************************************************************************
75
* ULLayoutsPrivate also acts as QQmlIncubator for the dynamically created layouts.
78
void ULLayoutsPrivate::setInitialState(QObject *object)
81
// object context's parent is the creation context; link it to the object so we
82
// delete them together
83
qmlContext(object)->parentContext()->setParent(object);
86
QQuickItem *item = static_cast<QQuickItem*>(object);
87
// set disabled and invisible, and set its parent as last action
88
item->setVisible(false);
92
* Called upon QQmlComponent::create() to notify the status of the component
95
void ULLayoutsPrivate::statusChanged(Status status)
98
if (status == Ready) {
100
previousLayoutItem = currentLayoutItem;
103
currentLayoutItem = qobject_cast<QQuickItem*>(object());
104
Q_ASSERT(currentLayoutItem);
106
//reparent components to be laid out
108
// set parent item, then enable and show layout
109
changes.addChange(new ParentChange(currentLayoutItem, q, false));
111
// hide default layout, then show the new one
112
// there's no need to queue these property changes as we do not need
113
// to back up their previosus states
114
contentItem->setVisible(false);
115
currentLayoutItem->setVisible(true);
118
// clear previous layout
119
delete previousLayoutItem;
120
previousLayoutItem = 0;
122
Q_EMIT q->currentLayoutChanged();
123
} else if (status == Error) {
129
* Re-parent items to the new layout.
131
void ULLayoutsPrivate::reparentItems()
133
// create copy of items list, to keep track of which ones we change
134
LaidOutItemsMap unusedItems = itemsToLayout;
136
// iterate through the Layout definition to find containers - ItemLayout items
137
QList<ULItemLayout*> containers = collectContainers(currentLayoutItem);
139
Q_FOREACH(ULItemLayout *container, containers) {
140
reparentToItemLayout(unusedItems, container);
144
QList<ULItemLayout*> ULLayoutsPrivate::collectContainers(QQuickItem *fromItem)
146
QList<ULItemLayout*> result;
147
// check first if the fromItem is also a container
148
ULItemLayout *container = qobject_cast<ULItemLayout*>(fromItem);
150
result.append(container);
153
// loop through children but exclude nested Layouts
154
QList<QQuickItem*> children = fromItem->childItems();
155
Q_FOREACH(QQuickItem *child, children) {
156
if (qobject_cast<ULLayouts*>(child)) {
159
result.append(collectContainers(child));
165
* Re-parent to ItemLayout.
167
void ULLayoutsPrivate::reparentToItemLayout(LaidOutItemsMap &map, ULItemLayout *fragment)
169
QString itemName = fragment->item();
170
if (itemName.isEmpty()) {
171
warning(fragment, "item not specified for ItemLayout");
175
QQuickItem *item = map.value(itemName);
177
warning(fragment, "item \"" + itemName
178
+ "\" not specified or has been specified for layout by"
179
" more than one active ItemLayout");
183
// the component fills the parent
184
changes.addParentChange(item, fragment, true);
185
changes.addChange(new AnchorChange(item, "fill", fragment));
186
changes.addChange(new PropertyChange(item, "anchors.margins", 0));
187
changes.addChange(new PropertyChange(item, "anchors.leftMargin", 0));
188
changes.addChange(new PropertyChange(item, "anchors.topMargin", 0));
189
changes.addChange(new PropertyChange(item, "anchors.rightMargin", 0));
190
changes.addChange(new PropertyChange(item, "anchors.bottomMargin", 0));
192
changes.addChange(new PropertyBackup(item, "width"));
193
changes.addChange(new PropertyBackup(item, "height"));
194
// break and backup anchors
195
changes.addChange(new AnchorBackup(item));
197
// remove from unused ones
198
map.remove(itemName);
202
* Validates the declared conditional layouts by checking whether they have name
203
* property set and whether the value set is unique, and whether the conditional
204
* layout has container defined.
206
void ULLayoutsPrivate::validateConditionalLayouts()
211
for (int i = 0; i < layouts.count(); i++) {
212
ULConditionalLayout *layout = layouts[i];
214
error(q, "Error in layout definitions!");
218
if (layout->layoutName().isEmpty()) {
219
warning(layout, "No name specified for layout. ConditionalLayout cannot be activated without name.");
222
if (names.contains(layout->layoutName())) {
223
warning(layout, "layout name \"" + layout->layoutName()
224
+ "\" not unique. Layout may not behave as expected.");
227
if (!layout->layout()) {
228
error(layout, "no container specified for layout \"" + layout->layoutName() +
229
"\". ConditionalLayout cannot be activated without a container.");
237
* Collect items to be laid out.
239
void ULLayoutsPrivate::getLaidOutItems(QQuickItem *container)
241
Q_FOREACH(QQuickItem *child, container->childItems()) {
242
// skip nested layouts
243
if (qobject_cast<ULLayouts*>(child)) {
246
ULLayoutsAttached *marker = qobject_cast<ULLayoutsAttached*>(
247
qmlAttachedPropertiesObject<ULLayouts>(child, false));
248
if (marker && !marker->item().isEmpty()) {
249
itemsToLayout.insert(marker->item(), child);
251
// continue to search in between the child's children
252
getLaidOutItems(child);
258
* Apply layout change. The new layout creation will be completed in statusChange().
260
void ULLayoutsPrivate::reLayout()
262
if (!ready || (currentLayoutIndex < 0)) {
265
if (!layouts[currentLayoutIndex]->layout()) {
273
// clear the incubator before using it
275
QQmlComponent *component = layouts[currentLayoutIndex]->layout();
276
// create using incubation as it may be created asynchronously,
277
// case when the attached properties are not yet enumerated
279
QQmlContext *context = new QQmlContext(qmlContext(q), q);
280
component->create(*this, context);
284
* Updates the current layout.
286
void ULLayoutsPrivate::updateLayout()
292
// go through conditions and re-parent for the first valid one
293
for (int i = 0; i < layouts.count(); i++) {
294
ULConditionalLayout *layout = layouts[i];
295
if (!layout->layout()) {
296
warning(layout, "Cannot activate layout \"" + layout->layoutName() +
297
"\" with no container specified. Falling back to default layout.");
300
if (!layout->layoutName().isEmpty() && layout->when() && layout->when()->evaluate().toBool()) {
301
if (currentLayoutIndex == i) {
304
currentLayoutIndex = i;
310
// check if we need to switch back to default layout
311
if (currentLayoutIndex >= 0) {
312
// revert and clear changes
315
// make contentItem visible
317
contentItem->setVisible(true);
318
delete currentLayoutItem;
319
currentLayoutItem = 0;
320
currentLayoutIndex = -1;
322
Q_EMIT q->currentLayoutChanged();
326
void ULLayoutsPrivate::error(QObject *item, const QString &message)
328
qmlInfo(item) << "ERROR: " << message;
329
QQmlEngine *engine = qmlEngine(item);
335
void ULLayoutsPrivate::error(QObject *item, const QList<QQmlError> &errors)
337
qmlInfo(item, errors);
338
QQmlEngine *engine = qmlEngine(item);
344
void ULLayoutsPrivate::warning(QObject *item, const QString &message)
346
qmlInfo(item) << "WARNING: " << message;
352
* \instantiates ULLayouts
353
* \inqmlmodule Ubuntu.Layouts 1.0
354
* \ingroup ubuntu-layouts
355
* \brief The Layouts component allows one to specify multiple different layouts for a
356
* fixed set of Items, and applies the desired layout to those Items.
358
* Layouts is a layout block component incorporating layout definitions and
359
* components to lay out. The layouts are defined in the \l layouts property, which
360
* is a list of ConditionalLayout components, each declaring the sizes and positions
361
* of the components specified to be laid out.
367
* ConditionalLayout {
369
* when: layouts.width > units.gu(60) && layouts.width <= units.gu(100)
371
* anchors.fill: parent
375
* ConditionalLayout {
377
* when: layouts.width > units.gu(100)
379
* anchors.fill: parent
380
* contentHeight: column.childrenRect.height
391
* The components to be laid out must be declared as children of the Layouts component,
392
* each set an attached property "Layouts.item" to be a unique string.
398
* ConditionalLayout {
400
* when: layouts.width > units.gu(60) && layouts.width <= units.gu(100)
402
* anchors.fill: parent
406
* ConditionalLayout {
408
* when: layouts.width > units.gu(100)
410
* anchors.fill: parent
411
* contentHeight: column.childrenRect.height
421
* anchors.fill: parent
424
* Layouts.item: "item1"
428
* Layouts.item: "item2"
434
* The layout of the children of Layouts is considered the default layout, i.e.
435
* currentLayout is an empty string. So in the above example, the buttons arranged
436
* in a row is the default layout.
438
* The layouts defined by ConditionalLayout components are created and activated
439
* when at least one of the layout's condition is evaluated to true. In which
440
* case components marked for layout are re-parented to the components defined
441
* to lay out those defined in the ConditionalLayout. In case multiple conditions
442
* are evaluated to true, the first one in the list will be activated. The deactivated
443
* layout is destroyed, exception being the default layout, which is kept in memory for
444
* the entire lifetime of the Layouts component.
446
* Upon activation, the created component fills in the entire layout block.
452
* ConditionalLayout {
454
* when: layouts.width > units.gu(60) && layouts.width <= units.gu(100)
456
* anchors.fill: parent
465
* ConditionalLayout {
467
* when: layouts.width > units.gu(100)
469
* anchors.fill: parent
470
* contentHeight: column.childrenRect.height
485
* anchors.fill: parent
488
* Layouts.item: "item1"
492
* Layouts.item: "item2"
498
* Conditional layouts must be named in order to be activatable. These names (strings)
499
* should be unique within a Layouts item and can be used to identify changes in
500
* between layouts in scripts, so additional layout specific customization on laid
501
* out items can be done. The current layout is presented by the currentLayout
504
* Extending the previous example by changing the button color to green when the
505
* current layout is "column", the code would look as follows:
510
* ConditionalLayout {
512
* when: layouts.width > units.gu(60) && layouts.width <= units.gu(100)
514
* anchors.fill: parent
523
* ConditionalLayout {
525
* when: layouts.width > units.gu(100)
527
* anchors.fill: parent
528
* contentHeight: column.childrenRect.height
543
* anchors.fill: parent
546
* Layouts.item: "item1"
547
* color: (layouts.currentLayout === "column") ? "green" : "gray"
551
* Layouts.item: "item2"
552
* color: (layouts.currentLayout === "column") ? "green" : "gray"
559
ULLayouts::ULLayouts(QQuickItem *parent):
561
d_ptr(new ULLayoutsPrivate(this))
565
ULLayouts::~ULLayouts()
569
ULLayoutsAttached * ULLayouts::qmlAttachedProperties(QObject *owner)
571
return new ULLayoutsAttached(owner);
574
void ULLayouts::componentComplete()
576
QQuickItem::componentComplete();
579
d->validateConditionalLayouts();
580
d->getLaidOutItems(d->contentItem);
584
void ULLayouts::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
587
QQuickItem::geometryChanged(newGeometry, oldGeometry);
588
// simply update the container's width/height to the new width/height
589
d->contentItem->setSize(newGeometry.size());
593
* \qmlproperty string Layouts::currentLayout
594
* The property holds the active layout name. The default layout is identified
595
* by an empty string. This property can be used for additional customization
596
* of the components which are not supported by the layouting.
599
QString ULLayouts::currentLayout() const
601
Q_D(const ULLayouts);
602
return d->currentLayoutIndex >= 0 ? d->layouts[d->currentLayoutIndex]->layoutName() : QString();
607
* Provides a list of layouts for internal use.
609
QList<ULConditionalLayout*> ULLayouts::layoutList()
617
* Returns the contentItem for internal use.
619
QQuickItem *ULLayouts::contentItem() const
621
Q_D(const ULLayouts);
622
return d->contentItem;
627
* \qmlproperty list<ConditionalLayout> Layouts::layouts
628
* The property holds the list of different ConditionalLayout elements.
630
QQmlListProperty<ULConditionalLayout> ULLayouts::layouts()
633
return QQmlListProperty<ULConditionalLayout>(this, &(d->layouts),
634
&ULLayoutsPrivate::append_layout,
635
&ULLayoutsPrivate::count_layouts,
636
&ULLayoutsPrivate::at_layout,
637
&ULLayoutsPrivate::clear_layouts);
642
* Overrides the default data property.
644
QQmlListProperty<QObject> ULLayouts::data()
647
return QQuickItemPrivate::get(d->contentItem)->data();
652
* Overrides the default children property.
654
QQmlListProperty<QQuickItem> ULLayouts::children()
657
return QQuickItemPrivate::get(d->contentItem)->children();
661
#include "moc_ullayouts.cpp"