From 6758f510dc70551114366f8e76887a7d585290c4 Mon Sep 17 00:00:00 2001
From: Jan Arve Saether <jan-arve.saether@digia.com>
Date: Wed, 13 Mar 2013 15:01:40 +0100
Subject: [PATCH] Add support for auto-positioning of items in a grid layout

Items will be laid sequentially in the grid, either in LeftToRight
mode or TopToBottom mode (depending on the value of the flow property)

Change-Id: Ib080517260ac4519171e77abd533cad3f306f70e
Reviewed-by: Jens Bache-Wiig <jens.bache-wiig@digia.com>
---
 examples/basiclayouts/main.qml              |  67 ++--
 src/layouts/qquicklayout.cpp                |  16 +-
 src/layouts/qquicklayout_p.h                |  10 +-
 src/layouts/qquicklinearlayout.cpp          | 235 ++++++++++----
 src/layouts/qquicklinearlayout_p.h          |  29 +-
 tests/auto/controls/controls.pro            |   1 +
 tests/auto/controls/data/tst_gridlayout.qml | 326 ++++++++++++++++++++
 tests/manual/Layout.qml                     |  61 ++++
 8 files changed, 635 insertions(+), 110 deletions(-)
 create mode 100644 tests/auto/controls/data/tst_gridlayout.qml

