aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard Moe Gustavsen <richard.gustavsen@qt.io>2018-02-13 12:51:04 +0100
committerRichard Moe Gustavsen <richard.gustavsen@qt.io>2018-04-12 07:58:48 +0000
commit5e1a4a444e9d059fb0b97671220ce80f9a477681 (patch)
treecc6baa4a95d7a9c4f215d5a9e80c413c30dc1bdb
parentcd14f003990194a9f5dd77f10e62cab2b7ce7d6b (diff)
QQuickTableView: add new class: QQuickTableView
Task-number: QTBUG-51710 Change-Id: I8827a9c80bfa1ed3b9116c7625b74e3c5b12bfce Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
-rw-r--r--src/imports/imports.pro3
-rw-r--r--src/imports/tableview/plugin.cpp64
-rw-r--r--src/imports/tableview/plugins.qmltypes33
-rw-r--r--src/imports/tableview/qmldir5
-rw-r--r--src/imports/tableview/tableview.pro10
-rw-r--r--src/quick/configure.json12
-rw-r--r--src/quick/items/items.pri7
-rw-r--r--src/quick/items/qquicktableview.cpp1562
-rw-r--r--src/quick/items/qquicktableview_p.h171
9 files changed, 1865 insertions, 2 deletions
diff --git a/src/imports/imports.pro b/src/imports/imports.pro
index 3263774fd5..190fb57553 100644
--- a/src/imports/imports.pro
+++ b/src/imports/imports.pro
@@ -18,7 +18,8 @@ qtHaveModule(quick) {
layouts \
qtquick2 \
window \
- testlib
+ testlib \
+ tableview
qtConfig(systemsemaphore): SUBDIRS += sharedimage
qtConfig(quick-particles): \
diff --git a/src/imports/tableview/plugin.cpp b/src/imports/tableview/plugin.cpp
new file mode 100644
index 0000000000..3bd9b72a8d
--- /dev/null
+++ b/src/imports/tableview/plugin.cpp
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the plugins of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtQml/qqmlextensionplugin.h>
+#include <QtQuick/private/qquicktableview_p.h>
+
+QT_BEGIN_NAMESPACE
+
+//![class decl]
+class QtQuickTableViewPlugin : public QQmlExtensionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
+public:
+ QtQuickTableViewPlugin(QObject *parent = nullptr) : QQmlExtensionPlugin(parent)
+ {}
+
+ void registerTypes(const char *uri) override
+ {
+ Q_ASSERT(QLatin1String(uri) == QLatin1String("Qt.labs.tableview"));
+ qmlRegisterType<QQuickTableView>(uri, 1, 0, "TableView");
+ }
+};
+//![class decl]
+
+QT_END_NAMESPACE
+
+#include "plugin.moc"
diff --git a/src/imports/tableview/plugins.qmltypes b/src/imports/tableview/plugins.qmltypes
new file mode 100644
index 0000000000..8510b698a9
--- /dev/null
+++ b/src/imports/tableview/plugins.qmltypes
@@ -0,0 +1,33 @@
+import QtQuick.tooling 1.2
+
+// This file describes the plugin-supplied types contained in the library.
+// It is used for QML tooling purposes only.
+//
+// This file was auto-generated by:
+// 'qmlplugindump -nonrelocatable Qt.labs.tableview 1.0'
+
+Module {
+ dependencies: ["QtQuick 2.8"]
+ Component {
+ name: "QQuickTableView"
+ defaultProperty: "flickableData"
+ prototype: "QQuickFlickable"
+ exports: ["Qt.labs.tableview/TableView 1.0"]
+ exportMetaObjectRevisions: [0]
+ attachedType: "QQuickTableViewAttached"
+ Property { name: "rows"; type: "int"; isReadonly: true }
+ Property { name: "columns"; type: "int"; isReadonly: true }
+ Property { name: "rowSpacing"; type: "double" }
+ Property { name: "columnSpacing"; type: "double" }
+ Property { name: "cacheBuffer"; type: "int" }
+ Property { name: "model"; type: "QVariant" }
+ Property { name: "delegate"; type: "QQmlComponent"; isPointer: true }
+ }
+ Component {
+ name: "QQuickTableViewAttached"
+ prototype: "QObject"
+ Property { name: "tableView"; type: "QQuickTableView"; isReadonly: true; isPointer: true }
+ Property { name: "row"; type: "int"; isReadonly: true }
+ Property { name: "column"; type: "int"; isReadonly: true }
+ }
+}
diff --git a/src/imports/tableview/qmldir b/src/imports/tableview/qmldir
new file mode 100644
index 0000000000..dac976794a
--- /dev/null
+++ b/src/imports/tableview/qmldir
@@ -0,0 +1,5 @@
+module Qt.labs.tableview
+plugin tableviewplugin
+classname QtQuickTableViewPlugin
+typeinfo plugins.qmltypes
+
diff --git a/src/imports/tableview/tableview.pro b/src/imports/tableview/tableview.pro
new file mode 100644
index 0000000000..97ced65e6b
--- /dev/null
+++ b/src/imports/tableview/tableview.pro
@@ -0,0 +1,10 @@
+CXX_MODULE = qml
+TARGET = tableviewplugin
+TARGETPATH = Qt/labs/tableview
+IMPORT_VERSION = 1.0
+
+SOURCES += $$PWD/plugin.cpp
+
+QT += quick-private qml-private
+
+load(qml_plugin)
diff --git a/src/quick/configure.json b/src/quick/configure.json
index b77ea863b3..01617c5395 100644
--- a/src/quick/configure.json
+++ b/src/quick/configure.json
@@ -15,6 +15,7 @@
"quick-flipable": "boolean",
"quick-gridview": "boolean",
"quick-listview": "boolean",
+ "quick-tableview": "boolean",
"quick-path": "boolean",
"quick-pathview": "boolean",
"quick-positioners": "boolean",
@@ -86,7 +87,7 @@
},
"quick-itemview": {
"label": "ItemView item",
- "condition": "features.quick-gridview || features.quick-listview",
+ "condition": "features.quick-gridview || features.quick-listview || features.quick-tableview",
"output": [
"privateFeature"
]
@@ -107,6 +108,14 @@
"privateFeature"
]
},
+ "quick-tableview": {
+ "label": "TableView item",
+ "purpose": "Provides the TableView item.",
+ "section": "Qt Quick",
+ "output": [
+ "privateFeature"
+ ]
+ },
"quick-particles": {
"label": "Particle support",
"purpose": "Provides a particle system.",
@@ -182,6 +191,7 @@
"quick-flipable",
"quick-gridview",
"quick-listview",
+ "quick-tableview",
"quick-path",
"quick-pathview",
"quick-positioners",
diff --git a/src/quick/items/items.pri b/src/quick/items/items.pri
index ca90f80cb8..c036bdbb42 100644
--- a/src/quick/items/items.pri
+++ b/src/quick/items/items.pri
@@ -144,6 +144,13 @@ qtConfig(quick-listview) {
$$PWD/qquicklistview.cpp
}
+qtConfig(quick-tableview) {
+ HEADERS += \
+ $$PWD/qquicktableview_p.h
+ SOURCES += \
+ $$PWD/qquicktableview.cpp
+}
+
qtConfig(quick-pathview) {
HEADERS += \
$$PWD/qquickpathview_p.h \
diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp
new file mode 100644
index 0000000000..47153823cd
--- /dev/null
+++ b/src/quick/items/qquicktableview.cpp
@@ -0,0 +1,1562 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qquicktableview_p.h"
+
+#include <QtCore/qtimer.h>
+#include <QtQml/private/qqmldelegatemodel_p.h>
+#include <QtQml/private/qqmlincubator_p.h>
+#include <QtQml/private/qqmlchangeset_p.h>
+#include <QtQml/qqmlinfo.h>
+
+#include <QtQuick/private/qquickflickable_p_p.h>
+#include <QtQuick/private/qquickitemviewfxitem_p_p.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcTableViewDelegateLifecycle, "qt.quick.tableview.lifecycle")
+
+#define Q_TABLEVIEW_UNREACHABLE(output) { dumpTable(); qWarning() << "output:" << output; Q_UNREACHABLE(); }
+#define Q_TABLEVIEW_ASSERT(cond, output) Q_ASSERT(cond || [&](){ dumpTable(); qWarning() << "output:" << output; return false;}())
+
+static const Qt::Edge allTableEdges[] = { Qt::LeftEdge, Qt::RightEdge, Qt::TopEdge, Qt::BottomEdge };
+static const int kBufferTimerInterval = 300;
+static const int kDefaultCacheBuffer = 300;
+
+static QLine rectangleEdge(const QRect &rect, Qt::Edge tableEdge)
+{
+ switch (tableEdge) {
+ case Qt::LeftEdge:
+ return QLine(rect.topLeft(), rect.bottomLeft());
+ case Qt::RightEdge:
+ return QLine(rect.topRight(), rect.bottomRight());
+ case Qt::TopEdge:
+ return QLine(rect.topLeft(), rect.topRight());
+ case Qt::BottomEdge:
+ return QLine(rect.bottomLeft(), rect.bottomRight());
+ }
+ return QLine();
+}
+
+static QRect expandedRect(const QRect &rect, Qt::Edge edge, int increment)
+{
+ switch (edge) {
+ case Qt::LeftEdge:
+ return rect.adjusted(-increment, 0, 0, 0);
+ case Qt::RightEdge:
+ return rect.adjusted(0, 0, increment, 0);
+ case Qt::TopEdge:
+ return rect.adjusted(0, -increment, 0, 0);
+ case Qt::BottomEdge:
+ return rect.adjusted(0, 0, 0, increment);
+ }
+ return QRect();
+}
+
+class FxTableItem;
+
+class QQuickTableViewPrivate : public QQuickFlickablePrivate
+{
+ Q_DECLARE_PUBLIC(QQuickTableView)
+
+public:
+ class TableEdgeLoadRequest
+ {
+ // Whenever we need to load new rows or columns in the
+ // table, we fill out a TableEdgeLoadRequest.
+ // TableEdgeLoadRequest is just a struct that keeps track
+ // of which cells that needs to be loaded, and which cell
+ // the table is currently loading. The loading itself is
+ // done by QQuickTableView.
+
+ public:
+ void begin(const QPoint &cell, QQmlIncubator::IncubationMode incubationMode)
+ {
+ Q_ASSERT(!active);
+ active = true;
+ tableEdge = Qt::Edge(0);
+ tableCells = QLine(cell, cell);
+ mode = incubationMode;
+ cellCount = 1;
+ currentIndex = 0;
+ qCDebug(lcTableViewDelegateLifecycle()) << "begin top-left:" << toString();
+ }
+
+ void begin(const QLine cellsToLoad, Qt::Edge edgeToLoad, QQmlIncubator::IncubationMode incubationMode)
+ {
+ Q_ASSERT(!active);
+ active = true;
+ tableEdge = edgeToLoad;
+ tableCells = cellsToLoad;
+ mode = incubationMode;
+ cellCount = tableCells.x2() - tableCells.x1() + tableCells.y2() - tableCells.y1() + 1;
+ currentIndex = 0;
+ qCDebug(lcTableViewDelegateLifecycle()) << "begin:" << toString();
+ }
+
+ inline void markAsDone() { active = false; }
+ inline bool isActive() { return active; }
+
+ inline QPoint firstCell() { return tableCells.p1(); }
+ inline QPoint lastCell() { return tableCells.p2(); }
+ inline QPoint currentCell() { return cellAt(currentIndex); }
+ inline QPoint previousCell() { return cellAt(currentIndex - 1); }
+
+ inline bool atBeginning() { return currentIndex == 0; }
+ inline bool hasCurrentCell() { return currentIndex < cellCount; }
+ inline void moveToNextCell() { ++currentIndex; }
+
+ inline Qt::Edge edge() { return tableEdge; }
+ inline QQmlIncubator::IncubationMode incubationMode() { return mode; }
+
+ QString toString()
+ {
+ QString str;
+ QDebug dbg(&str);
+ dbg.nospace() << "TableSectionLoadRequest(" << "edge:"
+ << tableEdge << " cells:" << tableCells << " incubation:";
+
+ switch (mode) {
+ case QQmlIncubator::Asynchronous:
+ dbg << "Asynchronous";
+ break;
+ case QQmlIncubator::AsynchronousIfNested:
+ dbg << "AsynchronousIfNested";
+ break;
+ case QQmlIncubator::Synchronous:
+ dbg << "Synchronous";
+ break;
+ }
+
+ return str;
+ }
+
+ private:
+ Qt::Edge tableEdge = Qt::Edge(0);
+ QLine tableCells;
+ int currentIndex = 0;
+ int cellCount = 0;
+ bool active = false;
+ QQmlIncubator::IncubationMode mode = QQmlIncubator::AsynchronousIfNested;
+
+ QPoint cellAt(int index)
+ {
+ int x = tableCells.p1().x() + (tableCells.dx() ? index : 0);
+ int y = tableCells.p1().y() + (tableCells.dy() ? index : 0);
+ return QPoint(x, y);
+ }
+ };
+
+ struct ColumnRowSize
+ {
+ // ColumnRowSize is a helper class for storing row heights
+ // and column widths. We calculate the average width of a column
+ // the first time it's scrolled into view based on the size of
+ // the loaded items in the new column. Since we never load more items
+ // that what fits inside the viewport (cachebuffer aside), this calculation
+ // would be different depending on which row you were at when the column
+ // was scrolling in. To avoid that a column resizes when it's scrolled
+ // in and out and in again, we store its width. But to avoid storing
+ // the width for all columns, we choose to only store the width if it
+ // differs from the column(s) to the left. The same logic applies for row heights.
+ // 'index' translates to either 'row' or 'column'.
+ int index;
+ qreal size;
+
+ static bool lessThan(const ColumnRowSize& a, const ColumnRowSize& b)
+ {
+ return a.index < b.index;
+ }
+ };
+
+public:
+ QQuickTableViewPrivate();
+
+ static inline QQuickTableViewPrivate *get(QQuickTableView *q) { return q->d_func(); }
+
+ void updatePolish() override;
+
+protected:
+ QList<FxTableItem *> loadedItems;
+
+ QQmlInstanceModel* model = nullptr;
+ QVariant modelVariant;
+
+ // loadedTable describes the table cells that are currently loaded (from top left
+ // row/column to bottom right row/column). loadedTableOuterRect describes the actual
+ // pixels that those cells cover, and is matched agains the viewport to determine when
+ // we need to fill up with more rows/columns. loadedTableInnerRect describes the pixels
+ // that the loaded table covers if you remove one row/column on each side of the table, and
+ // is used to determine rows/columns that are no longer visible and can be unloaded.
+ QRect loadedTable;
+ QRectF loadedTableOuterRect;
+ QRectF loadedTableInnerRect;
+
+ QRectF viewportRect = QRectF(0, 0, -1, -1);
+
+ QSize tableSize;
+
+ TableEdgeLoadRequest loadRequest;
+
+ QPoint contentSizeBenchMarkPoint = QPoint(-1, -1);
+ QSizeF cellSpacing;
+
+ int cacheBuffer = kDefaultCacheBuffer;
+ QTimer cacheBufferDelayTimer;
+ bool hasBufferedItems = false;
+
+ bool blockItemCreatedCallback = false;
+ bool tableInvalid = false;
+ bool tableRebuilding = false;
+ bool columnRowPositionsInvalid = false;
+
+ QVector<ColumnRowSize> columnWidths;
+ QVector<ColumnRowSize> rowHeights;
+
+ const static QPoint kLeft;
+ const static QPoint kRight;
+ const static QPoint kUp;
+ const static QPoint kDown;
+
+#ifdef QT_DEBUG
+ QString forcedIncubationMode = qEnvironmentVariable("QT_TABLEVIEW_INCUBATION_MODE");
+#endif
+
+protected:
+ inline QQmlDelegateModel *wrapperModel() const { return qobject_cast<QQmlDelegateModel*>(model); }
+
+ int modelIndexAtCell(const QPoint &cell);
+ QPoint cellAtModelIndex(int modelIndex);
+
+ void calculateColumnWidthsAfterRebuilding();
+ void calculateRowHeightsAfterRebuilding();
+ void calculateColumnWidth(int column);
+ void calculateRowHeight(int row);
+ void calculateEdgeSizeFromLoadRequest();
+ void calculateTableSize();
+
+ qreal columnWidth(int column);
+ qreal rowHeight(int row);
+
+ void relayoutTable();
+ void relayoutTableItems();
+
+ void layoutVerticalEdge(Qt::Edge tableEdge, bool adjustSize);
+ void layoutHorizontalEdge(Qt::Edge tableEdge, bool adjustSize);
+ void layoutTableEdgeFromLoadRequest();
+
+ void updateContentWidth();
+ void updateContentHeight();
+
+ void enforceFirstRowColumnAtOrigo();
+ void syncLoadedTableRectFromLoadedTable();
+ void syncLoadedTableFromLoadRequest();
+
+ bool canLoadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const;
+ bool canUnloadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const;
+ Qt::Edge nextEdgeToLoad(const QRectF rect);
+ Qt::Edge nextEdgeToUnload(const QRectF rect);
+
+ FxTableItem *loadedTableItem(const QPoint &cell) const;
+ FxTableItem *itemNextTo(const FxTableItem *fxTableItem, const QPoint &direction) const;
+ FxTableItem *createFxTableItem(const QPoint &cell, QQmlIncubator::IncubationMode incubationMode);
+ FxTableItem *loadFxTableItem(const QPoint &cell, QQmlIncubator::IncubationMode incubationMode);
+
+ void releaseItem(FxTableItem *fxTableItem);
+ void releaseLoadedItems();
+ void clear();
+
+ void unloadItem(const QPoint &cell);
+ void unloadItems(const QLine &items);
+
+ void loadInitialTopLeftItem();
+ void loadEdgesInsideRect(const QRectF &rect, QQmlIncubator::IncubationMode incubationMode);
+ void unloadEdgesOutsideRect(const QRectF &rect);
+ void loadAndUnloadVisibleEdges();
+ void cancelLoadRequest();
+ void processLoadRequest();
+ void beginRebuildTable();
+ void endRebuildTable();
+
+ void loadBuffer();
+ void unloadBuffer();
+ QRectF bufferRect();
+
+ void invalidateTable();
+ void invalidateColumnRowPositions();
+
+ void createWrapperModel();
+
+ void initItemCallback(int modelIndex, QObject *item);
+ void itemCreatedCallback(int modelIndex, QObject *object);
+ void modelUpdated(const QQmlChangeSet &changeSet, bool reset);
+
+ inline QString tableLayoutToString() const;
+ void dumpTable() const;
+};
+
+class FxTableItem : public QQuickItemViewFxItem
+{
+public:
+ FxTableItem(QQuickItem *item, QQuickTableView *table, bool own)
+ : QQuickItemViewFxItem(item, own, QQuickTableViewPrivate::get(table))
+ {
+ }
+
+ qreal position() const override { return 0; }
+ qreal endPosition() const override { return 0; }
+ qreal size() const override { return 0; }
+ qreal sectionSize() const override { return 0; }
+ bool contains(qreal, qreal) const override { return false; }
+
+ QPoint cell;
+};
+
+Q_DECLARE_TYPEINFO(QQuickTableViewPrivate::ColumnRowSize, Q_PRIMITIVE_TYPE);
+
+const QPoint QQuickTableViewPrivate::kLeft = QPoint(-1, 0);
+const QPoint QQuickTableViewPrivate::kRight = QPoint(1, 0);
+const QPoint QQuickTableViewPrivate::kUp = QPoint(0, -1);
+const QPoint QQuickTableViewPrivate::kDown = QPoint(0, 1);
+
+QQuickTableViewPrivate::QQuickTableViewPrivate()
+ : QQuickFlickablePrivate()
+{
+ cacheBufferDelayTimer.setSingleShot(true);
+ QObject::connect(&cacheBufferDelayTimer, &QTimer::timeout, [=]{ loadBuffer(); });
+}
+
+QString QQuickTableViewPrivate::tableLayoutToString() const
+{
+ return QString(QLatin1String("table cells: (%1,%2) -> (%3,%4), item count: %5, table rect: %6,%7 x %8,%9"))
+ .arg(loadedTable.topLeft().x()).arg(loadedTable.topLeft().y())
+ .arg(loadedTable.bottomRight().x()).arg(loadedTable.bottomRight().y())
+ .arg(loadedItems.count())
+ .arg(loadedTableOuterRect.x())
+ .arg(loadedTableOuterRect.y())
+ .arg(loadedTableOuterRect.width())
+ .arg(loadedTableOuterRect.height());
+}
+
+void QQuickTableViewPrivate::dumpTable() const
+{
+ auto listCopy = loadedItems;
+ std::stable_sort(listCopy.begin(), listCopy.end(),
+ [](const FxTableItem *lhs, const FxTableItem *rhs)
+ { return lhs->index < rhs->index; });
+
+ qWarning() << QStringLiteral("******* TABLE DUMP *******");
+ for (int i = 0; i < listCopy.count(); ++i)
+ qWarning() << static_cast<FxTableItem *>(listCopy.at(i))->cell;
+ qWarning() << tableLayoutToString();
+
+ QString filename = QStringLiteral("QQuickTableView_dumptable_capture.png");
+ if (q_func()->window()->grabWindow().save(filename))
+ qWarning() << "Window capture saved to:" << filename;
+}
+
+int QQuickTableViewPrivate::modelIndexAtCell(const QPoint &cell)
+{
+ int availableRows = tableSize.height();
+ int modelIndex = cell.y() + (cell.x() * availableRows);
+ Q_TABLEVIEW_ASSERT(modelIndex < model->count(), modelIndex << cell);
+ return modelIndex;
+}
+
+QPoint QQuickTableViewPrivate::cellAtModelIndex(int modelIndex)
+{
+ int availableRows = tableSize.height();
+ Q_TABLEVIEW_ASSERT(availableRows > 0, availableRows);
+ int column = int(modelIndex / availableRows);
+ int row = modelIndex % availableRows;
+ return QPoint(column, row);
+}
+
+void QQuickTableViewPrivate::updateContentWidth()
+{
+ Q_Q(QQuickTableView);
+
+ const qreal thresholdBeforeAdjust = 0.1;
+ int currentRightColumn = loadedTable.right();
+
+ if (currentRightColumn > contentSizeBenchMarkPoint.x()) {
+ contentSizeBenchMarkPoint.setX(currentRightColumn);
+
+ qreal currentWidth = loadedTableOuterRect.right();
+ qreal averageCellSize = currentWidth / (currentRightColumn + 1);
+ qreal averageSize = averageCellSize + cellSpacing.width();
+ qreal estimatedWith = (tableSize.width() * averageSize) - cellSpacing.width();
+
+ if (currentRightColumn >= tableSize.width() - 1) {
+ // We are at the last column, and can set the exact width
+ if (currentWidth != q->implicitWidth())
+ q->setContentWidth(currentWidth);
+ } else if (currentWidth >= q->implicitWidth()) {
+ // We are at the estimated width, but there are still more columns
+ q->setContentWidth(estimatedWith);
+ } else {
+ // Only set a new width if the new estimate is substantially different
+ qreal diff = 1 - (estimatedWith / q->implicitWidth());
+ if (qAbs(diff) > thresholdBeforeAdjust)
+ q->setContentWidth(estimatedWith);
+ }
+ }
+}
+
+void QQuickTableViewPrivate::updateContentHeight()
+{
+ Q_Q(QQuickTableView);
+
+ const qreal thresholdBeforeAdjust = 0.1;
+ int currentBottomRow = loadedTable.bottom();
+
+ if (currentBottomRow > contentSizeBenchMarkPoint.y()) {
+ contentSizeBenchMarkPoint.setY(currentBottomRow);
+
+ qreal currentHeight = loadedTableOuterRect.bottom();
+ qreal averageCellSize = currentHeight / (currentBottomRow + 1);
+ qreal averageSize = averageCellSize + cellSpacing.height();
+ qreal estimatedHeight = (tableSize.height() * averageSize) - cellSpacing.height();
+
+ if (currentBottomRow >= tableSize.height() - 1) {
+ // We are at the last row, and can set the exact height
+ if (currentHeight != q->implicitHeight())
+ q->setContentHeight(currentHeight);
+ } else if (currentHeight >= q->implicitHeight()) {
+ // We are at the estimated height, but there are still more rows
+ q->setContentHeight(estimatedHeight);
+ } else {
+ // Only set a new height if the new estimate is substantially different
+ qreal diff = 1 - (estimatedHeight / q->implicitHeight());
+ if (qAbs(diff) > thresholdBeforeAdjust)
+ q->setContentHeight(estimatedHeight);
+ }
+ }
+}
+
+void QQuickTableViewPrivate::enforceFirstRowColumnAtOrigo()
+{
+ // Gaps before the first row/column can happen if rows/columns
+ // changes size while flicking e.g because of spacing changes or
+ // changes to a column maxWidth/row maxHeight. Check for this, and
+ // move the whole table rect accordingly.
+ bool layoutNeeded = false;
+ const qreal flickMargin = 50;
+
+ if (loadedTable.x() == 0 && loadedTableOuterRect.x() != 0) {
+ // The table is at the beginning, but not at the edge of the
+ // content view. So move the table to origo.
+ loadedTableOuterRect.moveLeft(0);
+ layoutNeeded = true;
+ } else if (loadedTableOuterRect.x() < 0) {
+ // The table is outside the beginning of the content view. Move
+ // the whole table inside, and make some room for flicking.
+ loadedTableOuterRect.moveLeft(loadedTable.x() == 0 ? 0 : flickMargin);
+ layoutNeeded = true;
+ }
+
+ if (loadedTable.y() == 0 && loadedTableOuterRect.y() != 0) {
+ loadedTableOuterRect.moveTop(0);
+ layoutNeeded = true;
+ } else if (loadedTableOuterRect.y() < 0) {
+ loadedTableOuterRect.moveTop(loadedTable.y() == 0 ? 0 : flickMargin);
+ layoutNeeded = true;
+ }
+
+ if (layoutNeeded)
+ relayoutTableItems();
+}
+
+void QQuickTableViewPrivate::syncLoadedTableRectFromLoadedTable()
+{
+ QRectF topLeftRect = loadedTableItem(loadedTable.topLeft())->geometry();
+ QRectF bottomRightRect = loadedTableItem(loadedTable.bottomRight())->geometry();
+ loadedTableOuterRect = topLeftRect.united(bottomRightRect);
+ loadedTableInnerRect = QRectF(topLeftRect.bottomRight(), bottomRightRect.topLeft());
+}
+
+void QQuickTableViewPrivate::syncLoadedTableFromLoadRequest()
+{
+ switch (loadRequest.edge()) {
+ case Qt::LeftEdge:
+ case Qt::TopEdge:
+ loadedTable.setTopLeft(loadRequest.firstCell());
+ break;
+ case Qt::RightEdge:
+ case Qt::BottomEdge:
+ loadedTable.setBottomRight(loadRequest.lastCell());
+ break;
+ default:
+ loadedTable = QRect(loadRequest.firstCell(), loadRequest.lastCell());
+ }
+}
+
+FxTableItem *QQuickTableViewPrivate::itemNextTo(const FxTableItem *fxTableItem, const QPoint &direction) const
+{
+ return loadedTableItem(fxTableItem->cell + direction);
+}
+
+FxTableItem *QQuickTableViewPrivate::loadedTableItem(const QPoint &cell) const
+{
+ for (int i = 0; i < loadedItems.count(); ++i) {
+ FxTableItem *item = loadedItems.at(i);
+ if (item->cell == cell)
+ return item;
+ }
+
+ Q_TABLEVIEW_UNREACHABLE(cell);
+ return nullptr;
+}
+
+FxTableItem *QQuickTableViewPrivate::createFxTableItem(const QPoint &cell, QQmlIncubator::IncubationMode incubationMode)
+{
+ Q_Q(QQuickTableView);
+
+ bool ownItem = false;
+ int modelIndex = modelIndexAtCell(cell);
+
+ QObject* object = model->object(modelIndex, incubationMode);
+ if (!object) {
+ if (model->incubationStatus(modelIndex) == QQmlIncubator::Loading) {
+ // Item is incubating. Return nullptr for now, and let the table call this
+ // function again once we get a callback to itemCreatedCallback().
+ return nullptr;
+ }
+
+ qWarning() << "TableView: failed loading index:" << modelIndex;
+ object = new QQuickItem();
+ ownItem = true;
+ }
+
+ QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
+ if (!item) {
+ qWarning() << "TableView: delegate is not an item:" << modelIndex;
+ delete object;
+ // The model cannot provide an item for the given
+ // index, so we create a placeholder instead.
+ item = new QQuickItem();
+ ownItem = true;
+ }
+
+ item->setParentItem(q->contentItem());
+
+ FxTableItem *fxTableItem = new FxTableItem(item, q, ownItem);
+ fxTableItem->setVisible(false);
+ fxTableItem->cell = cell;
+ return fxTableItem;
+}
+
+FxTableItem *QQuickTableViewPrivate::loadFxTableItem(const QPoint &cell, QQmlIncubator::IncubationMode incubationMode)
+{
+#ifdef QT_DEBUG
+ // Since TableView needs to work flawlessly when e.g incubating inside an async
+ // loader, being able to override all loading to async while debugging can be helpful.
+ static const bool forcedAsync = forcedIncubationMode == QLatin1String("async");
+ if (forcedAsync)
+ incubationMode = QQmlIncubator::Asynchronous;
+#endif
+
+ // Note that even if incubation mode is asynchronous, the item might
+ // be ready immediately since the model has a cache of items.
+ QBoolBlocker guard(blockItemCreatedCallback);
+ auto item = createFxTableItem(cell, incubationMode);
+ qCDebug(lcTableViewDelegateLifecycle) << cell << "ready?" << bool(item);
+ return item;
+}
+
+void QQuickTableViewPrivate::releaseLoadedItems() {
+ // Make a copy and clear the list of items first to avoid destroyed
+ // items being accessed during the loop (QTBUG-61294)
+ auto const tmpList = loadedItems;
+ loadedItems.clear();
+ for (FxTableItem *item : tmpList)
+ releaseItem(item);
+}
+
+void QQuickTableViewPrivate::releaseItem(FxTableItem *fxTableItem)
+{
+ if (fxTableItem->item) {
+ if (fxTableItem->ownItem)
+ delete fxTableItem->item;
+ else if (model->release(fxTableItem->item) != QQmlInstanceModel::Destroyed)
+ fxTableItem->item->setParentItem(nullptr);
+ }
+
+ delete fxTableItem;
+}
+
+void QQuickTableViewPrivate::clear()
+{
+ tableInvalid = true;
+ tableRebuilding = false;
+ if (loadRequest.isActive())
+ cancelLoadRequest();
+
+ releaseLoadedItems();
+ loadedTable = QRect();
+ loadedTableOuterRect = QRect();
+ loadedTableInnerRect = QRect();
+ columnWidths.clear();
+ rowHeights.clear();
+ contentSizeBenchMarkPoint = QPoint(-1, -1);
+
+ updateContentWidth();
+ updateContentHeight();
+}
+
+void QQuickTableViewPrivate::unloadItem(const QPoint &cell)
+{
+ FxTableItem *item = loadedTableItem(cell);
+ loadedItems.removeOne(item);
+ releaseItem(item);
+}
+
+void QQuickTableViewPrivate::unloadItems(const QLine &items)
+{
+ qCDebug(lcTableViewDelegateLifecycle) << items;
+
+ if (items.dx()) {
+ int y = items.p1().y();
+ for (int x = items.p1().x(); x <= items.p2().x(); ++x)
+ unloadItem(QPoint(x, y));
+ } else {
+ int x = items.p1().x();
+ for (int y = items.p1().y(); y <= items.p2().y(); ++y)
+ unloadItem(QPoint(x, y));
+ }
+}
+
+bool QQuickTableViewPrivate::canLoadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const
+{
+ switch (tableEdge) {
+ case Qt::LeftEdge:
+ if (loadedTable.topLeft().x() == 0)
+ return false;
+ return loadedTableOuterRect.left() > fillRect.left();
+ case Qt::RightEdge:
+ if (loadedTable.bottomRight().x() >= tableSize.width() - 1)
+ return false;
+ return loadedTableOuterRect.right() < fillRect.right();
+ case Qt::TopEdge:
+ if (loadedTable.topLeft().y() == 0)
+ return false;
+ return loadedTableOuterRect.top() > fillRect.top();
+ case Qt::BottomEdge:
+ if (loadedTable.bottomRight().y() >= tableSize.height() - 1)
+ return false;
+ return loadedTableOuterRect.bottom() < fillRect.bottom();
+ }
+
+ return false;
+}
+
+bool QQuickTableViewPrivate::canUnloadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const
+{
+ // Note: if there is only one row or column left, we cannot unload, since
+ // they are needed as anchor point for further layouting.
+ const qreal floatingPointMargin = 1;
+
+ switch (tableEdge) {
+ case Qt::LeftEdge:
+ if (loadedTable.width() <= 1)
+ return false;
+ return loadedTableInnerRect.left() < fillRect.left() - floatingPointMargin;
+ case Qt::RightEdge:
+ if (loadedTable.width() <= 1)
+ return false;
+ return loadedTableInnerRect.right() > fillRect.right() + floatingPointMargin;
+ case Qt::TopEdge:
+ if (loadedTable.height() <= 1)
+ return false;
+ return loadedTableInnerRect.top() < fillRect.top() - floatingPointMargin;
+ case Qt::BottomEdge:
+ if (loadedTable.height() <= 1)
+ return false;
+ return loadedTableInnerRect.bottom() > fillRect.bottom() + floatingPointMargin;
+ }
+ Q_TABLEVIEW_UNREACHABLE(tableEdge);
+ return false;
+}
+
+Qt::Edge QQuickTableViewPrivate::nextEdgeToLoad(const QRectF rect)
+{
+ for (Qt::Edge edge : allTableEdges) {
+ if (canLoadTableEdge(edge, rect))
+ return edge;
+ }
+ return Qt::Edge(0);
+}
+
+Qt::Edge QQuickTableViewPrivate::nextEdgeToUnload(const QRectF rect)
+{
+ for (Qt::Edge edge : allTableEdges) {
+ if (canUnloadTableEdge(edge, rect))
+ return edge;
+ }
+ return Qt::Edge(0);
+}
+
+void QQuickTableViewPrivate::calculateColumnWidthsAfterRebuilding()
+{
+ qreal prevColumnWidth = 0;
+ for (int column = loadedTable.left(); column <= loadedTable.right(); ++column) {
+ qreal columnWidth = 0;
+ for (int row = loadedTable.top(); row <= loadedTable.bottom(); ++row) {
+ auto const item = loadedTableItem(QPoint(column, row));
+ columnWidth = qMax(columnWidth, item->geometry().width());
+ }
+
+ if (columnWidth == prevColumnWidth)
+ continue;
+
+ columnWidths.append({column, columnWidth});
+ prevColumnWidth = columnWidth;
+ }
+
+ if (columnWidths.isEmpty()) {
+ // Add at least one column, wo we don't need
+ // to check if the vector is empty elsewhere.
+ columnWidths.append({0, 0});
+ }
+}
+
+void QQuickTableViewPrivate::calculateRowHeightsAfterRebuilding()
+{
+ qreal prevRowHeight = 0;
+ for (int row = loadedTable.top(); row <= loadedTable.bottom(); ++row) {
+ qreal rowHeight = 0;
+ for (int column = loadedTable.left(); column <= loadedTable.right(); ++column) {
+ auto const item = loadedTableItem(QPoint(column, row));
+ rowHeight = qMax(rowHeight, item->geometry().height());
+ }
+
+ if (rowHeight == prevRowHeight)
+ continue;
+
+ rowHeights.append({row, rowHeight});
+ }
+
+ if (rowHeights.isEmpty()) {
+ // Add at least one row, wo we don't need
+ // to check if the vector is empty elsewhere.
+ rowHeights.append({0, 0});
+ }
+}
+
+void QQuickTableViewPrivate::calculateColumnWidth(int column)
+{
+ if (column < columnWidths.last().index) {
+ // We only do the calculation once, and then stick with the size.
+ // See comments inside ColumnRowSize struct.
+ return;
+ }
+
+ qreal columnWidth = 0;
+ for (int row = loadedTable.top(); row <= loadedTable.bottom(); ++row) {
+ auto const item = loadedTableItem(QPoint(column, row));
+ columnWidth = qMax(columnWidth, item->geometry().width());
+ }
+
+ if (columnWidth == columnWidths.last().size)
+ return;
+
+ columnWidths.append({column, columnWidth});
+}
+
+void QQuickTableViewPrivate::calculateRowHeight(int row)
+{
+ if (row < rowHeights.last().index) {
+ // We only do the calculation once, and then stick with the size.
+ // See comments inside ColumnRowSize struct.
+ return;
+ }
+
+ qreal rowHeight = 0;
+ for (int column = loadedTable.left(); column <= loadedTable.right(); ++column) {
+ auto const item = loadedTableItem(QPoint(column, row));
+ rowHeight = qMax(rowHeight, item->geometry().height());
+ }
+
+ if (rowHeight == rowHeights.last().size)
+ return;
+
+ rowHeights.append({row, rowHeight});
+}
+
+void QQuickTableViewPrivate::calculateEdgeSizeFromLoadRequest()
+{
+ if (tableRebuilding)
+ return;
+
+ switch (loadRequest.edge()) {
+ case Qt::LeftEdge:
+ case Qt::TopEdge:
+ // Flicking left or up through "never loaded" rows/columns is currently
+ // not supported. You always need to start loading the table from the beginning.
+ return;
+ case Qt::RightEdge:
+ if (tableSize.height() > 1)
+ calculateColumnWidth(loadedTable.right());
+ break;
+ case Qt::BottomEdge:
+ if (tableSize.width() > 1)
+ calculateRowHeight(loadedTable.bottom());
+ break;
+ default:
+ Q_TABLEVIEW_UNREACHABLE("This function should not be called when loading top-left item");
+ }
+}
+
+void QQuickTableViewPrivate::calculateTableSize()
+{
+ // tableSize is the same as row and column count, and will always
+ // be the same as the number of rows and columns in the model.
+ Q_Q(QQuickTableView);
+ QSize prevTableSize = tableSize;
+
+ if (auto wrapper = wrapperModel())
+ tableSize = QSize(wrapper->columns(), wrapper->rows());
+ else if (model)
+ tableSize = QSize(1, model->count());
+ else
+ tableSize = QSize(0, 0);
+
+ if (prevTableSize.width() != tableSize.width())
+ emit q->columnsChanged();
+ if (prevTableSize.height() != tableSize.height())
+ emit q->rowsChanged();
+}
+
+qreal QQuickTableViewPrivate::columnWidth(int column)
+{
+ if (!columnWidths.isEmpty()) {
+ // Find the ColumnRowSize assignment before, or at, column
+ auto iter = std::lower_bound(columnWidths.constBegin(), columnWidths.constEnd(),
+ ColumnRowSize{column, -1}, ColumnRowSize::lessThan);
+
+ // First check if we got an explicit assignment
+ if (iter->index == column)
+ return iter->size;
+
+ // If the table is not a list, return the size of
+ // ColumnRowSize element found before column.
+ if (tableSize.height() > 1)
+ return (iter - 1)->size;
+ }
+
+ // if we have an item loaded at column, return the width of the item.
+ if (column >= loadedTable.left() && column <= loadedTable.right())
+ return loadedTableItem(QPoint(column, 0))->geometry().width();
+
+ return -1;
+}
+
+qreal QQuickTableViewPrivate::rowHeight(int row)
+{
+ if (!rowHeights.isEmpty()) {
+ // Find the ColumnRowSize assignment before, or at, row
+ auto iter = std::lower_bound(rowHeights.constBegin(), rowHeights.constEnd(),
+ ColumnRowSize{row, -1}, ColumnRowSize::lessThan);
+
+ // First check if we got an explicit assignment
+ if (iter->index == row)
+ return iter->size;
+
+ // If the table is not a list, return the size of
+ // ColumnRowSize element found before column.
+ if (q_func()->columns() > 1)
+ return (iter - 1)->size;
+ }
+
+ // if we have an item loaded at row, return the height of the item.
+ if (row >= loadedTable.top() && row <= loadedTable.bottom())
+ return loadedTableItem(QPoint(0, row))->geometry().height();
+
+ return -1;
+}
+
+void QQuickTableViewPrivate::relayoutTable()
+{
+ relayoutTableItems();
+ columnRowPositionsInvalid = false;
+
+ syncLoadedTableRectFromLoadedTable();
+ contentSizeBenchMarkPoint = QPoint(-1, -1);
+ updateContentWidth();
+ updateContentHeight();
+}
+
+void QQuickTableViewPrivate::relayoutTableItems()
+{
+ qCDebug(lcTableViewDelegateLifecycle);
+ columnRowPositionsInvalid = false;
+
+ qreal nextColumnX = loadedTableOuterRect.x();
+ qreal nextRowY = loadedTableOuterRect.y();
+
+ for (int column = loadedTable.left(); column <= loadedTable.right(); ++column) {
+ // Adjust the geometry of all cells in the current column
+ qreal width = columnWidth(column);
+ for (int row = loadedTable.top(); row <= loadedTable.bottom(); ++row) {
+ auto item = loadedTableItem(QPoint(column, row));
+ QRectF geometry = item->geometry();
+ geometry.moveLeft(nextColumnX);
+ geometry.setWidth(width);
+ item->setGeometry(geometry);
+ }
+
+ nextColumnX += width + cellSpacing.width();
+ }
+
+ for (int row = loadedTable.top(); row <= loadedTable.bottom(); ++row) {
+ // Adjust the geometry of all cells in the current row
+ qreal height = rowHeight(row);
+ for (int column = loadedTable.left(); column <= loadedTable.right(); ++column) {
+ auto item = loadedTableItem(QPoint(column, row));
+ QRectF geometry = item->geometry();
+ geometry.moveTop(nextRowY);
+ geometry.setHeight(height);
+ item->setGeometry(geometry);
+ }
+
+ nextRowY += height + cellSpacing.height();
+ }
+
+ if (Q_UNLIKELY(lcTableViewDelegateLifecycle().isDebugEnabled())) {
+ for (int column = loadedTable.left(); column <= loadedTable.right(); ++column) {
+ for (int row = loadedTable.top(); row <= loadedTable.bottom(); ++row) {
+ QPoint cell = QPoint(column, row);
+ qCDebug(lcTableViewDelegateLifecycle()) << "relayout item:" << cell << loadedTableItem(cell)->geometry();
+ }
+ }
+ }
+}
+
+void QQuickTableViewPrivate::layoutVerticalEdge(Qt::Edge tableEdge, bool adjustSize)
+{
+ int column = (tableEdge == Qt::LeftEdge) ? loadedTable.left() : loadedTable.right();
+ QPoint neighbourDirection = (tableEdge == Qt::LeftEdge) ? kRight : kLeft;
+ qreal width = adjustSize ? columnWidth(column) : 0;
+ qreal left = -1;
+
+ for (int row = loadedTable.top(); row <= loadedTable.bottom(); ++row) {
+ auto fxTableItem = loadedTableItem(QPoint(column, row));
+ auto const neighbourItem = itemNextTo(fxTableItem, neighbourDirection);
+ QRectF geometry = fxTableItem->geometry();
+
+ if (adjustSize) {
+ geometry.setWidth(width);
+ geometry.setHeight(neighbourItem->geometry().height());
+ }
+
+ if (left == -1) {
+ // left will be the same for all items in the
+ // column, so do the calculation once.
+ left = tableEdge == Qt::LeftEdge ?
+ neighbourItem->geometry().left() - cellSpacing.width() - geometry.width() :
+ neighbourItem->geometry().right() + cellSpacing.width();
+ }
+
+ geometry.moveLeft(left);
+ geometry.moveTop(neighbourItem->geometry().top());
+
+ fxTableItem->setGeometry(geometry);
+ fxTableItem->setVisible(true);
+
+ qCDebug(lcTableViewDelegateLifecycle()) << "layout item:"
+ << QPoint(column, row)
+ << fxTableItem->geometry()
+ << "adjust size:" << adjustSize;
+ }
+}
+
+void QQuickTableViewPrivate::layoutHorizontalEdge(Qt::Edge tableEdge, bool adjustSize)
+{
+ int row = (tableEdge == Qt::TopEdge) ? loadedTable.top() : loadedTable.bottom();
+ QPoint neighbourDirection = (tableEdge == Qt::TopEdge) ? kDown : kUp;
+ qreal height = adjustSize ? rowHeight(row) : 0;
+ qreal top = -1;
+
+ for (int column = loadedTable.left(); column <= loadedTable.right(); ++column) {
+ auto fxTableItem = loadedTableItem(QPoint(column, row));
+ auto const neighbourItem = itemNextTo(fxTableItem, neighbourDirection);
+ QRectF geometry = fxTableItem->geometry();
+
+ if (adjustSize) {
+ geometry.setWidth(neighbourItem->geometry().width());
+ geometry.setHeight(height);
+ }
+
+ if (top == -1) {
+ // top will be the same for all items in the
+ // row, so do the calculation once.
+ top = tableEdge == Qt::TopEdge ?
+ neighbourItem->geometry().top() - cellSpacing.height() - geometry.height() :
+ neighbourItem->geometry().bottom() + cellSpacing.height();
+ }
+
+ geometry.moveTop(top);
+ geometry.moveLeft(neighbourItem->geometry().left());
+
+ fxTableItem->setGeometry(geometry);
+ fxTableItem->setVisible(true);
+
+ qCDebug(lcTableViewDelegateLifecycle()) << "layout item:"
+ << QPoint(column, row)
+ << fxTableItem->geometry()
+ << "adjust size:" << adjustSize;
+ }
+}
+
+void QQuickTableViewPrivate::layoutTableEdgeFromLoadRequest()
+{
+ // If tableRebuilding is true, we avoid adjusting cell sizes until all items
+ // needed to fill up the viewport has been loaded. This way we can base the later
+ // relayoutTable() on the original size of the items. But we still need to
+ // position the items now at approximate positions so we know (roughly) how many
+ // rows/columns we need to load before we can consider the viewport as filled up.
+ // Only then will the table rebuilding be considered done, and relayoutTable() called.
+ // The relayout might cause new rows and columns to be loaded/unloaded depending on
+ // whether the new sizes reveals or hides already loaded rows/columns.
+ const bool adjustSize = !tableRebuilding;
+
+ switch (loadRequest.edge()) {
+ case Qt::LeftEdge:
+ case Qt::RightEdge:
+ layoutVerticalEdge(loadRequest.edge(), adjustSize);
+ break;
+ case Qt::TopEdge:
+ case Qt::BottomEdge:
+ layoutHorizontalEdge(loadRequest.edge(), adjustSize);
+ break;
+ default:
+ // Request loaded top-left item rather than an edge.
+ // ###todo: support starting with other top-left items than 0,0
+ Q_TABLEVIEW_ASSERT(loadRequest.firstCell() == QPoint(0, 0), loadRequest.toString());
+ loadedTableItem(QPoint(0, 0))->setVisible(true);
+ qCDebug(lcTableViewDelegateLifecycle) << "top-left item geometry:" << loadedTableItem(QPoint(0, 0))->geometry();
+ break;
+ }
+}
+
+void QQuickTableViewPrivate::cancelLoadRequest()
+{
+ loadRequest.markAsDone();
+ model->cancel(modelIndexAtCell(loadRequest.currentCell()));
+
+ if (tableInvalid) {
+ // No reason to rollback already loaded edge items
+ // since we anyway are about to reload all items.
+ return;
+ }
+
+ if (loadRequest.atBeginning()) {
+ // No items have yet been loaded, so nothing to unload
+ return;
+ }
+
+ QLine rollbackItems;
+ rollbackItems.setP1(loadRequest.firstCell());
+ rollbackItems.setP2(loadRequest.previousCell());
+ qCDebug(lcTableViewDelegateLifecycle()) << "rollback:" << rollbackItems << tableLayoutToString();
+ unloadItems(rollbackItems);
+}
+
+void QQuickTableViewPrivate::processLoadRequest()
+{
+ Q_TABLEVIEW_ASSERT(loadRequest.isActive(), "");
+
+ while (loadRequest.hasCurrentCell()) {
+ QPoint cell = loadRequest.currentCell();
+ FxTableItem *fxTableItem = loadFxTableItem(cell, loadRequest.incubationMode());
+
+ if (!fxTableItem) {
+ // Requested item is not yet ready. Just leave, and wait for this
+ // function to be called again when the item is ready.
+ return;
+ }
+
+ loadedItems.append(fxTableItem);
+ loadRequest.moveToNextCell();
+ }
+
+ qCDebug(lcTableViewDelegateLifecycle()) << "all items loaded!";
+
+ syncLoadedTableFromLoadRequest();
+ calculateEdgeSizeFromLoadRequest();
+ layoutTableEdgeFromLoadRequest();
+
+ syncLoadedTableRectFromLoadedTable();
+ enforceFirstRowColumnAtOrigo();
+ updateContentWidth();
+ updateContentHeight();
+
+ loadRequest.markAsDone();
+ qCDebug(lcTableViewDelegateLifecycle()) << "request completed! Table:" << tableLayoutToString();
+}
+
+void QQuickTableViewPrivate::beginRebuildTable()
+{
+ qCDebug(lcTableViewDelegateLifecycle());
+ clear();
+ tableInvalid = false;
+ tableRebuilding = true;
+ calculateTableSize();
+ loadInitialTopLeftItem();
+ loadAndUnloadVisibleEdges();
+}
+
+void QQuickTableViewPrivate::endRebuildTable()
+{
+ tableRebuilding = false;
+
+ if (loadedItems.isEmpty())
+ return;
+
+ // We don't calculate row/column sizes for lists.
+ // Instead we we use the sizes of the items directly
+ // unless for explicit row/column size assignments.
+ columnWidths.clear();
+ rowHeights.clear();
+ if (tableSize.height() > 1)
+ calculateColumnWidthsAfterRebuilding();
+ if (tableSize.width() > 1)
+ calculateRowHeightsAfterRebuilding();
+
+ relayoutTable();
+ qCDebug(lcTableViewDelegateLifecycle()) << tableLayoutToString();
+}
+
+void QQuickTableViewPrivate::loadInitialTopLeftItem()
+{
+ Q_TABLEVIEW_ASSERT(loadedItems.isEmpty(), "");
+
+ if (tableSize.isEmpty())
+ return;
+
+ // Load top-left item. After loaded, loadItemsInsideRect() will take
+ // care of filling out the rest of the table.
+ loadRequest.begin(QPoint(0, 0), QQmlIncubator::AsynchronousIfNested);
+ processLoadRequest();
+}
+
+void QQuickTableViewPrivate::unloadEdgesOutsideRect(const QRectF &rect)
+{
+ while (Qt::Edge edge = nextEdgeToUnload(rect)) {
+ unloadItems(rectangleEdge(loadedTable, edge));
+ loadedTable = expandedRect(loadedTable, edge, -1);
+ syncLoadedTableRectFromLoadedTable();
+ qCDebug(lcTableViewDelegateLifecycle) << tableLayoutToString();
+ }
+}
+
+void QQuickTableViewPrivate::loadEdgesInsideRect(const QRectF &rect, QQmlIncubator::IncubationMode incubationMode)
+{
+ while (Qt::Edge edge = nextEdgeToLoad(rect)) {
+ QLine cellsToLoad = rectangleEdge(expandedRect(loadedTable, edge, 1), edge);
+ loadRequest.begin(cellsToLoad, edge, incubationMode);
+ processLoadRequest();
+ if (loadRequest.isActive())
+ return;
+ }
+}
+
+void QQuickTableViewPrivate::loadAndUnloadVisibleEdges()
+{
+ // Unload table edges that have been moved outside the visible part of the
+ // table (including buffer area), and load new edges that has been moved inside.
+ // Note: an important point is that we always keep the table rectangular
+ // and without holes to reduce complexity (we never leave the table in
+ // a half-loaded state, or keep track of multiple patches).
+ // We load only one edge (row or column) at a time. This is especially
+ // important when loading into the buffer, since we need to be able to
+ // cancel the buffering quickly if the user starts to flick, and then
+ // focus all further loading on the edges that are flicked into view.
+
+ if (loadRequest.isActive()) {
+ // Don't start loading more edges while we're
+ // already waiting for another one to load.
+ return;
+ }
+
+ if (loadedItems.isEmpty()) {
+ // We need at least the top-left item to be loaded before we can
+ // start loading edges around it. Not having a top-left item at
+ // this point means that the model is empty (or row and and column
+ // is 0) since we have no active load request.
+ return;
+ }
+
+ unloadEdgesOutsideRect(hasBufferedItems ? bufferRect() : viewportRect);
+ loadEdgesInsideRect(viewportRect, QQmlIncubator::AsynchronousIfNested);
+}
+
+void QQuickTableViewPrivate::loadBuffer()
+{
+ // Rather than making sure to stop the timer from all locations that can
+ // violate the "buffering allowed" state, we just check that we're in the
+ // right state here before we start buffering.
+ if (cacheBuffer <= 0 || loadRequest.isActive() || loadedItems.isEmpty())
+ return;
+
+ qCDebug(lcTableViewDelegateLifecycle());
+ loadEdgesInsideRect(bufferRect(), QQmlIncubator::Asynchronous);
+ hasBufferedItems = true;
+}
+
+void QQuickTableViewPrivate::unloadBuffer()
+{
+ if (!hasBufferedItems)
+ return;
+
+ qCDebug(lcTableViewDelegateLifecycle());
+ hasBufferedItems = false;
+ cacheBufferDelayTimer.stop();
+ if (loadRequest.isActive())
+ cancelLoadRequest();
+ unloadEdgesOutsideRect(viewportRect);
+}
+
+QRectF QQuickTableViewPrivate::bufferRect()
+{
+ return viewportRect.adjusted(-cacheBuffer, -cacheBuffer, cacheBuffer, cacheBuffer);
+}
+
+void QQuickTableViewPrivate::invalidateTable() {
+ tableInvalid = true;
+ if (loadRequest.isActive())
+ cancelLoadRequest();
+ q_func()->polish();
+}
+
+void QQuickTableViewPrivate::invalidateColumnRowPositions() {
+ columnRowPositionsInvalid = true;
+ q_func()->polish();
+}
+
+void QQuickTableViewPrivate::updatePolish()
+{
+ // Whenever something changes, e.g viewport moves, spacing is set to a
+ // new value, model changes etc, this function will end up being called. Here
+ // we check what needs to be done, and load/unload cells accordingly.
+
+ if (loadRequest.isActive()) {
+ // We're currently loading items async to build a new edge in the table. We see the loading
+ // as an atomic operation, which means that we don't continue doing anything else until all
+ // items have been received and laid out. Note that updatePolish is then called once more
+ // after the loadRequest has completed to handle anything that might have occurred in-between.
+ return;
+ }
+
+ if (tableInvalid) {
+ beginRebuildTable();
+ if (loadRequest.isActive())
+ return;
+ }
+
+ if (tableRebuilding)
+ endRebuildTable();
+
+ if (loadedItems.isEmpty()) {
+ qCDebug(lcTableViewDelegateLifecycle()) << "no items loaded, meaning empty model or row/column == 0";
+ return;
+ }
+
+ if (columnRowPositionsInvalid)
+ relayoutTable();
+
+ if (hasBufferedItems && nextEdgeToLoad(viewportRect)) {
+ // We are about to load more edges, so trim down the table as much
+ // as possible to avoid loading cells that are outside the viewport.
+ unloadBuffer();
+ }
+
+ loadAndUnloadVisibleEdges();
+
+ if (loadRequest.isActive())
+ return;
+
+ if (cacheBuffer > 0) {
+ // When polish hasn't been called for a while (which means that the viewport
+ // rect hasn't changed), we start buffering items. We delay this operation by
+ // using a timer to increase performance (by not loading hidden items) while
+ // the user is flicking.
+ cacheBufferDelayTimer.start(kBufferTimerInterval);
+ }
+}
+
+void QQuickTableViewPrivate::createWrapperModel()
+{
+ Q_Q(QQuickTableView);
+
+ auto delegateModel = new QQmlDelegateModel(qmlContext(q), q);
+ if (q->isComponentComplete())
+ delegateModel->componentComplete();
+ model = delegateModel;
+}
+
+void QQuickTableViewPrivate::itemCreatedCallback(int modelIndex, QObject*)
+{
+ if (blockItemCreatedCallback)
+ return;
+
+ qCDebug(lcTableViewDelegateLifecycle) << "item done loading:"
+ << cellAtModelIndex(modelIndex);
+
+ // Since the item we waited for has finished incubating, we can
+ // continue with the load request. processLoadRequest will
+ // ask the model for the requested item once more, which will be
+ // quick since the model has cached it.
+ processLoadRequest();
+ loadAndUnloadVisibleEdges();
+ updatePolish();
+}
+
+void QQuickTableViewPrivate::initItemCallback(int modelIndex, QObject *object)
+{
+ Q_UNUSED(modelIndex);
+ QQuickItem *item = qmlobject_cast<QQuickItem *>(object);
+ if (!item)
+ return;
+
+ QObject *attachedObject = qmlAttachedPropertiesObject<QQuickTableView>(item);
+ if (QQuickTableViewAttached *attached = static_cast<QQuickTableViewAttached *>(attachedObject)) {
+ attached->setTableView(q_func());
+ // Even though row and column is injected directly into the context of a delegate item
+ // from QQmlDelegateModel and its model classes, they will only return which row and
+ // column an item represents in the model. This might be different from which
+ // cell an item ends up in in the Table, if a different rows/columns has been set
+ // on it (which is typically the case for list models). For those cases, Table.row
+ // and Table.column can be helpful.
+ QPoint cell = cellAtModelIndex(modelIndex);
+ attached->setColumn(cell.x());
+ attached->setRow(cell.y());
+ }
+}
+
+void QQuickTableViewPrivate::modelUpdated(const QQmlChangeSet &changeSet, bool reset)
+{
+ Q_UNUSED(changeSet);
+ Q_UNUSED(reset);
+ Q_Q(QQuickTableView);
+
+ if (!q->isComponentComplete())
+ return;
+
+ // Rudimentary solution for now until support for
+ // more fine-grained updates and transitions are implemented.
+ auto modelVariant = q->model();
+ q->setModel(QVariant());
+ q->setModel(modelVariant);
+}
+
+QQuickTableView::QQuickTableView(QQuickItem *parent)
+ : QQuickFlickable(*(new QQuickTableViewPrivate), parent)
+{
+}
+
+int QQuickTableView::rows() const
+{
+ return d_func()->tableSize.height();
+}
+
+int QQuickTableView::columns() const
+{
+ return d_func()->tableSize.width();
+}
+
+qreal QQuickTableView::rowSpacing() const
+{
+ return d_func()->cellSpacing.height();
+}
+
+void QQuickTableView::setRowSpacing(qreal spacing)
+{
+ Q_D(QQuickTableView);
+ if (qFuzzyCompare(d->cellSpacing.height(), spacing))
+ return;
+
+ d->cellSpacing.setHeight(spacing);
+ d->invalidateColumnRowPositions();
+ emit rowSpacingChanged();
+}
+
+qreal QQuickTableView::columnSpacing() const
+{
+ return d_func()->cellSpacing.width();
+}
+
+void QQuickTableView::setColumnSpacing(qreal spacing)
+{
+ Q_D(QQuickTableView);
+ if (qFuzzyCompare(d->cellSpacing.width(), spacing))
+ return;
+
+ d->cellSpacing.setWidth(spacing);
+ d->invalidateColumnRowPositions();
+ emit columnSpacingChanged();
+}
+
+int QQuickTableView::cacheBuffer() const
+{
+ return d_func()->cacheBuffer;
+}
+
+void QQuickTableView::setCacheBuffer(int newBuffer)
+{
+ Q_D(QQuickTableView);
+ if (d->cacheBuffer == newBuffer || newBuffer < 0)
+ return;
+
+ d->cacheBuffer = newBuffer;
+
+ if (newBuffer == 0)
+ d->unloadBuffer();
+
+ emit cacheBufferChanged();
+ polish();
+}
+
+QVariant QQuickTableView::model() const
+{
+ return d_func()->modelVariant;
+}
+
+void QQuickTableView::setModel(const QVariant &newModel)
+{
+ Q_D(QQuickTableView);
+
+ d->modelVariant = newModel;
+ QVariant effectiveModelVariant = d->modelVariant;
+ if (effectiveModelVariant.userType() == qMetaTypeId<QJSValue>())
+ effectiveModelVariant = effectiveModelVariant.value<QJSValue>().toVariant();
+
+ if (d->model) {
+ QObjectPrivate::disconnect(d->model, &QQmlInstanceModel::createdItem, d, &QQuickTableViewPrivate::itemCreatedCallback);
+ QObjectPrivate::disconnect(d->model, &QQmlInstanceModel::initItem, d, &QQuickTableViewPrivate::initItemCallback);
+ QObjectPrivate::disconnect(d->model, &QQmlInstanceModel::modelUpdated, d, &QQuickTableViewPrivate::modelUpdated);
+ }
+
+ const auto instanceModel = qobject_cast<QQmlInstanceModel *>(qvariant_cast<QObject*>(effectiveModelVariant));
+
+ if (instanceModel) {
+ if (d->wrapperModel())
+ delete d->model;
+ d->model = instanceModel;
+ } else {
+ if (!d->wrapperModel())
+ d->createWrapperModel();
+ d->wrapperModel()->setModel(effectiveModelVariant);
+ }
+
+ QObjectPrivate::connect(d->model, &QQmlInstanceModel::createdItem, d, &QQuickTableViewPrivate::itemCreatedCallback);
+ QObjectPrivate::connect(d->model, &QQmlInstanceModel::initItem, d, &QQuickTableViewPrivate::initItemCallback);
+ QObjectPrivate::connect(d->model, &QQmlInstanceModel::modelUpdated, d, &QQuickTableViewPrivate::modelUpdated);
+
+ d->invalidateTable();
+
+ emit modelChanged();
+}
+
+QQmlComponent *QQuickTableView::delegate() const
+{
+ if (auto wrapperModel = d_func()->wrapperModel())
+ return wrapperModel->delegate();
+
+ return nullptr;
+}
+
+void QQuickTableView::setDelegate(QQmlComponent *newDelegate)
+{
+ Q_D(QQuickTableView);
+ if (newDelegate == delegate())
+ return;
+
+ if (!d->wrapperModel())
+ d->createWrapperModel();
+
+ d->wrapperModel()->setDelegate(newDelegate);
+ d->invalidateTable();
+
+ emit delegateChanged();
+}
+
+QQuickTableViewAttached *QQuickTableView::qmlAttachedProperties(QObject *obj)
+{
+ return new QQuickTableViewAttached(obj);
+}
+
+void QQuickTableView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
+{
+ Q_D(QQuickTableView);
+ QQuickFlickable::geometryChanged(newGeometry, oldGeometry);
+
+ QRectF rect = QRectF(contentX(), contentY(), newGeometry.width(), newGeometry.height());
+ if (!rect.isValid())
+ return;
+
+ d->viewportRect = rect;
+ polish();
+}
+
+void QQuickTableView::viewportMoved(Qt::Orientations orientation)
+{
+ Q_D(QQuickTableView);
+ QQuickFlickable::viewportMoved(orientation);
+
+ d->viewportRect = QRectF(contentX(), contentY(), width(), height());
+ d->updatePolish();
+}
+
+void QQuickTableView::componentComplete()
+{
+ Q_D(QQuickTableView);
+
+ if (!d->model)
+ setModel(QVariant());
+
+ if (auto wrapperModel = d->wrapperModel())
+ wrapperModel->componentComplete();
+
+ QQuickFlickable::componentComplete();
+}
+
+#include "moc_qquicktableview_p.cpp"
+
+QT_END_NAMESPACE
diff --git a/src/quick/items/qquicktableview_p.h b/src/quick/items/qquicktableview_p.h
new file mode 100644
index 0000000000..13c164bd11
--- /dev/null
+++ b/src/quick/items/qquicktableview_p.h
@@ -0,0 +1,171 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQUICKTABLEVIEW_P_H
+#define QQUICKTABLEVIEW_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qpointer.h>
+#include <QtQuick/private/qtquickglobal_p.h>
+#include <QtQuick/private/qquickflickable_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQuickTableViewAttached;
+class QQuickTableViewPrivate;
+class QQmlChangeSet;
+
+class Q_QUICK_PRIVATE_EXPORT QQuickTableView : public QQuickFlickable
+{
+ Q_OBJECT
+
+ Q_PROPERTY(int rows READ rows NOTIFY rowsChanged)
+ Q_PROPERTY(int columns READ columns NOTIFY columnsChanged)
+ Q_PROPERTY(qreal rowSpacing READ rowSpacing WRITE setRowSpacing NOTIFY rowSpacingChanged)
+ Q_PROPERTY(qreal columnSpacing READ columnSpacing WRITE setColumnSpacing NOTIFY columnSpacingChanged)
+ Q_PROPERTY(int cacheBuffer READ cacheBuffer WRITE setCacheBuffer NOTIFY cacheBufferChanged)
+
+ Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged)
+ Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
+
+public:
+ QQuickTableView(QQuickItem *parent = nullptr);
+
+ int rows() const;
+ int columns() const;
+
+ qreal rowSpacing() const;
+ void setRowSpacing(qreal spacing);
+
+ qreal columnSpacing() const;
+ void setColumnSpacing(qreal spacing);
+
+ int cacheBuffer() const;
+ void setCacheBuffer(int newBuffer);
+
+ QVariant model() const;
+ void setModel(const QVariant &newModel);
+
+ QQmlComponent *delegate() const;
+ void setDelegate(QQmlComponent *);
+
+ static QQuickTableViewAttached *qmlAttachedProperties(QObject *);
+
+Q_SIGNALS:
+ void rowsChanged();
+ void columnsChanged();
+ void rowSpacingChanged();
+ void columnSpacingChanged();
+ void cacheBufferChanged();
+ void modelChanged();
+ void delegateChanged();
+
+protected:
+ void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
+ void viewportMoved(Qt::Orientations orientation) override;
+ void componentComplete() override;
+
+private:
+ Q_DISABLE_COPY(QQuickTableView)
+ Q_DECLARE_PRIVATE(QQuickTableView)
+};
+
+class Q_QUICK_PRIVATE_EXPORT QQuickTableViewAttached : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QQuickTableView *tableView READ tableView NOTIFY tableViewChanged)
+ Q_PROPERTY(int row READ row NOTIFY rowChanged)
+ Q_PROPERTY(int column READ column NOTIFY columnChanged)
+
+public:
+ QQuickTableViewAttached(QObject *parent)
+ : QObject(parent) {}
+
+ QQuickTableView *tableView() const { return m_tableview; }
+ void setTableView(QQuickTableView *newTableView) {
+ if (newTableView == m_tableview)
+ return;
+ m_tableview = newTableView;
+ Q_EMIT tableViewChanged();
+ }
+
+ int row() const { return m_row; }
+ void setRow(int newRow) {
+ if (newRow == m_row)
+ return;
+ m_row = newRow;
+ Q_EMIT rowChanged();
+ }
+
+ int column() const { return m_column; }
+ void setColumn(int newColumn) {
+ if (newColumn == m_column)
+ return;
+ m_column = newColumn;
+ Q_EMIT columnChanged();
+ }
+
+Q_SIGNALS:
+ void tableViewChanged();
+ void rowChanged();
+ void columnChanged();
+
+private:
+ QPointer<QQuickTableView> m_tableview;
+ int m_row = -1;
+ int m_column = -1;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQuickTableView)
+QML_DECLARE_TYPEINFO(QQuickTableView, QML_HAS_ATTACHED_PROPERTIES)
+
+#endif // QQUICKTABLEVIEW_P_H