summaryrefslogtreecommitdiffstats
path: root/src/quick/items
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2019-05-02 15:13:49 +0200
committerLaszlo Agocs <laszlo.agocs@qt.io>2019-05-02 15:14:36 +0200
commit44ca7e31ee9365a72cd17ecd335ec4d0161420a9 (patch)
treec03b4f8263b41986b6f5d4ac9c6e35e371e98a33 /src/quick/items
parent580fa7dc88aae23053e44ffa335a15f6af112a20 (diff)
parent325e6305b418ffe1dfb9a36c2516c6a8a3de5733 (diff)
Merge remote-tracking branch 'origin/dev' into wip/scenegraphng
Diffstat (limited to 'src/quick/items')
-rw-r--r--src/quick/items/qquickevents.cpp38
-rw-r--r--src/quick/items/qquickevents_p_p.h1
-rw-r--r--src/quick/items/qquickitemsmodule.cpp9
-rw-r--r--src/quick/items/qquickitemview_p_p.h6
-rw-r--r--src/quick/items/qquickrepeater.cpp1
-rw-r--r--src/quick/items/qquicktableview.cpp527
-rw-r--r--src/quick/items/qquicktableview_p.h10
-rw-r--r--src/quick/items/qquicktableview_p_p.h45
-rw-r--r--src/quick/items/qquickwindow.cpp2
9 files changed, 532 insertions, 107 deletions
diff --git a/src/quick/items/qquickevents.cpp b/src/quick/items/qquickevents.cpp
index c43eab6b8a..28b217b7b3 100644
--- a/src/quick/items/qquickevents.cpp
+++ b/src/quick/items/qquickevents.cpp
@@ -1135,10 +1135,10 @@ void QQuickEventTouchPoint::reset(const QTouchEvent::TouchPoint &tp, ulong times
struct PointVelocityData {
QVector2D velocity;
QPointF pos;
- ulong timestamp;
+ ulong timestamp = 0;
};
-typedef QMap<quint64, PointVelocityData*> PointDataForPointIdMap;
+typedef QMap<quint64, PointVelocityData> PointDataForPointIdMap;
Q_GLOBAL_STATIC(PointDataForPointIdMap, g_previousPointData)
static const int PointVelocityAgeLimit = 500; // milliseconds
@@ -1149,42 +1149,36 @@ static const int PointVelocityAgeLimit = 500; // milliseconds
*/
QVector2D QQuickEventPoint::estimatedVelocity() const
{
- PointVelocityData *prevPoint = g_previousPointData->value(m_pointId);
- if (!prevPoint) {
+ auto prevPointIt = g_previousPointData->find(m_pointId);
+ auto end = g_previousPointData->end();
+ if (prevPointIt == end) {
// cleanup events older than PointVelocityAgeLimit
- auto end = g_previousPointData->end();
for (auto it = g_previousPointData->begin(); it != end; ) {
- PointVelocityData *data = it.value();
- if (m_timestamp - data->timestamp > PointVelocityAgeLimit) {
+ if (m_timestamp - it->timestamp > PointVelocityAgeLimit)
it = g_previousPointData->erase(it);
- delete data;
- } else {
+ else
++it;
- }
}
- // TODO optimize: stop this dynamic memory thrashing
- prevPoint = new PointVelocityData;
- prevPoint->velocity = QVector2D();
- prevPoint->timestamp = 0;
- prevPoint->pos = QPointF();
- g_previousPointData->insert(m_pointId, prevPoint);
+ prevPointIt = g_previousPointData->insert(m_pointId, PointVelocityData());
}
- const ulong timeElapsed = m_timestamp - prevPoint->timestamp;
+
+ auto &prevPoint = prevPointIt.value();
+ const ulong timeElapsed = m_timestamp - prevPoint.timestamp;
if (timeElapsed == 0) // in case we call estimatedVelocity() twice on the same QQuickEventPoint
return m_velocity;
QVector2D newVelocity;
- if (prevPoint->timestamp != 0)
- newVelocity = QVector2D(m_scenePos - prevPoint->pos)/timeElapsed;
+ if (prevPoint.timestamp != 0)
+ newVelocity = QVector2D(m_scenePos - prevPoint.pos) / timeElapsed;
// VERY simple kalman filter: does a weighted average
// where the older velocities get less and less significant
static const float KalmanGain = 0.7f;
QVector2D filteredVelocity = newVelocity * KalmanGain + m_velocity * (1.0f - KalmanGain);
- prevPoint->velocity = filteredVelocity;
- prevPoint->pos = m_scenePos;
- prevPoint->timestamp = m_timestamp;
+ prevPoint.velocity = filteredVelocity;
+ prevPoint.pos = m_scenePos;
+ prevPoint.timestamp = m_timestamp;
return filteredVelocity;
}
diff --git a/src/quick/items/qquickevents_p_p.h b/src/quick/items/qquickevents_p_p.h
index e614b1bd6d..1a3737091f 100644
--- a/src/quick/items/qquickevents_p_p.h
+++ b/src/quick/items/qquickevents_p_p.h
@@ -605,6 +605,7 @@ private:
bool m_inverted = false;
friend class QQuickWindowPrivate;
+ friend class QQuickWheelHandler;
Q_DISABLE_COPY(QQuickPointerScrollEvent)
};
diff --git a/src/quick/items/qquickitemsmodule.cpp b/src/quick/items/qquickitemsmodule.cpp
index cd5cca5ac9..2a1c442653 100644
--- a/src/quick/items/qquickitemsmodule.cpp
+++ b/src/quick/items/qquickitemsmodule.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQuick module of the Qt Toolkit.
@@ -120,6 +120,7 @@
#include "handlers/qquickpinchhandler_p.h"
#include "handlers/qquickpointhandler_p.h"
#include "handlers/qquicktaphandler_p.h"
+#include "handlers/qquickwheelhandler_p.h"
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(lcTransient)
@@ -475,6 +476,12 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor)
qmlRegisterUncreatableType<QQuickItemView, 13>(uri, 2, 13, itemViewName, itemViewMessage);
qmlRegisterType<QQuickPathView, 13>(uri, 2, 13, "PathView");
qmlRegisterType<QQuickGridView, 13>(uri, 2, 13, "GridView");
+#if QT_CONFIG(quick_tableview)
+ qmlRegisterType<QQuickTableView, 14>(uri, 2, 14, "TableView");
+#endif
+#if QT_CONFIG(wheelevent)
+ qmlRegisterType<QQuickWheelHandler>(uri, 2, 14, "WheelHandler");
+#endif
}
static void initResources()
diff --git a/src/quick/items/qquickitemview_p_p.h b/src/quick/items/qquickitemview_p_p.h
index ea5b5df9c6..0f1594f904 100644
--- a/src/quick/items/qquickitemview_p_p.h
+++ b/src/quick/items/qquickitemview_p_p.h
@@ -59,9 +59,9 @@ QT_REQUIRE_CONFIG(quick_itemview);
#include "qquickitemviewfxitem_p_p.h"
#include "qquickitemviewtransition_p.h"
#include "qquickflickable_p_p.h"
-#include <QtQml/private/qqmlobjectmodel_p.h>
-#include <QtQml/private/qqmldelegatemodel_p.h>
-#include <QtQml/private/qqmlchangeset_p.h>
+#include <QtQmlModels/private/qqmlobjectmodel_p.h>
+#include <QtQmlModels/private/qqmldelegatemodel_p.h>
+#include <QtQmlModels/private/qqmlchangeset_p.h>
QT_BEGIN_NAMESPACE
diff --git a/src/quick/items/qquickrepeater.cpp b/src/quick/items/qquickrepeater.cpp
index 805b6fe190..c8a03aff33 100644
--- a/src/quick/items/qquickrepeater.cpp
+++ b/src/quick/items/qquickrepeater.cpp
@@ -41,7 +41,6 @@
#include "qquickrepeater_p_p.h"
#include <private/qqmlglobal_p.h>
-#include <private/qqmllistaccessor_p.h>
#include <private/qqmlchangeset_p.h>
#include <private/qqmldelegatemodel_p.h>
diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp
index 1dca5bea4a..8f5130fc17 100644
--- a/src/quick/items/qquicktableview.cpp
+++ b/src/quick/items/qquicktableview.cpp
@@ -42,10 +42,10 @@
#include <QtCore/qtimer.h>
#include <QtCore/qdir.h>
-#include <QtQml/private/qqmldelegatemodel_p.h>
-#include <QtQml/private/qqmldelegatemodel_p_p.h>
+#include <QtQmlModels/private/qqmldelegatemodel_p.h>
+#include <QtQmlModels/private/qqmldelegatemodel_p_p.h>
#include <QtQml/private/qqmlincubator_p.h>
-#include <QtQml/private/qqmlchangeset_p.h>
+#include <QtQmlModels/private/qqmlchangeset_p.h>
#include <QtQml/qqmlinfo.h>
#include <QtQuick/private/qquickflickable_p_p.h>
@@ -76,14 +76,23 @@
\section1 Example Usage
+ \section2 C++ Models
+
The following example shows how to create a model from C++ with multiple
columns:
- \snippet qml/tableview/tablemodel.cpp 0
+ \snippet qml/tableview/cpp-tablemodel.cpp 0
And then how to use it from QML:
- \snippet qml/tableview/tablemodel.qml 0
+ \snippet qml/tableview/cpp-tablemodel.qml 0
+
+ \section2 QML Models
+
+ For prototyping and displaying very simple data (from a web API, for
+ example), \l TableModel can be used:
+
+ \snippet qml/tableview/qml-tablemodel.qml 0
\section1 Reusing items
@@ -606,6 +615,11 @@ void QQuickTableViewPrivate::updateContentWidth()
{
Q_Q(QQuickTableView);
+ if (syncHorizontally) {
+ q->QQuickFlickable::setContentWidth(syncView->contentWidth());
+ return;
+ }
+
if (explicitContentWidth.isValid()) {
// Don't calculate contentWidth when it
// was set explicitly by the application.
@@ -625,6 +639,11 @@ void QQuickTableViewPrivate::updateContentHeight()
{
Q_Q(QQuickTableView);
+ if (syncVertically) {
+ q->QQuickFlickable::setContentHeight(syncView->contentHeight());
+ return;
+ }
+
if (explicitContentHeight.isValid()) {
// Don't calculate contentHeight when it
// was set explicitly by the application.
@@ -1048,6 +1067,11 @@ qreal QQuickTableViewPrivate::getColumnLayoutWidth(int column)
if (explicitColumnWidth >= 0)
return explicitColumnWidth;
+ if (syncHorizontally) {
+ if (syncView->d_func()->loadedColumns.contains(column))
+ return syncView->d_func()->getColumnLayoutWidth(column);
+ }
+
// Iterate over the currently visible items in the column. The downside
// of doing that, is that the column width will then only be based on the implicit
// width of the currently loaded items (which can be different depending on which
@@ -1077,6 +1101,11 @@ qreal QQuickTableViewPrivate::getRowLayoutHeight(int row)
if (explicitRowHeight >= 0)
return explicitRowHeight;
+ if (syncVertically) {
+ if (syncView->d_func()->loadedRows.contains(row))
+ return syncView->d_func()->getRowLayoutHeight(row);
+ }
+
// Iterate over the currently visible items in the row. The downside
// of doing that, is that the row height will then only be based on the implicit
// height of the currently loaded items (which can be different depending on which
@@ -1106,6 +1135,9 @@ qreal QQuickTableViewPrivate::getColumnWidth(int column)
if (cachedColumnWidth.startIndex == column)
return cachedColumnWidth.size;
+ if (syncHorizontally)
+ return syncView->d_func()->getColumnWidth(column);
+
if (columnWidthProvider.isUndefined())
return noExplicitColumnWidth;
@@ -1140,6 +1172,9 @@ qreal QQuickTableViewPrivate::getRowHeight(int row)
if (cachedRowHeight.startIndex == row)
return cachedRowHeight.size;
+ if (syncVertically)
+ return syncView->d_func()->getRowHeight(row);
+
if (rowHeightProvider.isUndefined())
return noExplicitRowHeight;
@@ -1489,48 +1524,103 @@ bool QQuickTableViewPrivate::moveToNextRebuildState()
return true;
}
-QPoint QQuickTableViewPrivate::calculateNewTopLeft()
-{
- const int firstVisibleLeft = nextVisibleEdgeIndex(Qt::RightEdge, 0);
- const int firstVisibleTop = nextVisibleEdgeIndex(Qt::BottomEdge, 0);
-
- return QPoint(firstVisibleLeft, firstVisibleTop);
-}
-
-void QQuickTableViewPrivate::calculateTopLeft(QPoint &topLeft, QPointF &topLeftPos)
+void QQuickTableViewPrivate::calculateTopLeft(QPoint &topLeftCell, QPointF &topLeftPos)
{
if (tableSize.isEmpty()) {
- releaseLoadedItems(QQmlTableInstanceModel::NotReusable);
- topLeft = QPoint(kEdgeIndexAtEnd, kEdgeIndexAtEnd);
+ // There is no cell that can be top left
+ topLeftCell.rx() = kEdgeIndexAtEnd;
+ topLeftCell.ry() = kEdgeIndexAtEnd;
return;
}
- if (rebuildOptions & RebuildOption::All) {
- qCDebug(lcTableViewDelegateLifecycle()) << "RebuildOption::All";
- releaseLoadedItems(QQmlTableInstanceModel::NotReusable);
- topLeft = calculateNewTopLeft();
- } else if (rebuildOptions & RebuildOption::ViewportOnly) {
- qCDebug(lcTableViewDelegateLifecycle()) << "RebuildOption::ViewportOnly";
- releaseLoadedItems(reusableFlag);
+ if (syncHorizontally || syncVertically) {
+ const auto syncView_d = syncView->d_func();
- if (rebuildOptions & RebuildOption::CalculateNewTopLeftRow) {
- const int newRow = int(viewportRect.y() / (averageEdgeSize.height() + cellSpacing.height()));
- topLeft.ry() = qBound(0, newRow, tableSize.height() - 1);
- topLeftPos.ry() = topLeft.y() * (averageEdgeSize.height() + cellSpacing.height());
- } else {
- topLeft.ry() = qBound(0, topRow(), tableSize.height() - 1);
- topLeftPos.ry() = loadedTableOuterRect.topLeft().y();
+ if (syncView_d->loadedItems.isEmpty()) {
+ // The sync view contains no loaded items. This probably means
+ // that it has not been rebuilt yet. Which also means that
+ // we cannot rebuild anything before this happens.
+ topLeftCell.rx() = kEdgeIndexNotSet;
+ topLeftCell.ry() = kEdgeIndexNotSet;
+ return;
+ }
+
+ // Get sync view top left, and use that as our own top left (if possible)
+ const QPoint syncViewTopLeftCell(syncView_d->leftColumn(), syncView_d->topRow());
+ const auto syncViewTopLeftFxItem = syncView_d->loadedTableItem(syncViewTopLeftCell);
+ const QPointF syncViewTopLeftPos = syncViewTopLeftFxItem->geometry().topLeft();
+
+ if (syncHorizontally) {
+ topLeftCell.rx() = syncViewTopLeftCell.x();
+ topLeftPos.rx() = syncViewTopLeftPos.x();
+
+ if (topLeftCell.x() >= tableSize.width()) {
+ // Top left is outside our own model.
+ topLeftCell.rx() = kEdgeIndexAtEnd;
+ topLeftPos.rx() = kEdgeIndexAtEnd;
+ }
}
- if (rebuildOptions & RebuildOption::CalculateNewTopLeftColumn) {
+
+ if (syncVertically) {
+ topLeftCell.ry() = syncViewTopLeftCell.y();
+ topLeftPos.ry() = syncViewTopLeftPos.y();
+
+ if (topLeftCell.y() >= tableSize.height()) {
+ // Top left is outside our own model.
+ topLeftCell.ry() = kEdgeIndexAtEnd;
+ topLeftPos.ry() = kEdgeIndexAtEnd;
+ }
+ }
+
+ if (syncHorizontally && syncVertically) {
+ // We have a valid top left, so we're done
+ return;
+ }
+ }
+
+ // Since we're not sync-ing both horizontal and vertical, calculate the missing
+ // dimention(s) ourself. If we rebuild all, we find the first visible top-left
+ // item starting from cell(0, 0). Otherwise, guesstimate which row or column that
+ // should be the new top-left given the geometry of the viewport.
+
+ if (!syncHorizontally) {
+ if (rebuildOptions & RebuildOption::All) {
+ // Find the first visible column from the beginning
+ topLeftCell.rx() = nextVisibleEdgeIndex(Qt::RightEdge, 0);
+ if (topLeftCell.x() == kEdgeIndexAtEnd) {
+ // No visible column found
+ return;
+ }
+ } else if (rebuildOptions & RebuildOption::CalculateNewTopLeftColumn) {
+ // Guesstimate new top left
const int newColumn = int(viewportRect.x() / (averageEdgeSize.width() + cellSpacing.width()));
- topLeft.rx() = qBound(0, newColumn, tableSize.width() - 1);
- topLeftPos.rx() = topLeft.x() * (averageEdgeSize.width() + cellSpacing.width());
+ topLeftCell.rx() = qBound(0, newColumn, tableSize.width() - 1);
+ topLeftPos.rx() = topLeftCell.x() * (averageEdgeSize.width() + cellSpacing.width());
} else {
- topLeft.rx() = qBound(0, leftColumn(), tableSize.width() - 1);
+ // Keep the current top left, unless it's outside model
+ topLeftCell.rx() = qBound(0, leftColumn(), tableSize.width() - 1);
topLeftPos.rx() = loadedTableOuterRect.topLeft().x();
}
- } else {
- Q_TABLEVIEW_UNREACHABLE(rebuildOptions);
+ }
+
+ if (!syncVertically) {
+ if (rebuildOptions & RebuildOption::All) {
+ // Find the first visible row from the beginning
+ topLeftCell.ry() = nextVisibleEdgeIndex(Qt::BottomEdge, 0);
+ if (topLeftCell.y() == kEdgeIndexAtEnd) {
+ // No visible row found
+ return;
+ }
+ } else if (rebuildOptions & RebuildOption::CalculateNewTopLeftRow) {
+ // Guesstimate new top left
+ const int newRow = int(viewportRect.y() / (averageEdgeSize.height() + cellSpacing.height()));
+ topLeftCell.ry() = qBound(0, newRow, tableSize.height() - 1);
+ topLeftPos.ry() = topLeftCell.y() * (averageEdgeSize.height() + cellSpacing.height());
+ } else {
+ // Keep the current top left, unless it's outside model
+ topLeftCell.ry() = qBound(0, topRow(), tableSize.height() - 1);
+ topLeftPos.ry() = loadedTableOuterRect.topLeft().y();
+ }
}
}
@@ -1542,12 +1632,22 @@ void QQuickTableViewPrivate::beginRebuildTable()
QPointF topLeftPos;
calculateTopLeft(topLeft, topLeftPos);
+ if (rebuildOptions & RebuildOption::All)
+ releaseLoadedItems(QQmlTableInstanceModel::NotReusable);
+ else if (rebuildOptions & RebuildOption::ViewportOnly)
+ releaseLoadedItems(reusableFlag);
+
loadedColumns.clear();
loadedRows.clear();
loadedTableOuterRect = QRect();
loadedTableInnerRect = QRect();
clearEdgeSizeCache();
+ if (syncHorizontally)
+ setLocalViewportX(syncView->contentX());
+ if (syncVertically)
+ setLocalViewportY(syncView->contentY());
+
if (!model) {
qCDebug(lcTableViewDelegateLifecycle()) << "no model found, leaving table empty";
return;
@@ -1564,7 +1664,12 @@ void QQuickTableViewPrivate::beginRebuildTable()
}
if (topLeft.x() == kEdgeIndexAtEnd || topLeft.y() == kEdgeIndexAtEnd) {
- qCDebug(lcTableViewDelegateLifecycle()) << "no visible rows or columns, leaving table empty";
+ qCDebug(lcTableViewDelegateLifecycle()) << "no visible row or column found, leaving table empty";
+ return;
+ }
+
+ if (topLeft.x() == kEdgeIndexNotSet || topLeft.y() == kEdgeIndexNotSet) {
+ qCDebug(lcTableViewDelegateLifecycle()) << "could not resolve top-left item, leaving table empty";
return;
}
@@ -1733,11 +1838,59 @@ void QQuickTableViewPrivate::scheduleRebuildTable(RebuildOptions options) {
q_func()->polish();
}
+QQuickTableView *QQuickTableViewPrivate::rootSyncView() const
+{
+ QQuickTableView *root = const_cast<QQuickTableView *>(q_func());
+ while (QQuickTableView *view = root->d_func()->syncView)
+ root = view;
+ return root;
+}
+
void QQuickTableViewPrivate::updatePolish()
{
+ // We always start updating from the top of the syncView tree, since
+ // the layout of a syncView child will depend on the layout of the syncView.
+ // E.g when a new column is flicked in, the syncView should load and layout
+ // the column first, before any syncChildren gets a chance to do the same.
+ Q_TABLEVIEW_ASSERT(!polishing, "recursive updatePolish() calls are not allowed!");
+ rootSyncView()->d_func()->updateTableRecursive();
+}
+
+bool QQuickTableViewPrivate::updateTableRecursive()
+{
+ if (polishing) {
+ // We're already updating the Table in this view, so
+ // we cannot continue. Signal this back by returning false.
+ // The caller can then choose to call "polish()" instead, to
+ // do the update later.
+ return false;
+ }
+
+ const bool updateComplete = updateTable();
+ if (!updateComplete)
+ return false;
+
+ for (auto syncChild : qAsConst(syncChildren)) {
+ auto syncChild_d = syncChild->d_func();
+ syncChild_d->scheduledRebuildOptions |= rebuildOptions;
+
+ const bool descendantUpdateComplete = syncChild_d->updateTableRecursive();
+ if (!descendantUpdateComplete)
+ return false;
+ }
+
+ rebuildOptions = RebuildOption::None;
+
+ return true;
+}
+
+bool QQuickTableViewPrivate::updateTable()
+{
// 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 we cannot complete the update (because we need to wait for an item
+ // to load async), we return false.
Q_TABLEVIEW_ASSERT(!polishing, "recursive updatePolish() calls are not allowed!");
QBoolBlocker polishGuard(polishing, true);
@@ -1747,25 +1900,27 @@ void QQuickTableViewPrivate::updatePolish()
// 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;
+ return false;
}
if (rebuildState != RebuildState::Done) {
processRebuildTable();
- return;
+ return rebuildState == RebuildState::Done;
}
syncWithPendingChanges();
if (rebuildState == RebuildState::Begin) {
processRebuildTable();
- return;
+ return rebuildState == RebuildState::Done;
}
if (loadedItems.isEmpty())
- return;
+ return !loadRequest.isActive();
loadAndUnloadVisibleEdges();
+
+ return !loadRequest.isActive();
}
void QQuickTableViewPrivate::fixup(QQuickFlickablePrivate::AxisData &data, qreal minExtent, qreal maxExtent)
@@ -1855,9 +2010,14 @@ void QQuickTableViewPrivate::syncWithPendingChanges()
// such assignments into effect until we're in a state that allows it.
Q_Q(QQuickTableView);
viewportRect = QRectF(q->contentX(), q->contentY(), q->width(), q->height());
+
+ // Sync rebuild options first, in case we schedule a rebuild from one of the
+ // other sync calls above. If so, we need to start a new rebuild from the top.
syncRebuildOptions();
+
syncModel();
syncDelegate();
+ syncSyncView();
}
void QQuickTableViewPrivate::syncRebuildOptions()
@@ -1922,6 +2082,46 @@ void QQuickTableViewPrivate::syncModel()
connectToModel();
}
+void QQuickTableViewPrivate::syncSyncView()
+{
+ Q_Q(QQuickTableView);
+
+ if (assignedSyncView != syncView) {
+ if (syncView)
+ syncView->d_func()->syncChildren.removeOne(q);
+
+ if (assignedSyncView) {
+ QQuickTableView *view = assignedSyncView;
+
+ while (view) {
+ if (view == q) {
+ if (!layoutWarningIssued) {
+ layoutWarningIssued = true;
+ qmlWarning(q) << "TableView: recursive syncView connection detected!";
+ }
+ syncView = nullptr;
+ return;
+ }
+ view = view->d_func()->syncView;
+ }
+
+ assignedSyncView->d_func()->syncChildren.append(q);
+ scheduledRebuildOptions |= RebuildOption::ViewportOnly;
+ q->polish();
+ }
+
+ syncView = assignedSyncView;
+ }
+
+ syncHorizontally = syncView && assignedSyncDirection & Qt::Horizontal;
+ syncVertically = syncView && assignedSyncDirection & Qt::Vertical;
+
+ if (syncHorizontally)
+ q->setColumnSpacing(syncView->columnSpacing());
+ if (syncVertically)
+ q->setRowSpacing(syncView->rowSpacing());
+}
+
void QQuickTableViewPrivate::connectToModel()
{
Q_TABLEVIEW_ASSERT(model, "");
@@ -2051,6 +2251,84 @@ void QQuickTableViewPrivate::modelResetCallback()
scheduleRebuildTable(RebuildOption::All);
}
+void QQuickTableViewPrivate::scheduleRebuildIfFastFlick()
+{
+ Q_Q(QQuickTableView);
+
+ // If the viewport has moved more than one page vertically or horizontally, we switch
+ // strategy from refilling edges around the current table to instead rebuild the table
+ // from scratch inside the new viewport. This will greatly improve performance when flicking
+ // a long distance in one go, which can easily happen when dragging on scrollbars.
+
+ // Check the viewport moved more than one page vertically
+ if (!viewportRect.intersects(QRectF(viewportRect.x(), q->contentY(), 1, q->height()))) {
+ scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftRow;
+ scheduledRebuildOptions |= RebuildOption::ViewportOnly;
+ }
+
+ // Check the viewport moved more than one page horizontally
+ if (!viewportRect.intersects(QRectF(q->contentX(), viewportRect.y(), q->width(), 1))) {
+ scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftColumn;
+ scheduledRebuildOptions |= RebuildOption::ViewportOnly;
+ }
+}
+
+void QQuickTableViewPrivate::setLocalViewportX(qreal contentX)
+{
+ // Set the new viewport position if changed, but don't trigger any
+ // rebuilds or updates. We use this function internally to distinguish
+ // external flicking from internal sync-ing of the content view.
+ Q_Q(QQuickTableView);
+ QBoolBlocker blocker(inSetLocalViewportPos, true);
+
+ if (qFuzzyCompare(contentX, q->contentX()))
+ return;
+
+ q->setContentX(contentX);
+}
+
+void QQuickTableViewPrivate::setLocalViewportY(qreal contentY)
+{
+ // Set the new viewport position if changed, but don't trigger any
+ // rebuilds or updates. We use this function internally to distinguish
+ // external flicking from internal sync-ing of the content view.
+ Q_Q(QQuickTableView);
+ QBoolBlocker blocker(inSetLocalViewportPos, true);
+
+ if (qFuzzyCompare(contentY, q->contentY()))
+ return;
+
+ q->setContentY(contentY);
+}
+
+void QQuickTableViewPrivate::syncViewportPosRecursive()
+{
+ Q_Q(QQuickTableView);
+ QBoolBlocker recursionGuard(inSyncViewportPosRecursive, true);
+
+ if (syncView) {
+ auto syncView_d = syncView->d_func();
+ if (!syncView_d->inSyncViewportPosRecursive) {
+ if (syncHorizontally)
+ syncView_d->setLocalViewportX(q->contentX());
+ if (syncVertically)
+ syncView_d->setLocalViewportY(q->contentY());
+ syncView_d->syncViewportPosRecursive();
+ }
+ }
+
+ for (auto syncChild : qAsConst(syncChildren)) {
+ auto syncChild_d = syncChild->d_func();
+ if (!syncChild_d->inSyncViewportPosRecursive) {
+ if (syncChild_d->syncHorizontally)
+ syncChild_d->setLocalViewportX(q->contentX());
+ if (syncChild_d->syncVertically)
+ syncChild_d->setLocalViewportY(q->contentY());
+ syncChild_d->syncViewportPosRecursive();
+ }
+ }
+}
+
QQuickTableView::QQuickTableView(QQuickItem *parent)
: QQuickFlickable(*(new QQuickTableViewPrivate), parent)
{
@@ -2214,6 +2492,41 @@ void QQuickTableView::setContentHeight(qreal height)
QQuickFlickable::setContentHeight(height);
}
+QQuickTableView *QQuickTableView::syncView() const
+{
+ return d_func()->assignedSyncView;
+}
+
+void QQuickTableView::setSyncView(QQuickTableView *view)
+{
+ Q_D(QQuickTableView);
+ if (d->assignedSyncView == view)
+ return;
+
+ d->assignedSyncView = view;
+ d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly);
+
+ emit syncViewChanged();
+}
+
+Qt::Orientations QQuickTableView::syncDirection() const
+{
+ return d_func()->assignedSyncDirection;
+}
+
+void QQuickTableView::setSyncDirection(Qt::Orientations direction)
+{
+ Q_D(QQuickTableView);
+ if (d->assignedSyncDirection == direction)
+ return;
+
+ d->assignedSyncDirection = direction;
+ if (d->assignedSyncView)
+ d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly);
+
+ emit syncDirectionChanged();
+}
+
void QQuickTableView::forceLayout()
{
d_func()->forceLayout();
@@ -2241,45 +2554,42 @@ void QQuickTableView::geometryChanged(const QRectF &newGeometry, const QRectF &o
void QQuickTableView::viewportMoved(Qt::Orientations orientation)
{
Q_D(QQuickTableView);
+
+ // If the new viewport position was set from the setLocalViewportXY()
+ // functions, we just update the position silently and return. Otherwise, if
+ // the viewport was flicked by the user, or some other control, we
+ // recursively sync all the views in the hierarchy to the same position.
QQuickFlickable::viewportMoved(orientation);
+ if (d->inSetLocalViewportPos)
+ return;
- QQuickTableViewPrivate::RebuildOptions options = QQuickTableViewPrivate::RebuildOption::None;
+ // Move all views in the syncView hierarchy to the same contentX/Y.
+ // We need to start from this view (and not the root syncView) to
+ // ensure that we respect all the individual syncDirection flags
+ // between the individual views in the hierarchy.
+ d->syncViewportPosRecursive();
- // Check the viewport moved more than one page vertically
- if (!d->viewportRect.intersects(QRectF(d->viewportRect.x(), contentY(), 1, height())))
- options |= QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftRow;
- // Check the viewport moved more than one page horizontally
- if (!d->viewportRect.intersects(QRectF(contentX(), d->viewportRect.y(), width(), 1)))
- options |= QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftColumn;
-
- if (options) {
- // When the viewport has moved more than one page vertically or horizontally, we switch
- // strategy from refilling edges around the current table to instead rebuild the table
- // from scratch inside the new viewport. This will greatly improve performance when flicking
- // a long distance in one go, which can easily happen when dragging on scrollbars.
- options |= QQuickTableViewPrivate::RebuildOption::ViewportOnly;
- d->scheduleRebuildTable(options);
- }
-
- if (d->scheduledRebuildOptions) {
- // No reason to do anything, since we're about to rebuild the whole table anyway.
- // Besides, calling updatePolish, which will start the rebuild, can easily cause
- // binding loops to happen since we usually end up modifying the geometry of the
- // viewport (contentItem) as well.
- return;
- }
+ auto rootView = d->rootSyncView();
+ auto rootView_d = rootView->d_func();
- // Calling polish() will schedule a polish event. But while the user is flicking, several
- // mouse events will be handled before we get an updatePolish() call. And the updatePolish()
- // call will only see the last mouse position. This results in a stuttering flick experience
- // (especially on windows). We improve on this by calling updatePolish() directly. But this
- // has the pitfall that we open up for recursive callbacks. E.g while inside updatePolish(), we
- // load/unload items, and emit signals. The application can listen to those signals and set a
- // new contentX/Y on the flickable. So we need to guard for this, to avoid unexpected behavior.
- if (d->polishing)
- polish();
- else
- d->updatePolish();
+ rootView_d->scheduleRebuildIfFastFlick();
+
+ if (!rootView_d->polishScheduled) {
+ if (rootView_d->scheduledRebuildOptions) {
+ // When we need to rebuild, collecting several viewport
+ // moves and do a single polish gives a quicker UI.
+ rootView->polish();
+ } else {
+ // Updating the table right away when flicking
+ // slowly gives a smoother experience.
+ const bool updated = rootView->d_func()->updateTableRecursive();
+ if (!updated) {
+ // One, or more, of the views are already in an
+ // update, so we need to wait a cycle.
+ rootView->polish();
+ }
+ }
+ }
}
void QQuickTableViewPrivate::_q_componentFinalized()
@@ -2315,6 +2625,71 @@ void QQuickTableView::componentComplete()
d_func()->registerCallbackWhenBindingsAreEvaluated();
}
+class QObjectPrivate;
+class QQuickTableSectionSizeProviderPrivate : public QObjectPrivate {
+public:
+ QQuickTableSectionSizeProviderPrivate();
+ ~QQuickTableSectionSizeProviderPrivate();
+ QHash<int, qreal> hash;
+};
+
+QQuickTableSectionSizeProvider::QQuickTableSectionSizeProvider(QObject *parent)
+ : QObject (*(new QQuickTableSectionSizeProviderPrivate), parent)
+{
+}
+
+void QQuickTableSectionSizeProvider::setSize(int section, qreal size)
+{
+ Q_D(QQuickTableSectionSizeProvider);
+ if (section < 0 || size < 0) {
+ qmlWarning(this) << "setSize: section or size less than zero";
+ return;
+ }
+ if (qFuzzyCompare(QQuickTableSectionSizeProvider::size(section), size))
+ return;
+ d->hash.insert(section, size);
+ emit sizeChanged();
+}
+
+// return -1.0 if no valid explicit size retrieved
+qreal QQuickTableSectionSizeProvider::size(int section)
+{
+ Q_D(QQuickTableSectionSizeProvider);
+ auto it = d->hash.find(section);
+ if (it != d->hash.end())
+ return *it;
+ return -1.0;
+}
+
+// return true if section is valid
+bool QQuickTableSectionSizeProvider::resetSize(int section)
+{
+ Q_D(QQuickTableSectionSizeProvider);
+ if (d->hash.empty())
+ return false;
+
+ auto ret = d->hash.remove(section);
+ if (ret)
+ emit sizeChanged();
+ return ret;
+}
+
+void QQuickTableSectionSizeProvider::resetAll()
+{
+ Q_D(QQuickTableSectionSizeProvider);
+ d->hash.clear();
+ emit sizeChanged();
+}
+
+QQuickTableSectionSizeProviderPrivate::QQuickTableSectionSizeProviderPrivate()
+ : QObjectPrivate()
+{
+}
+
+QQuickTableSectionSizeProviderPrivate::~QQuickTableSectionSizeProviderPrivate()
+{
+
+}
#include "moc_qquicktableview_p.cpp"
QT_END_NAMESPACE
diff --git a/src/quick/items/qquicktableview_p.h b/src/quick/items/qquicktableview_p.h
index f32c71b983..3d46221574 100644
--- a/src/quick/items/qquicktableview_p.h
+++ b/src/quick/items/qquicktableview_p.h
@@ -79,6 +79,8 @@ class Q_QUICK_PRIVATE_EXPORT QQuickTableView : public QQuickFlickable
Q_PROPERTY(bool reuseItems READ reuseItems WRITE setReuseItems NOTIFY reuseItemsChanged)
Q_PROPERTY(qreal contentWidth READ contentWidth WRITE setContentWidth NOTIFY contentWidthChanged)
Q_PROPERTY(qreal contentHeight READ contentHeight WRITE setContentHeight NOTIFY contentHeightChanged)
+ Q_PROPERTY(QQuickTableView *syncView READ syncView WRITE setSyncView NOTIFY syncViewChanged REVISION 14)
+ Q_PROPERTY(Qt::Orientations syncDirection READ syncDirection WRITE setSyncDirection NOTIFY syncDirectionChanged REVISION 14)
public:
QQuickTableView(QQuickItem *parent = nullptr);
@@ -110,6 +112,12 @@ public:
void setContentWidth(qreal width);
void setContentHeight(qreal height);
+ QQuickTableView *syncView() const;
+ void setSyncView(QQuickTableView *view);
+
+ Qt::Orientations syncDirection() const;
+ void setSyncDirection(Qt::Orientations direction);
+
Q_INVOKABLE void forceLayout();
static QQuickTableViewAttached *qmlAttachedProperties(QObject *);
@@ -124,6 +132,8 @@ Q_SIGNALS:
void modelChanged();
void delegateChanged();
void reuseItemsChanged();
+ Q_REVISION(14) void syncViewChanged();
+ Q_REVISION(14) void syncDirectionChanged();
protected:
void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
diff --git a/src/quick/items/qquicktableview_p_p.h b/src/quick/items/qquicktableview_p_p.h
index 6bbfd4e2d6..7f2aee9105 100644
--- a/src/quick/items/qquicktableview_p_p.h
+++ b/src/quick/items/qquicktableview_p_p.h
@@ -54,9 +54,9 @@
#include "qquicktableview_p.h"
#include <QtCore/qtimer.h>
-#include <QtQml/private/qqmltableinstancemodel_p.h>
+#include <QtQmlModels/private/qqmltableinstancemodel_p.h>
#include <QtQml/private/qqmlincubator_p.h>
-#include <QtQml/private/qqmlchangeset_p.h>
+#include <QtQmlModels/private/qqmlchangeset_p.h>
#include <QtQml/qqmlinfo.h>
#include <QtQuick/private/qquickflickable_p_p.h>
@@ -70,6 +70,25 @@ static const qreal kDefaultRowHeight = 50;
static const qreal kDefaultColumnWidth = 50;
class FxTableItem;
+class QQuickTableSectionSizeProviderPrivate;
+
+class Q_QUICK_PRIVATE_EXPORT QQuickTableSectionSizeProvider : public QObject {
+ Q_OBJECT
+
+public:
+ QQuickTableSectionSizeProvider(QObject *parent=nullptr);
+ void setSize(int section, qreal size);
+ qreal size(int section);
+ bool resetSize(int section);
+ void resetAll();
+
+Q_SIGNALS:
+ void sizeChanged();
+
+private:
+ Q_DISABLE_COPY(QQuickTableSectionSizeProvider)
+ Q_DECLARE_PRIVATE(QQuickTableSectionSizeProvider)
+};
class Q_QML_AUTOTEST_EXPORT QQuickTableViewPrivate : public QQuickFlickablePrivate
{
@@ -249,9 +268,15 @@ public:
bool blockItemCreatedCallback = false;
bool layoutWarningIssued = false;
bool polishing = false;
+ bool syncVertically = false;
+ bool syncHorizontally = false;
+ bool inSetLocalViewportPos = false;
+ bool inSyncViewportPosRecursive = false;
QJSValue rowHeightProvider;
QJSValue columnWidthProvider;
+ QQuickTableSectionSizeProvider rowHeights;
+ QQuickTableSectionSizeProvider columnWidths;
EdgeRange cachedNextVisibleEdgeIndex[4];
EdgeRange cachedColumnWidth;
@@ -271,6 +296,11 @@ public:
QSizeF averageEdgeSize;
+ QPointer<QQuickTableView> assignedSyncView;
+ QPointer<QQuickTableView> syncView;
+ QList<QPointer<QQuickTableView> > syncChildren;
+ Qt::Orientations assignedSyncDirection = Qt::Horizontal | Qt::Vertical;
+
const static QPoint kLeft;
const static QPoint kRight;
const static QPoint kUp;
@@ -303,6 +333,10 @@ public:
inline int leftColumn() const { return loadedColumns.firstKey(); }
inline int rightColumn() const { return loadedColumns.lastKey(); }
+ QQuickTableView *rootSyncView() const;
+
+ bool updateTableRecursive();
+ bool updateTable();
void relayoutTable();
void relayoutTableItems();
@@ -349,7 +383,6 @@ public:
void processRebuildTable();
bool moveToNextRebuildState();
- QPoint calculateNewTopLeft();
void calculateTopLeft(QPoint &topLeft, QPointF &topLeftPos);
void beginRebuildTable();
void layoutAfterLoadingInitialTable();
@@ -369,6 +402,7 @@ public:
inline void syncDelegate();
inline void syncModel();
inline void syncRebuildOptions();
+ inline void syncSyncView();
void connectToModel();
void disconnectFromModel();
@@ -382,6 +416,11 @@ public:
void layoutChangedCallback(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint);
void modelResetCallback();
+ void scheduleRebuildIfFastFlick();
+ void setLocalViewportX(qreal contentX);
+ void setLocalViewportY(qreal contentY);
+ void syncViewportPosRecursive();
+
void _q_componentFinalized();
void registerCallbackWhenBindingsAreEvaluated();
diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp
index 9d0f4a6893..f3a2b07620 100644
--- a/src/quick/items/qquickwindow.cpp
+++ b/src/quick/items/qquickwindow.cpp
@@ -2551,7 +2551,7 @@ bool QQuickWindowPrivate::deliverPressOrReleaseEvent(QQuickPointerEvent *event,
if (point->grabberPointerHandler())
cancelTouchMouseSynthesis();
} else {
- qCWarning(DBG_TOUCH_TARGET) << "during delivery of touch press, synth-mouse ID" << touchMouseId << "is missing from" << event;
+ qCWarning(DBG_TOUCH_TARGET) << "during delivery of touch press, synth-mouse ID" << hex << touchMouseId << "is missing from" << event;
}
}
for (int i = 0; i < pointCount; ++i) {