diff --git a/examples/basiclayouts/main.qml b/examples/basiclayouts/main.qml
index d2b37204a..fd107cc17 100644
--- a/examples/basiclayouts/main.qml
+++ b/examples/basiclayouts/main.qml
@@ -58,7 +58,6 @@ ApplicationWindow {
         id: mainLayout
         anchors.fill: parent
         anchors.margins: margin
-        spacing: 6
         GroupBox {
             id: rowBox
             title: "Row layout"
@@ -68,22 +67,13 @@ ApplicationWindow {
 
             RowLayout {
                 id: rowLayout
-                spacing: 6
                 anchors.fill: parent
-                Button {
-                    text: "Button 1"
-                }
-                Button {
-                    text: "Button 2"
-                }
-                Button {
-                    text: "Button 3"
+                TextField {
+                    placeholderText: "This wants to grow horizontally"
+                    Layout.fillWidth: true
                 }
                 Button {
-                    text: "Button 4"
-                }
-                Item {
-                    Layout.fillWidth: true
+                    text: "Button"
                 }
             }
         }
@@ -97,49 +87,30 @@ ApplicationWindow {
 
             GridLayout {
                 id: gridLayout
-                rowSpacing: 6
-                columnSpacing: 6
+                rows: 3
+                flow: GridLayout.TopToBottom
                 anchors.fill: parent
-                Label {
-                    text: "Line 1"
-                    Layout.row: 0
-                    Layout.column: 0
-                }
-                Label {
-                    text: "Line 2"
-                    Layout.row: 1
-                    Layout.column: 0
-                }
-                Label {
-                    text: "Line 3"
-                    Layout.row: 2
-                    Layout.column: 0
-                }
-                TextField {
-                    Layout.row: 0
-                    Layout.column: 1
-                }
-                TextField {
-                    Layout.row: 1
-                    Layout.column: 1
-                }
-                TextField {
-                    Layout.row: 2
-                    Layout.column: 1
-                }
+
+                Label { text: "Line 1" }
+                Label { text: "Line 2" }
+                Label { text: "Line 3" }
+
+                TextField { }
+                TextField { }
+                TextField { }
+
                 TextArea {
-                    text: "This widget spans over three rows in the grid layout"
-                    Layout.row: 0
-                    Layout.column: 2
+                    text: "This widget spans over three rows in the GridLayout.\n"
+                        + "All items in the GridLayout are implicitly positioned from top to bottom."
                     Layout.rowSpan: 3
                     Layout.fillHeight: true
                     Layout.fillWidth: true
                 }
             }
         }
-        TextField {
+        TextArea {
             id: t3
-            placeholderText: "This is a placeholder for a TextField"
+            text: "This fills the whole cell"
             width: 200
             height: 400
             Layout.fillHeight: true
diff --git a/src/layouts/qquicklayout.cpp b/src/layouts/qquicklayout.cpp
index 77c3d4431..68e6b6b3e 100644
--- a/src/layouts/qquicklayout.cpp
+++ b/src/layouts/qquicklayout.cpp
@@ -54,8 +54,8 @@ QQuickLayoutAttached::QQuickLayoutAttached(QObject *parent)
       m_preferredHeight(0),
       m_maximumWidth(q_declarativeLayoutMaxSize),
       m_maximumHeight(q_declarativeLayoutMaxSize),
-      m_row(0),
-      m_column(0),
+      m_row(-1),
+      m_column(-1),
       m_rowSpan(1),
       m_columnSpan(1),
       m_fillWidth(false),
@@ -149,6 +149,18 @@ void QQuickLayoutAttached::setFillHeight(bool fill)
     }
 }
 
+void QQuickLayoutAttached::setRow(int row)
+{
+    if (row >= 0 && row != m_row)
+        m_row = row;
+}
+
+void QQuickLayoutAttached::setColumn(int column)
+{
+    if (column >= 0 && column != m_column)
+        m_column = column;
+}
+
 void QQuickLayoutAttached::invalidateItem()
 {
     quickLayoutDebug() << "QQuickLayoutAttached::invalidateItem";
diff --git a/src/layouts/qquicklayout_p.h b/src/layouts/qquicklayout_p.h
index ad1f2df58..a13d79e3d 100644
--- a/src/layouts/qquicklayout_p.h
+++ b/src/layouts/qquicklayout_p.h
@@ -150,10 +150,12 @@ public:
     void setFillHeight(bool fill);
     bool isFillHeightSet() const { return m_isFillHeightSet; }
 
-    int row() const { return m_row; }
-    void setRow(int row) { m_row = row; }
-    int column() const { return m_column; }
-    void setColumn(int column) { m_column = column; }
+    int row() const { return qMax(m_row, 0); }
+    void setRow(int row);
+    bool isRowSet() const { return m_row >= 0; }
+    int column() const { return qMax(m_column, 0); }
+    void setColumn(int column);
+    bool isColumnSet() const { return m_column >= 0; }
 
     int rowSpan() const { return m_rowSpan; }
     void setRowSpan(int span) { m_rowSpan = span; }
diff --git a/src/layouts/qquicklinearlayout.cpp b/src/layouts/qquicklinearlayout.cpp
index a0cb63356..635bb576f 100644
--- a/src/layouts/qquicklinearlayout.cpp
+++ b/src/layouts/qquicklinearlayout.cpp
@@ -42,6 +42,7 @@
 #include "qquicklinearlayout_p.h"
 #include <QtCore/qnumeric.h>
 #include "qdebug.h"
+#include <limits>
 /*!
     \qmltype RowLayout
     \instantiates QQuickRowLayout
@@ -158,10 +159,7 @@ void QQuickGridLayoutBase::updateLayoutItems()
         return;
     quickLayoutDebug() << "QQuickGridLayoutBase::updateLayoutItems";
     d->engine.deleteItems();
-    foreach (QQuickItem *child,  childItems()) {
-        if (child->isVisible())
-            insertLayoutItem(child);
-    }
+    insertLayoutItems();
 
     invalidate();
     quickLayoutDebug() << "QQuickGridLayoutBase::updateLayoutItems LEAVING";
@@ -223,41 +221,6 @@ void QQuickGridLayoutBase::geometryChanged(const QRectF &newGeometry, const QRec
     rearrange(newGeometry.size());
 }
 
-void QQuickGridLayoutBase::insertLayoutItem(QQuickItem *item)
-{
-    Q_D(QQuickGridLayoutBase);
-    if (!item) {
-        qWarning("QGraphicsGridLayout::addItem: cannot add null item");
-        return;
-    }
-    QQuickLayoutAttached *info = attachedLayoutObject(item, false);
-    int row = 0;
-    int column = 0;
-    int rowSpan = 1;
-    int columnSpan = 1;
-    Qt::Alignment alignment = 0;
-    if (info) {
-        row = info->row();
-        column = info->column();
-        rowSpan = info->rowSpan();
-        columnSpan = info->columnSpan();
-    }
-    if (row < 0 || column < 0) {
-        qWarning("QQuickGridLayoutBase::insertLayoutItemAt: invalid row/column: %d",
-                 row < 0 ? row : column);
-        return;
-    }
-    if (columnSpan < 1 || rowSpan < 1) {
-        qWarning("QQuickGridLayoutBase::addItem: invalid row span/column span: %d",
-                 rowSpan < 1 ? rowSpan : columnSpan);
-        return;
-    }
-    QQuickGridLayoutItem *layoutItem = new QQuickGridLayoutItem(item, row, column, rowSpan, columnSpan, alignment);
-    d->engine.insertItem(layoutItem, -1);
-
-    setupItemLayout(item);
-}
-
 void QQuickGridLayoutBase::removeGridItem(QGridLayoutItem *gridItem)
 {
     Q_D(QQuickGridLayoutBase);
@@ -378,6 +341,168 @@ void QQuickGridLayout::setRowSpacing(qreal spacing)
     invalidate();
 }
 
+int QQuickGridLayout::columns() const
+{
+    Q_D(const QQuickGridLayout);
+    return d->columns;
+}
+
+void QQuickGridLayout::setColumns(int columns)
+{
+    Q_D(QQuickGridLayout);
+    if (d->columns == columns)
+        return;
+    d->columns = columns;
+    invalidate();
+    emit columnsChanged();
+}
+
+int QQuickGridLayout::rows() const
+{
+    Q_D(const QQuickGridLayout);
+    return d->rows;
+}
+
+void QQuickGridLayout::setRows(int rows)
+{
+    Q_D(QQuickGridLayout);
+    if (d->rows == rows)
+        return;
+    d->rows = rows;
+    invalidate();
+    emit rowsChanged();
+}
+
+QQuickGridLayout::Flow QQuickGridLayout::flow() const
+{
+    Q_D(const QQuickGridLayout);
+    return d->flow;
+}
+
+void QQuickGridLayout::setFlow(QQuickGridLayout::Flow flow)
+{
+    Q_D(QQuickGridLayout);
+    if (d->flow == flow)
+        return;
+    d->flow = flow;
+    invalidate();
+    emit flowChanged();
+}
+
+void QQuickGridLayout::insertLayoutItems()
+{
+    Q_D(QQuickGridLayout);
+
+    int nextCellPos[2] = {0,0};
+    int &nextColumn = nextCellPos[0];
+    int &nextRow = nextCellPos[1];
+
+    const int flowOrientation = flow();
+    int &flowColumn = nextCellPos[flowOrientation];
+    int &flowRow = nextCellPos[1 - flowOrientation];
+    int flowBound = (flowOrientation == QQuickGridLayout::LeftToRight) ? columns() : rows();
+
+    if (flowBound < 0)
+        flowBound = std::numeric_limits<int>::max();
+
+    foreach (QQuickItem *child,  childItems()) {
+        if (child->isVisible()) {
+            QQuickLayoutAttached *info = attachedLayoutObject(child, false);
+
+            // Will skip Repeater among other things
+            const bool skipItem = !info && (!child->width() || !child->height())
+                      && (!child->implicitWidth() || !child->implicitHeight());
+            if (skipItem)
+                continue;
+
+            int row = -1;
+            int column = -1;
+            int span[2] = {1,1};
+            int &columnSpan = span[0];
+            int &rowSpan = span[1];
+
+            bool invalidRowColumn = false;
+            if (info) {
+                if (info->isRowSet() || info->isColumnSet()) {
+                    // If row is specified and column is not specified (or vice versa),
+                    // the unspecified component of the cell position should default to 0
+                    row = column = 0;
+                    if (info->isRowSet()) {
+                        row = info->row();
+                        invalidRowColumn = row < 0;
+                    }
+                    if (info->isColumnSet()) {
+                        column = info->column();
+                        invalidRowColumn = column < 0;
+                    }
+                }
+                if (invalidRowColumn) {
+                    qWarning("QQuickGridLayoutBase::insertLayoutItems: invalid row/column: %d",
+                             row < 0 ? row : column);
+                    return;
+                }
+                rowSpan = info->rowSpan();
+                columnSpan = info->columnSpan();
+                if (columnSpan < 1 || rowSpan < 1) {
+                    qWarning("QQuickGridLayoutBase::addItem: invalid row span/column span: %d",
+                             rowSpan < 1 ? rowSpan : columnSpan);
+                    return;
+                }
+            }
+
+            Q_ASSERT(columnSpan >= 1);
+            Q_ASSERT(rowSpan >= 1);
+
+            if (row >= 0)
+                nextRow = row;
+            if (column >= 0)
+                nextColumn = column;
+
+            if (row < 0 || column < 0) {
+                /* if row or column is not specified, find next position by
+                   advancing in the flow direction until there is a cell that
+                   can accept the item.
+
+                   The acceptance rules are pretty simple, but complexity arises
+                   when an item requires several cells (due to spans):
+                   1. Check if the cells that the item will require
+                      does not extend beyond columns (for LeftToRight) or
+                      rows (for TopToBottom).
+                   2. Check if the cells that the item will require is not already
+                      taken by another item.
+                */
+                bool cellAcceptsItem;
+                while (true) {
+                    // Check if the item does not span beyond the layout bound
+                    cellAcceptsItem = (flowColumn + span[flowOrientation]) <= flowBound;
+
+                    // Check if all the required cells are not taken
+                    for (int rs = 0; cellAcceptsItem && rs < rowSpan; ++rs) {
+                        for (int cs = 0; cellAcceptsItem && cs < columnSpan; ++cs) {
+                            if (d->engine.itemAt(nextRow + rs, nextColumn + cs)) {
+                                cellAcceptsItem = false;
+                            }
+                        }
+                    }
+                    if (cellAcceptsItem)
+                        break;
+                    ++flowColumn;
+                    if (flowColumn == flowBound) {
+                        flowColumn = 0;
+                        ++flowRow;
+                    }
+                }
+            }
+            column = nextColumn;
+            row = nextRow;
+            QQuickGridLayoutItem *layoutItem = new QQuickGridLayoutItem(child, row, column, rowSpan, columnSpan);
+
+            d->engine.insertItem(layoutItem, -1);
+
+            setupItemLayout(child);
+        }
+    }
+}
 
 /**********************************
  **
@@ -410,23 +535,25 @@ void QQuickLinearLayout::setSpacing(qreal spacing)
     invalidate();
 }
 
-
-void QQuickLinearLayout::insertLayoutItem(QQuickItem *item)
+void QQuickLinearLayout::insertLayoutItems()
 {
     Q_D(QQuickLinearLayout);
-    const int index = d->engine.rowCount(d->orientation);
-    d->engine.insertRow(index, d->orientation);
-
-    int gridRow = 0;
-    int gridColumn = index;
-    if (d->orientation == Qt::Vertical)
-        qSwap(gridRow, gridColumn);
-    QQuickGridLayoutItem *layoutItem = new QQuickGridLayoutItem(item, gridRow, gridColumn, 1, 1, 0);
-    d->engine.insertItem(layoutItem, index);
-
-    setupItemLayout(item);
+    foreach (QQuickItem *child,  childItems()) {
+        Q_ASSERT(child);
+        if (child->isVisible()) {
+            const int index = d->engine.rowCount(d->orientation);
+            d->engine.insertRow(index, d->orientation);
+
+            int gridRow = 0;
+            int gridColumn = index;
+            if (d->orientation == Qt::Vertical)
+                qSwap(gridRow, gridColumn);
+            QQuickGridLayoutItem *layoutItem = new QQuickGridLayoutItem(child, gridRow, gridColumn, 1, 1, 0);
+            d->engine.insertItem(layoutItem, index);
+
+            setupItemLayout(child);
+        }
+    }
 }
 
-
-
 QT_END_NAMESPACE
diff --git a/src/layouts/qquicklinearlayout_p.h b/src/layouts/qquicklinearlayout_p.h
index a4773d048..ea418790e 100644
--- a/src/layouts/qquicklinearlayout_p.h
+++ b/src/layouts/qquicklinearlayout_p.h
@@ -71,7 +71,7 @@ public:
 protected:
     void updateLayoutItems();
     void rearrange(const QSizeF &size);
-    virtual void insertLayoutItem(QQuickItem *item);
+    virtual void insertLayoutItems() = 0;
     void removeLayoutItem(QQuickItem *item);
     void itemChange(ItemChange change, const ItemChangeData &data);
     void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry);
@@ -111,16 +111,36 @@ class QQuickGridLayout : public QQuickGridLayoutBase
     Q_OBJECT
     Q_PROPERTY(qreal columnSpacing READ columnSpacing WRITE setColumnSpacing NOTIFY columnSpacingChanged)
     Q_PROPERTY(qreal rowSpacing READ rowSpacing WRITE setRowSpacing NOTIFY rowSpacingChanged)
+    Q_PROPERTY(int columns READ columns WRITE setColumns NOTIFY columnsChanged)
+    Q_PROPERTY(int rows READ rows WRITE setRows NOTIFY rowsChanged)
+    Q_PROPERTY(Flow flow READ flow WRITE setFlow NOTIFY flowChanged)
 public:
     explicit QQuickGridLayout(QQuickItem *parent = 0);
     qreal columnSpacing() const;
     void setColumnSpacing(qreal spacing);
     qreal rowSpacing() const;
     void setRowSpacing(qreal spacing);
+
+    int columns() const;
+    void setColumns(int columns);
+    int rows() const;
+    void setRows(int rows);
+
+    Q_ENUMS(Flow)
+    enum Flow { LeftToRight, TopToBottom };
+    Flow flow() const;
+    void setFlow(Flow flow);
+
+    void insertLayoutItems();
+
 signals:
     void columnSpacingChanged();
     void rowSpacingChanged();
 
+    void columnsChanged();
+    void rowsChanged();
+
+    void flowChanged();
 private:
     Q_DECLARE_PRIVATE(QQuickGridLayout)
 };
@@ -129,9 +149,12 @@ class QQuickGridLayoutPrivate : public QQuickGridLayoutBasePrivate
 {
     Q_DECLARE_PUBLIC(QQuickGridLayout)
 public:
-    QQuickGridLayoutPrivate() {}
+    QQuickGridLayoutPrivate(): columns(-1), rows(-1), flow(QQuickGridLayout::LeftToRight) {}
     qreal columnSpacing;
     qreal rowSpacing;
+    int columns;
+    int rows;
+    QQuickGridLayout::Flow flow;
 };
 
 
@@ -152,6 +175,8 @@ public:
     qreal spacing() const;
     void setSpacing(qreal spacing);
 
+    void insertLayoutItems();
+
 signals:
     void spacingChanged();
 private:
diff --git a/tests/auto/controls/controls.pro b/tests/auto/controls/controls.pro
index 8db8bf8f0..4d37c8544 100644
--- a/tests/auto/controls/controls.pro
+++ b/tests/auto/controls/controls.pro
@@ -30,6 +30,7 @@ OTHER_FILES += \
     $$PWD/data/tst_page.qml \
     $$PWD/data/tst_menubar.qml \
     $$PWD/data/tst_rowlayout.qml \
+    $$PWD/data/tst_gridlayout.qml \
     $$PWD/data/tst_slider.qml \
     $$PWD/data/tst_statusbar.qml \
     $$PWD/data/tst_tab.qml \
diff --git a/tests/auto/controls/data/tst_gridlayout.qml b/tests/auto/controls/data/tst_gridlayout.qml
new file mode 100644
index 000000000..da1cd8149
--- /dev/null
+++ b/tests/auto/controls/data/tst_gridlayout.qml
@@ -0,0 +1,326 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Quick Controls module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+**     of its contributors may be used to endorse or promote products derived
+**     from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.1
+import QtTest 1.0
+import QtQuick.Layouts 1.0
+
+Item {
+    id: container
+    width: 200
+    height: 200
+    TestCase {
+        id: testCase
+        name: "Tests_GridLayout"
+        when: windowShown
+        width: 200
+        height: 200
+
+        Component {
+            id: layout_flowLeftToRight_Component
+            GridLayout {
+                columns: 4
+                columnSpacing: 0
+                rowSpacing: 0
+                // red rectangles are auto-positioned
+                // black rectangles are explicitly positioned with row,column
+                Rectangle {
+                    // First one should auto position itself at (0,0)
+                    id: r1
+                    color: "red"
+                    width: 20
+                    height: 20
+                }
+                Rectangle {
+                    // (1,1)
+                    id: r2
+                    color: "black"
+                    width: 20
+                    height: 20
+                    Layout.row: 1
+                    Layout.column: 1
+                    Layout.rowSpan: 2
+                    Layout.columnSpan: 2
+                    Layout.fillHeight: true
+                    Layout.fillWidth: true
+                }
+                Rectangle {
+                    // (0,1)
+                    id: r3
+                    color: "black"
+                    width: 20
+                    height: 20
+                    Layout.row: 0
+                    Layout.column: 1
+                }
+                Rectangle {
+                    // This one won't fit on the left and right sides of the big black box
+                    // inserted at (3,0)
+                    id: r4
+                    color: "red"
+                    width: 20
+                    height: 20
+                    Layout.columnSpan: 2
+                    Layout.rowSpan: 2
+                    Layout.fillHeight: true
+                    Layout.fillWidth: true
+                }
+                Rectangle {
+                    // continue flow from (0,2)
+                    id: r5
+                    color: "black"
+                    width: 20
+                    height: 20
+                    Layout.row: 0
+                    Layout.column: 2
+                }
+                Repeater {
+                    // ...and let the rest of the items automatically fill in the empty cells
+                    model: 8
+                    Rectangle {
+                        color: "red"
+                        width: 20
+                        height: 20
+                        Text { text: index }
+                    }
+                }
+            }
+        }
+
+        function test_flowLeftToRight() {
+            var layout = layout_flowLeftToRight_Component.createObject(container);
+            compare(layout.implicitWidth, 80);
+            compare(layout.children[0].x, 0);
+            compare(layout.children[0].y, 0);
+            compare(layout.children[1].x, 20);
+            compare(layout.children[1].y, 20);
+            compare(layout.children[2].x, 20);
+            compare(layout.children[2].y, 0);
+            compare(layout.children[3].x, 0);
+            compare(layout.children[3].y, 60);
+            compare(layout.children[4].x, 40);
+            compare(layout.children[4].y, 0);
+
+            // assumes that the repeater is the last item among the items it creates
+            compare(layout.children[5].x, 60);
+            compare(layout.children[5].y, 00);
+            compare(layout.children[6].x, 00);
+            compare(layout.children[6].y, 20);
+            compare(layout.children[7].x, 60);
+            compare(layout.children[7].y, 20);
+            compare(layout.children[8].x, 00);
+            compare(layout.children[8].y, 40);
+            compare(layout.children[9].x, 60);
+            compare(layout.children[9].y, 40);
+            compare(layout.children[10].x, 40);
+            compare(layout.children[10].y, 60);
+            compare(layout.children[11].x, 60);
+            compare(layout.children[11].y, 60);
+            compare(layout.children[12].x, 40);
+            compare(layout.children[12].y, 80);
+
+            layout.destroy();
+        }
+
+
+        Component {
+            id: layout_flowLeftToRightDefaultPositions_Component
+            GridLayout {
+                columns: 2
+                columnSpacing: 0
+                rowSpacing: 0
+                // red rectangles are auto-positioned
+                // black rectangles are explicitly positioned with row,column
+                // gray rectangles are items with just one row or just one column specified
+                Rectangle {
+                    // First one should auto position itself at (0,0)
+                    id: r1
+                    color: "red"
+                    width: 20
+                    height: 20
+                }
+                Rectangle {
+                    // (1,0)
+                    id: r2
+                    color: "gray"
+                    width: 20
+                    height: 20
+                    Layout.row: 1
+                }
+                Rectangle {
+                    // (1,1)
+                    id: r3
+                    color: "black"
+                    width: 20
+                    height: 20
+                    Layout.row: 1
+                    Layout.column: 1
+                }
+                Rectangle {
+                    // (1,0), warning emitted
+                    id: r4
+                    color: "gray"
+                    width: 20
+                    height: 20
+                    Layout.row: 1
+                }
+            }
+        }
+
+        function test_flowLeftToRightDefaultPositions() {
+            ignoreWarning("QGridLayoutEngine::addItem: Cell (1, 0) already taken");
+            var layout = layout_flowLeftToRightDefaultPositions_Component.createObject(container);
+            compare(layout.implicitWidth, 40);
+            compare(layout.children[0].x, 0);
+            compare(layout.children[0].y, 0);
+            compare(layout.children[1].x, 0);
+            compare(layout.children[1].y, 20);
+            compare(layout.children[2].x, 20);
+            compare(layout.children[2].y, 20);
+            layout.destroy();
+        }
+
+
+        Component {
+            id: layout_flowTopToBottom_Component
+            GridLayout {
+                rows: 4
+                columnSpacing: 0
+                rowSpacing: 0
+                flow: GridLayout.TopToBottom
+                // red rectangles are auto-positioned
+                // black rectangles are explicitly positioned with row,column
+                Rectangle {
+                    // First one should auto position itself at (0,0)
+                    id: r1
+                    color: "red"
+                    width: 20
+                    height: 20
+                }
+                Rectangle {
+                    // (1,1)
+                    id: r2
+                    color: "black"
+                    width: 20
+                    height: 20
+                    Layout.row: 1
+                    Layout.column: 1
+                    Layout.rowSpan: 2
+                    Layout.columnSpan: 2
+                    Layout.fillHeight: true
+                    Layout.fillWidth: true
+                }
+                Rectangle {
+                    // (2,0)
+                    id: r3
+                    color: "black"
+                    width: 20
+                    height: 20
+                    Layout.row: 2
+                    Layout.column: 0
+                }
+                Rectangle {
+                    // This one won't fit on the left and right sides of the big black box
+                    // inserted at (0,3)
+                    id: r4
+                    color: "red"
+                    width: 20
+                    height: 20
+                    Layout.rowSpan: 2
+                    Layout.fillHeight: true
+                }
+                Rectangle {
+                    // continue flow from (1,0)
+                    id: r5
+                    color: "black"
+                    width: 20
+                    height: 20
+                    Layout.row: 1
+                    Layout.column: 0
+                }
+                Repeater {
+                    // ...and let the rest of the items automatically fill in the empty cells
+                    model: 8
+                    Rectangle {
+                        color: "red"
+                        width: 20
+                        height: 20
+                        Text { text: index }
+                    }
+                }
+            }
+        }
+
+        function test_flowTopToBottom() {
+            var layout = layout_flowTopToBottom_Component.createObject(container);
+            compare(layout.children[0].x, 0);
+            compare(layout.children[0].y, 0);
+            compare(layout.children[1].x, 20);
+            compare(layout.children[1].y, 20);
+            compare(layout.children[2].x, 0);
+            compare(layout.children[2].y, 40);
+            compare(layout.children[3].x, 60);
+            compare(layout.children[3].y, 0);
+            compare(layout.children[4].x, 0);
+            compare(layout.children[4].y, 20);
+
+            // The repeated items
+            compare(layout.children[5].x, 0);
+            compare(layout.children[5].y, 60);
+            compare(layout.children[6].x, 20);
+            compare(layout.children[6].y, 0);
+            compare(layout.children[7].x, 20);
+            compare(layout.children[7].y, 60);
+            compare(layout.children[8].x, 40);
+            compare(layout.children[8].y, 0);
+            compare(layout.children[9].x, 40);
+            compare(layout.children[9].y, 60);
+            compare(layout.children[10].x, 60);
+            compare(layout.children[10].y, 40);
+            compare(layout.children[11].x, 60);
+            compare(layout.children[11].y, 60);
+            compare(layout.children[12].x, 80);
+            compare(layout.children[12].y, 0);
+
+            layout.destroy();
+        }
+
+    }
+}
diff --git a/tests/manual/Layout.qml b/tests/manual/Layout.qml
index 2fe822223..2b4bd2004 100644
--- a/tests/manual/Layout.qml
+++ b/tests/manual/Layout.qml
@@ -719,6 +719,67 @@ Item {
                     Text {
                         text: "Norwegian flag"
                     }
+                    // [1]
+                    GridLayout {
+                        columns: 4
+                        flow: GridLayout.LeftToRight
+                        anchors.left: parent.left
+                        anchors.right: parent.right
+                        Rectangle {
+                            color: "green"
+                            width: 20
+                            height: 20
+                            Layout.horizontalSizePolicy: Layout.Expanding
+                            Layout.verticalSizePolicy: Layout.Expanding
+                        }
+                        Rectangle {
+                            color: "green"
+                            width: 20
+                            height: 20
+                            Layout.row: 1
+                            Layout.column: 1
+                            Layout.rowSpan: 2
+                            Layout.columnSpan: 2
+                            Layout.horizontalSizePolicy: Layout.Expanding
+                            Layout.verticalSizePolicy: Layout.Expanding
+                        }
+                        Rectangle {
+                            color: "green"
+                            width: 20
+                            height: 20
+                            Layout.row: 0
+                            Layout.column: 1
+                            Layout.horizontalSizePolicy: Layout.Expanding
+                            Layout.verticalSizePolicy: Layout.Expanding
+                        }
+                        Rectangle {
+                            color: "green"
+                            width: 20
+                            height: 20
+                            Layout.rowSpan: 2
+                            Layout.horizontalSizePolicy: Layout.Expanding
+                            Layout.verticalSizePolicy: Layout.Expanding
+                        }
+                        Repeater {
+                            model: 10
+                            Rectangle {
+                                color: Qt.rgba(1, 0, 0, 1 - (index/10.0))
+                                width: 20
+                                height: 20
+                                Layout.horizontalSizePolicy: Layout.Expanding
+                                Layout.verticalSizePolicy: Layout.Expanding
+                                Text { text: index }
+                            }
+                        }
+                        Rectangle {
+                            color: "green"
+                            width: 20
+                            Layout.columnSpan:2
+                            height: 20
+                            Layout.horizontalSizePolicy: Layout.Expanding
+                            Layout.verticalSizePolicy: Layout.Expanding
+                        }
+                    }
                 }
             }
         }
-- 
GitLab