Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qquicktableview.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquicktableview_p.h"
6
7#include <QtCore/qtimer.h>
8#include <QtCore/qdir.h>
9#include <QtQmlModels/private/qqmldelegatemodel_p.h>
10#include <QtQmlModels/private/qqmldelegatemodel_p_p.h>
11#include <QtQml/private/qqmlincubator_p.h>
12#include <QtQmlModels/private/qqmlchangeset_p.h>
13#include <QtQml/qqmlinfo.h>
14
15#include <QtQuick/private/qquickflickable_p_p.h>
16#include <QtQuick/private/qquickitemviewfxitem_p_p.h>
17#include <QtQuick/private/qquicktaphandler_p.h>
18
1450
1451Q_LOGGING_CATEGORY(lcTableViewDelegateLifecycle, "qt.quick.tableview.lifecycle")
1452
1453#define Q_TABLEVIEW_UNREACHABLE(output) { dumpTable(); qWarning() << "output:" << output; Q_UNREACHABLE(); }
1454#define Q_TABLEVIEW_ASSERT(cond, output) Q_ASSERT((cond) || [&](){ dumpTable(); qWarning() << "output:" << output; return false;}())
1455
1457
1458static const char* kRequiredProperties = "_qt_tableview_requiredpropertymask";
1459static const char* kRequiredProperty_selected = "selected";
1460static const char* kRequiredProperty_current = "current";
1461static const char* kRequiredProperty_editing = "editing";
1462
1464 : startIndex(kEdgeIndexNotSet)
1465 , endIndex(kEdgeIndexNotSet)
1466 , size(0)
1467{}
1468
1470{
1471 if (startIndex == kEdgeIndexNotSet)
1472 return false;
1473
1474 if (endIndex == kEdgeIndexAtEnd) {
1475 switch (edge) {
1476 case Qt::LeftEdge:
1477 case Qt::TopEdge:
1478 return index <= startIndex;
1479 case Qt::RightEdge:
1480 case Qt::BottomEdge:
1481 return index >= startIndex;
1482 }
1483 }
1484
1485 const int s = std::min(startIndex, endIndex);
1486 const int e = std::max(startIndex, endIndex);
1487 return index >= s && index <= e;
1488}
1489
1492{
1493}
1494
1496{
1497 if (editItem) {
1498 QQuickItem *cellItem = editItem->parentItem();
1499 Q_ASSERT(cellItem);
1502 }
1503
1504 if (editModel)
1505 delete editModel;
1506
1507 for (auto *fxTableItem : loadedItems) {
1508 if (auto item = fxTableItem->item) {
1509 if (fxTableItem->ownItem)
1510 delete item;
1511 else if (tableModel)
1513 }
1514 delete fxTableItem;
1515 }
1516
1517 if (tableModel)
1518 delete tableModel;
1519}
1520
1522{
1523 if (loadedItems.isEmpty())
1524 return QLatin1String("table is empty!");
1525 return QString(QLatin1String("table cells: (%1,%2) -> (%3,%4), item count: %5, table rect: %6,%7 x %8,%9"))
1526 .arg(leftColumn()).arg(topRow())
1528 .arg(loadedItems.size())
1533}
1534
1536{
1537 auto listCopy = loadedItems.values();
1538 std::stable_sort(listCopy.begin(), listCopy.end(),
1539 [](const FxTableItem *lhs, const FxTableItem *rhs)
1540 { return lhs->index < rhs->index; });
1541
1542 qWarning() << QStringLiteral("******* TABLE DUMP *******");
1543 for (int i = 0; i < listCopy.size(); ++i)
1544 qWarning() << static_cast<FxTableItem *>(listCopy.at(i))->cell;
1546
1547 const QString filename = QStringLiteral("QQuickTableView_dumptable_capture.png");
1548 const QString path = QDir::current().absoluteFilePath(filename);
1549 if (q_func()->window() && q_func()->window()->grabWindow().save(path))
1550 qWarning() << "Window capture saved to:" << path;
1551}
1552
1554 const QVariant &value, int serializedModelIndex, QObject *object, bool init)
1555{
1556 Q_Q(QQuickTableView);
1557
1558 QQmlTableInstanceModel *tableInstanceModel = qobject_cast<QQmlTableInstanceModel *>(model);
1559 if (!tableInstanceModel) {
1560 // TableView only supports using required properties when backed by
1561 // a QQmlTableInstanceModel. This is almost always the case, except
1562 // if you assign it an ObjectModel or a DelegateModel (which are really
1563 // not supported by TableView, it expects a QAIM).
1564 return;
1565 }
1566
1567 // Attaching a property list to the delegate item is just a
1568 // work-around until QMetaProperty::isRequired() works (QTBUG-98846).
1569 const QString propertyName = QString::fromUtf8(property);
1570
1571 if (init) {
1572 bool wasRequired = false;
1573 if (object == editItem) {
1574 // Special case: the item that we should write to belongs to the edit
1575 // model rather than 'model' (which is used for normal delegate items).
1576 wasRequired = editModel->setRequiredProperty(serializedModelIndex, propertyName, value);
1577 } else {
1578 wasRequired = tableInstanceModel->setRequiredProperty(serializedModelIndex, propertyName, value);
1579 }
1580 if (wasRequired) {
1581 QStringList propertyList = object->property(kRequiredProperties).toStringList();
1582 object->setProperty(kRequiredProperties, propertyList << propertyName);
1583 }
1584 } else {
1585 {
1586 const QStringList propertyList = object->property(kRequiredProperties).toStringList();
1587 if (propertyList.contains(propertyName)) {
1588 const auto metaObject = object->metaObject();
1589 const int propertyIndex = metaObject->indexOfProperty(property);
1590 const auto metaProperty = metaObject->property(propertyIndex);
1591 metaProperty.write(object, value);
1592 }
1593 }
1594
1595 if (editItem) {
1596 // Whenever we're told to update a required property for a table item that has the
1597 // same model index as the edit item, we also mirror that update to the edit item.
1598 // As such, this function is never called for the edit item directly (except the
1599 // first time when it needs to be initialized).
1600 Q_TABLEVIEW_ASSERT(object != editItem, "");
1601 const QModelIndex modelIndex = q->modelIndex(cellAtModelIndex(serializedModelIndex));
1602 if (modelIndex == editIndex) {
1604 if (propertyList.contains(propertyName)) {
1605 const auto metaObject = editItem->metaObject();
1606 const int propertyIndex = metaObject->indexOfProperty(property);
1607 const auto metaProperty = metaObject->property(propertyIndex);
1608 metaProperty.write(editItem, value);
1609 }
1610 }
1611 }
1612
1613 }
1614}
1615
1617{
1618 return const_cast<QQuickTableView *>(q_func())->contentItem();
1619}
1620
1622{
1623 Q_Q(QQuickTableView);
1624 Q_UNUSED(pos);
1625
1627 qmlWarning(q) << "Cannot start selection: TableView.selectionBehavior == TableView.SelectionDisabled";
1628 return false;
1629 }
1630
1631 // Only allow a selection if it doesn't conflict with resizing
1633 return false;
1634
1635 // For SingleSelection and ContiguousSelection, we should only allow one selection at a time
1639
1640 selectionStartCell = QPoint(-1, -1);
1641 selectionEndCell = QPoint(-1, -1);
1642 q->closeEditor();
1643 return true;
1644}
1645
1647{
1648 if (loadedItems.isEmpty())
1649 return;
1650 if (!selectionModel) {
1652 qmlWarning(q_func()) << "Cannot set selection: no SelectionModel assigned!";
1653 warnNoSelectionModel = false;
1654 return;
1655 }
1657 if (!qaim)
1658 return;
1659
1662 return;
1663 }
1664
1665 const QRect prevSelection = selection();
1666 const QPoint clampedCell = clampedCellAtPos(pos);
1667 if (!cellIsValid(clampedCell))
1668 return;
1669
1670 setCurrentIndex(clampedCell);
1671
1672 switch (selectionBehavior) {
1674 selectionStartCell = clampedCell;
1675 break;
1677 selectionStartCell = QPoint(0, clampedCell.y());
1678 break;
1680 selectionStartCell = QPoint(clampedCell.x(), 0);
1681 break;
1683 return;
1684 }
1685
1687 return;
1688
1689 // Update selection model
1690 updateSelection(prevSelection, selection());
1691}
1692
1694{
1695 if (loadedItems.isEmpty())
1696 return;
1697 if (!selectionModel) {
1699 qmlWarning(q_func()) << "Cannot set selection: no SelectionModel assigned!";
1700 warnNoSelectionModel = false;
1701 return;
1702 }
1704 if (!qaim)
1705 return;
1706
1707 const QRect prevSelection = selection();
1708
1709 QPoint clampedCell;
1711 clampedCell = selectionStartCell;
1712 } else {
1713 clampedCell = clampedCellAtPos(pos);
1714 if (!cellIsValid(clampedCell))
1715 return;
1716 }
1717
1718 setCurrentIndex(clampedCell);
1719
1720 switch (selectionBehavior) {
1722 selectionEndCell = clampedCell;
1723 break;
1725 selectionEndCell = QPoint(tableSize.width() - 1, clampedCell.y());
1726 break;
1728 selectionEndCell = QPoint(clampedCell.x(), tableSize.height() - 1);
1729 break;
1731 return;
1732 }
1733
1735 return;
1736
1737 // Update selection model
1738 updateSelection(prevSelection, selection());
1739}
1740
1742{
1743 Q_Q(const QQuickTableView);
1744
1745 // Note: pos should be relative to selectionPointerHandlerTarget()
1746 QPoint cell = q->cellAtPosition(pos, true);
1747 if (cellIsValid(cell))
1748 return cell;
1749
1750 // Clamp the cell to the loaded table and the viewport, whichever is the smallest
1751 QPointF clampedPos(
1754 QPointF clampedPosInView = q->mapFromItem(selectionPointerHandlerTarget(), clampedPos);
1755 clampedPosInView.rx() = qBound(0., clampedPosInView.x(), viewportRect.width());
1756 clampedPosInView.ry() = qBound(0., clampedPosInView.y(), viewportRect.height());
1757 clampedPos = q->mapToItem(selectionPointerHandlerTarget(), clampedPosInView);
1758
1759 return q->cellAtPosition(clampedPos, true);
1760}
1761
1762void QQuickTableViewPrivate::updateSelection(const QRect &oldSelection, const QRect &newSelection)
1763{
1765 const QRect oldRect = oldSelection.normalized();
1766 const QRect newRect = newSelection.normalized();
1767
1768 // Select cells inside the new selection rect
1769 {
1770 const QModelIndex startIndex = qaim->index(newRect.y(), newRect.x());
1771 const QModelIndex endIndex = qaim->index(newRect.y() + newRect.height(), newRect.x() + newRect.width());
1773 }
1774
1775 // Unselect cells in the new minus old rects
1776 if (oldRect.x() < newRect.x()) {
1777 const QModelIndex startIndex = qaim->index(oldRect.y(), oldRect.x());
1778 const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), newRect.x() - 1);
1780 } else if (oldRect.x() + oldRect.width() > newRect.x() + newRect.width()) {
1781 const QModelIndex startIndex = qaim->index(oldRect.y(), newRect.x() + newRect.width() + 1);
1782 const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), oldRect.x() + oldRect.width());
1784 }
1785
1786 if (oldRect.y() < newRect.y()) {
1787 const QModelIndex startIndex = qaim->index(oldRect.y(), oldRect.x());
1788 const QModelIndex endIndex = qaim->index(newRect.y() - 1, oldRect.x() + oldRect.width());
1790 } else if (oldRect.y() + oldRect.height() > newRect.y() + newRect.height()) {
1791 const QModelIndex startIndex = qaim->index(newRect.y() + newRect.height() + 1, oldRect.x());
1792 const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), oldRect.x() + oldRect.width());
1794 }
1795}
1796
1798{
1799 selectionStartCell = QPoint(-1, -1);
1800 selectionEndCell = QPoint(-1, -1);
1801
1802 if (selectionModel)
1804}
1805
1807{
1808 // Normalize the selection if necessary, so that the start cell is to the left
1809 // and above the end cell. This is typically done after a selection drag has
1810 // finished so that the start and end positions up in sync with the handles.
1811 // This will not cause any changes to the selection itself.
1813 std::swap(selectionStartCell.rx(), selectionEndCell.rx());
1815 std::swap(selectionStartCell.ry(), selectionEndCell.ry());
1816}
1817
1819{
1820 Q_Q(const QQuickTableView);
1821
1822 QPoint topLeftCell = selectionStartCell;
1823 QPoint bottomRightCell = selectionEndCell;
1824 if (bottomRightCell.x() < topLeftCell.x())
1825 std::swap(topLeftCell.rx(), bottomRightCell.rx());
1826 if (selectionEndCell.y() < topLeftCell.y())
1827 std::swap(topLeftCell.ry(), bottomRightCell.ry());
1828
1829 const QPoint leftCell(topLeftCell.x(), topRow());
1830 const QPoint topCell(leftColumn(), topLeftCell.y());
1831 const QPoint rightCell(bottomRightCell.x(), topRow());
1832 const QPoint bottomCell(leftColumn(), bottomRightCell.y());
1833
1834 // If the corner cells of the selection are loaded, we can position the
1835 // selection rectangle at its exact location. Otherwise we extend it out
1836 // to the edges of the content item. This is not ideal, but the best we
1837 // can do while the location of the corner cells are unknown.
1838 // This will at least move the selection handles (and other overlay) out
1839 // of the viewport until the affected cells are eventually loaded.
1840 int left = 0;
1841 int top = 0;
1842 int right = 0;
1843 int bottom = 0;
1844
1845 if (loadedItems.contains(modelIndexAtCell(leftCell)))
1846 left = loadedTableItem(leftCell)->geometry().left();
1847 else if (leftCell.x() > rightColumn())
1848 left = q->contentWidth();
1849
1851 top = loadedTableItem(topCell)->geometry().top();
1852 else if (topCell.y() > bottomRow())
1853 top = q->contentHeight();
1854
1855 if (loadedItems.contains(modelIndexAtCell(rightCell)))
1856 right = loadedTableItem(rightCell)->geometry().right();
1857 else if (rightCell.x() > rightColumn())
1858 right = q->contentWidth();
1859
1860 if (loadedItems.contains(modelIndexAtCell(bottomCell)))
1861 bottom = loadedTableItem(bottomCell)->geometry().bottom();
1862 else if (bottomCell.y() > bottomRow())
1863 bottom = q->contentHeight();
1864
1865 return QRectF(left, top, right - left, bottom - top);
1866}
1867
1869{
1873}
1874
1876{
1877 Q_Q(QQuickTableView);
1878
1879 if (loadedItems.isEmpty())
1880 return QSizeF();
1881
1882 // Scroll the content item towards pos.
1883 // Return the distance in pixels from the edge of the viewport to pos.
1884 // The caller will typically use this information to throttle the scrolling speed.
1885 // If pos is already inside the viewport, or the viewport is scrolled all the way
1886 // to the end, we return 0.
1887 QSizeF dist(0, 0);
1888
1889 const bool outsideLeft = pos.x() < viewportRect.x();
1890 const bool outsideRight = pos.x() >= viewportRect.right() - 1;
1891 const bool outsideTop = pos.y() < viewportRect.y();
1892 const bool outsideBottom = pos.y() >= viewportRect.bottom() - 1;
1893
1894 if (outsideLeft) {
1895 const bool firstColumnLoaded = atTableEnd(Qt::LeftEdge);
1896 const qreal remainingDist = viewportRect.left() - loadedTableOuterRect.left();
1897 if (remainingDist > 0 || !firstColumnLoaded) {
1898 qreal stepX = step.width();
1899 if (firstColumnLoaded)
1900 stepX = qMin(stepX, remainingDist);
1901 q->setContentX(q->contentX() - stepX);
1902 dist.setWidth(pos.x() - viewportRect.left() - 1);
1903 }
1904 } else if (outsideRight) {
1905 const bool lastColumnLoaded = atTableEnd(Qt::RightEdge);
1906 const qreal remainingDist = loadedTableOuterRect.right() - viewportRect.right();
1907 if (remainingDist > 0 || !lastColumnLoaded) {
1908 qreal stepX = step.width();
1909 if (lastColumnLoaded)
1910 stepX = qMin(stepX, remainingDist);
1911 q->setContentX(q->contentX() + stepX);
1912 dist.setWidth(pos.x() - viewportRect.right() - 1);
1913 }
1914 }
1915
1916 if (outsideTop) {
1917 const bool firstRowLoaded = atTableEnd(Qt::TopEdge);
1918 const qreal remainingDist = viewportRect.top() - loadedTableOuterRect.top();
1919 if (remainingDist > 0 || !firstRowLoaded) {
1920 qreal stepY = step.height();
1921 if (firstRowLoaded)
1922 stepY = qMin(stepY, remainingDist);
1923 q->setContentY(q->contentY() - stepY);
1924 dist.setHeight(pos.y() - viewportRect.top() - 1);
1925 }
1926 } else if (outsideBottom) {
1927 const bool lastRowLoaded = atTableEnd(Qt::BottomEdge);
1928 const qreal remainingDist = loadedTableOuterRect.bottom() - viewportRect.bottom();
1929 if (remainingDist > 0 || !lastRowLoaded) {
1930 qreal stepY = step.height();
1931 if (lastRowLoaded)
1932 stepY = qMin(stepY, remainingDist);
1933 q->setContentY(q->contentY() + stepY);
1934 dist.setHeight(pos.y() - viewportRect.bottom() - 1);
1935 }
1936 }
1937
1938 return dist;
1939}
1940
1942{
1943 QObject *attachedObject = qmlAttachedPropertiesObject<QQuickTableView>(object);
1944 return static_cast<QQuickTableViewAttached *>(attachedObject);
1945}
1946
1948{
1949 // QQmlTableInstanceModel expects index to be in column-major
1950 // order. This means that if the view is transposed (with a flipped
1951 // width and height), we need to calculate it in row-major instead.
1952 if (isTransposed) {
1953 int availableColumns = tableSize.width();
1954 return (cell.y() * availableColumns) + cell.x();
1955 } else {
1956 int availableRows = tableSize.height();
1957 return (cell.x() * availableRows) + cell.y();
1958 }
1959}
1960
1962{
1963 // QQmlTableInstanceModel expects index to be in column-major
1964 // order. This means that if the view is transposed (with a flipped
1965 // width and height), we need to calculate it in row-major instead.
1966 if (isTransposed) {
1967 int availableColumns = tableSize.width();
1968 int row = int(modelIndex / availableColumns);
1969 int column = modelIndex % availableColumns;
1970 return QPoint(column, row);
1971 } else {
1972 int availableRows = tableSize.height();
1973 int column = int(modelIndex / availableRows);
1974 int row = modelIndex % availableRows;
1975 return QPoint(column, row);
1976 }
1977}
1978
1980{
1981 // Convert QModelIndex to cell index. A cell index is just an
1982 // integer representation of a cell instead of using a QPoint.
1983 const QPoint cell = q_func()->cellAtIndex(modelIndex);
1984 if (!cellIsValid(cell))
1985 return -1;
1986 return modelIndexAtCell(cell);
1987}
1988
1990{
1991 return int(log2(float(edge)));
1992}
1993
1995{
1998
1999 for (Qt::Edge edge : allTableEdges)
2001}
2002
2004{
2005 // Find the next column (or row) around the loaded table that is
2006 // visible, and should be loaded next if the content item moves.
2007 int startIndex = -1;
2008 switch (edge) {
2009 case Qt::LeftEdge: startIndex = leftColumn() - 1; break;
2010 case Qt::RightEdge: startIndex = rightColumn() + 1; break;
2011 case Qt::TopEdge: startIndex = topRow() - 1; break;
2012 case Qt::BottomEdge: startIndex = bottomRow() + 1; break;
2013 }
2014
2015 return nextVisibleEdgeIndex(edge, startIndex);
2016}
2017
2019{
2020 // First check if we have already searched for the first visible index
2021 // after the given startIndex recently, and if so, return the cached result.
2022 // The cached result is valid if startIndex is inside the range between the
2023 // startIndex and the first visible index found after it.
2024 auto &cachedResult = cachedNextVisibleEdgeIndex[edgeToArrayIndex(edge)];
2025 if (cachedResult.containsIndex(edge, startIndex))
2026 return cachedResult.endIndex;
2027
2028 // Search for the first column (or row) in the direction of edge that is
2029 // visible, starting from the given column (startIndex).
2030 int foundIndex = kEdgeIndexNotSet;
2031 int testIndex = startIndex;
2032
2033 switch (edge) {
2034 case Qt::LeftEdge: {
2035 forever {
2036 if (testIndex < 0) {
2037 foundIndex = kEdgeIndexAtEnd;
2038 break;
2039 }
2040
2041 if (!isColumnHidden(testIndex)) {
2042 foundIndex = testIndex;
2043 break;
2044 }
2045
2046 --testIndex;
2047 }
2048 break; }
2049 case Qt::RightEdge: {
2050 forever {
2051 if (testIndex > tableSize.width() - 1) {
2052 foundIndex = kEdgeIndexAtEnd;
2053 break;
2054 }
2055
2056 if (!isColumnHidden(testIndex)) {
2057 foundIndex = testIndex;
2058 break;
2059 }
2060
2061 ++testIndex;
2062 }
2063 break; }
2064 case Qt::TopEdge: {
2065 forever {
2066 if (testIndex < 0) {
2067 foundIndex = kEdgeIndexAtEnd;
2068 break;
2069 }
2070
2071 if (!isRowHidden(testIndex)) {
2072 foundIndex = testIndex;
2073 break;
2074 }
2075
2076 --testIndex;
2077 }
2078 break; }
2079 case Qt::BottomEdge: {
2080 forever {
2081 if (testIndex > tableSize.height() - 1) {
2082 foundIndex = kEdgeIndexAtEnd;
2083 break;
2084 }
2085
2086 if (!isRowHidden(testIndex)) {
2087 foundIndex = testIndex;
2088 break;
2089 }
2090
2091 ++testIndex;
2092 }
2093 break; }
2094 }
2095
2096 cachedResult.startIndex = startIndex;
2097 cachedResult.endIndex = foundIndex;
2098 return foundIndex;
2099}
2100
2102{
2103 // Note that we actually never really know what the content size / size of the full table will
2104 // be. Even if e.g spacing changes, and we normally would assume that the size of the table
2105 // would increase accordingly, the model might also at some point have removed/hidden/resized
2106 // rows/columns outside the viewport. This would also affect the size, but since we don't load
2107 // rows or columns outside the viewport, this information is ignored. And even if we did, we
2108 // might also have been fast-flicked to a new location at some point, and started a new rebuild
2109 // there based on a new guesstimated top-left cell. So the calculated content size should always
2110 // be understood as a guesstimate, which sometimes can be really off (as a tradeoff for performance).
2111 // When this is not acceptable, the user can always set a custom content size explicitly.
2112 Q_Q(QQuickTableView);
2113
2114 if (syncHorizontally) {
2115 QBoolBlocker fixupGuard(inUpdateContentSize, true);
2116 q->QQuickFlickable::setContentWidth(syncView->contentWidth());
2117 return;
2118 }
2119
2121 // Don't calculate contentWidth when it
2122 // was set explicitly by the application.
2123 return;
2124 }
2125
2126 if (loadedItems.isEmpty()) {
2127 QBoolBlocker fixupGuard(inUpdateContentSize, true);
2128 if (model && model->count() > 0 && tableModel && tableModel->delegate())
2129 q->QQuickFlickable::setContentWidth(kDefaultColumnWidth);
2130 else
2131 q->QQuickFlickable::setContentWidth(0);
2132 return;
2133 }
2134
2136 const int columnsRemaining = nextColumn == kEdgeIndexAtEnd ? 0 : tableSize.width() - nextColumn;
2137 const qreal remainingColumnWidths = columnsRemaining * averageEdgeSize.width();
2138 const qreal remainingSpacing = columnsRemaining * cellSpacing.width();
2139 const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing;
2140 const qreal estimatedWidth = loadedTableOuterRect.right() + estimatedRemainingWidth;
2141
2142 QBoolBlocker fixupGuard(inUpdateContentSize, true);
2143 q->QQuickFlickable::setContentWidth(estimatedWidth);
2144}
2145
2147{
2148 Q_Q(QQuickTableView);
2149
2150 if (syncVertically) {
2151 QBoolBlocker fixupGuard(inUpdateContentSize, true);
2152 q->QQuickFlickable::setContentHeight(syncView->contentHeight());
2153 return;
2154 }
2155
2157 // Don't calculate contentHeight when it
2158 // was set explicitly by the application.
2159 return;
2160 }
2161
2162 if (loadedItems.isEmpty()) {
2163 QBoolBlocker fixupGuard(inUpdateContentSize, true);
2164 if (model && model->count() > 0 && tableModel && tableModel->delegate())
2165 q->QQuickFlickable::setContentHeight(kDefaultRowHeight);
2166 else
2167 q->QQuickFlickable::setContentHeight(0);
2168 return;
2169 }
2170
2172 const int rowsRemaining = nextRow == kEdgeIndexAtEnd ? 0 : tableSize.height() - nextRow;
2173 const qreal remainingRowHeights = rowsRemaining * averageEdgeSize.height();
2174 const qreal remainingSpacing = rowsRemaining * cellSpacing.height();
2175 const qreal estimatedRemainingHeight = remainingRowHeights + remainingSpacing;
2176 const qreal estimatedHeight = loadedTableOuterRect.bottom() + estimatedRemainingHeight;
2177
2178 QBoolBlocker fixupGuard(inUpdateContentSize, true);
2179 q->QQuickFlickable::setContentHeight(estimatedHeight);
2180}
2181
2183{
2184 // When rows or columns outside the viewport are removed or added, or a rebuild
2185 // forces us to guesstimate a new top-left, the edges of the table might end up
2186 // out of sync with the edges of the content view. We detect this situation here, and
2187 // move the origin to ensure that there will never be gaps at the end of the table.
2188 // Normally we detect that the size of the whole table is not going to be equal to the
2189 // size of the content view already when we load the last row/column, and especially
2190 // before it's flicked completely inside the viewport. For those cases we simply adjust
2191 // the origin/endExtent, to give a smooth flicking experience.
2192 // But if flicking fast (e.g with a scrollbar), it can happen that the viewport ends up
2193 // outside the end of the table in just one viewport update. To avoid a "blink" in the
2194 // viewport when that happens, we "move" the loaded table into the viewport to cover it.
2195 Q_Q(QQuickTableView);
2196
2197 bool tableMovedHorizontally = false;
2198 bool tableMovedVertically = false;
2199
2200 const int nextLeftColumn = nextVisibleEdgeIndexAroundLoadedTable(Qt::LeftEdge);
2201 const int nextRightColumn = nextVisibleEdgeIndexAroundLoadedTable(Qt::RightEdge);
2202 const int nextTopRow = nextVisibleEdgeIndexAroundLoadedTable(Qt::TopEdge);
2203 const int nextBottomRow = nextVisibleEdgeIndexAroundLoadedTable(Qt::BottomEdge);
2204
2205 if (syncHorizontally) {
2206 const auto syncView_d = syncView->d_func();
2207 origin.rx() = syncView_d->origin.x();
2208 endExtent.rwidth() = syncView_d->endExtent.width();
2210 } else if (nextLeftColumn == kEdgeIndexAtEnd) {
2211 // There are no more columns to load on the left side of the table.
2212 // In that case, we ensure that the origin match the beginning of the table.
2214 // We have a blank area at the left end of the viewport. In that case we don't have time to
2215 // wait for the viewport to move (after changing origin), since that will take an extra
2216 // update cycle, which will be visible as a blink. Instead, unless the blank spot is just
2217 // us overshooting, we brute force the loaded table inside the already existing viewport.
2218 if (loadedTableOuterRect.left() > origin.x()) {
2219 const qreal diff = loadedTableOuterRect.left() - origin.x();
2222 tableMovedHorizontally = true;
2223 }
2224 }
2227 } else if (loadedTableOuterRect.left() <= origin.x() + cellSpacing.width()) {
2228 // The table rect is at the origin, or outside, but we still have more
2229 // visible columns to the left. So we try to guesstimate how much space
2230 // the rest of the columns will occupy, and move the origin accordingly.
2231 const int columnsRemaining = nextLeftColumn + 1;
2232 const qreal remainingColumnWidths = columnsRemaining * averageEdgeSize.width();
2233 const qreal remainingSpacing = columnsRemaining * cellSpacing.width();
2234 const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing;
2235 origin.rx() = loadedTableOuterRect.left() - estimatedRemainingWidth;
2237 } else if (nextRightColumn == kEdgeIndexAtEnd) {
2238 // There are no more columns to load on the right side of the table.
2239 // In that case, we ensure that the end of the content view match the end of the table.
2241 // We have a blank area at the right end of the viewport. In that case we don't have time to
2242 // wait for the viewport to move (after changing endExtent), since that will take an extra
2243 // update cycle, which will be visible as a blink. Instead, unless the blank spot is just
2244 // us overshooting, we brute force the loaded table inside the already existing viewport.
2245 const qreal w = qMin(viewportRect.right(), q->contentWidth() + endExtent.width());
2246 if (loadedTableOuterRect.right() < w) {
2247 const qreal diff = loadedTableOuterRect.right() - w;
2250 tableMovedHorizontally = true;
2251 }
2252 }
2253 endExtent.rwidth() = loadedTableOuterRect.right() - q->contentWidth();
2255 } else if (loadedTableOuterRect.right() >= q->contentWidth() + endExtent.width() - cellSpacing.width()) {
2256 // The right-most column is outside the end of the content view, and we
2257 // still have more visible columns in the model. This can happen if the application
2258 // has set a fixed content width.
2259 const int columnsRemaining = tableSize.width() - nextRightColumn;
2260 const qreal remainingColumnWidths = columnsRemaining * averageEdgeSize.width();
2261 const qreal remainingSpacing = columnsRemaining * cellSpacing.width();
2262 const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing;
2263 const qreal pixelsOutsideContentWidth = loadedTableOuterRect.right() - q->contentWidth();
2264 endExtent.rwidth() = pixelsOutsideContentWidth + estimatedRemainingWidth;
2266 }
2267
2268 if (syncVertically) {
2269 const auto syncView_d = syncView->d_func();
2270 origin.ry() = syncView_d->origin.y();
2271 endExtent.rheight() = syncView_d->endExtent.height();
2273 } else if (nextTopRow == kEdgeIndexAtEnd) {
2274 // There are no more rows to load on the top side of the table.
2275 // In that case, we ensure that the origin match the beginning of the table.
2277 // We have a blank area at the top of the viewport. In that case we don't have time to
2278 // wait for the viewport to move (after changing origin), since that will take an extra
2279 // update cycle, which will be visible as a blink. Instead, unless the blank spot is just
2280 // us overshooting, we brute force the loaded table inside the already existing viewport.
2281 if (loadedTableOuterRect.top() > origin.y()) {
2282 const qreal diff = loadedTableOuterRect.top() - origin.y();
2285 tableMovedVertically = true;
2286 }
2287 }
2290 } else if (loadedTableOuterRect.top() <= origin.y() + cellSpacing.height()) {
2291 // The table rect is at the origin, or outside, but we still have more
2292 // visible rows at the top. So we try to guesstimate how much space
2293 // the rest of the rows will occupy, and move the origin accordingly.
2294 const int rowsRemaining = nextTopRow + 1;
2295 const qreal remainingRowHeights = rowsRemaining * averageEdgeSize.height();
2296 const qreal remainingSpacing = rowsRemaining * cellSpacing.height();
2297 const qreal estimatedRemainingHeight = remainingRowHeights + remainingSpacing;
2298 origin.ry() = loadedTableOuterRect.top() - estimatedRemainingHeight;
2300 } else if (nextBottomRow == kEdgeIndexAtEnd) {
2301 // There are no more rows to load on the bottom side of the table.
2302 // In that case, we ensure that the end of the content view match the end of the table.
2304 // We have a blank area at the bottom of the viewport. In that case we don't have time to
2305 // wait for the viewport to move (after changing endExtent), since that will take an extra
2306 // update cycle, which will be visible as a blink. Instead, unless the blank spot is just
2307 // us overshooting, we brute force the loaded table inside the already existing viewport.
2308 const qreal h = qMin(viewportRect.bottom(), q->contentHeight() + endExtent.height());
2309 if (loadedTableOuterRect.bottom() < h) {
2310 const qreal diff = loadedTableOuterRect.bottom() - h;
2313 tableMovedVertically = true;
2314 }
2315 }
2316 endExtent.rheight() = loadedTableOuterRect.bottom() - q->contentHeight();
2318 } else if (loadedTableOuterRect.bottom() >= q->contentHeight() + endExtent.height() - cellSpacing.height()) {
2319 // The bottom-most row is outside the end of the content view, and we
2320 // still have more visible rows in the model. This can happen if the application
2321 // has set a fixed content height.
2322 const int rowsRemaining = tableSize.height() - nextBottomRow;
2323 const qreal remainingRowHeigts = rowsRemaining * averageEdgeSize.height();
2324 const qreal remainingSpacing = rowsRemaining * cellSpacing.height();
2325 const qreal estimatedRemainingHeight = remainingRowHeigts + remainingSpacing;
2326 const qreal pixelsOutsideContentHeight = loadedTableOuterRect.bottom() - q->contentHeight();
2327 endExtent.rheight() = pixelsOutsideContentHeight + estimatedRemainingHeight;
2329 }
2330
2331 if (tableMovedHorizontally || tableMovedVertically) {
2332 qCDebug(lcTableViewDelegateLifecycle) << "move table to" << loadedTableOuterRect;
2333
2334 // relayoutTableItems() will take care of moving the existing
2335 // delegate items into the new loadedTableOuterRect.
2337
2338 // Inform the sync children that they need to rebuild to stay in sync
2339 for (auto syncChild : std::as_const(syncChildren)) {
2340 auto syncChild_d = syncChild->d_func();
2341 syncChild_d->scheduledRebuildOptions |= RebuildOption::ViewportOnly;
2342 if (tableMovedHorizontally)
2343 syncChild_d->scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftColumn;
2344 if (tableMovedVertically)
2345 syncChild_d->scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftRow;
2346 }
2347 }
2348
2350 qCDebug(lcTableViewDelegateLifecycle) << "move origin and endExtent to:" << origin << endExtent;
2351 // updateBeginningEnd() will let the new extents take effect. This will also change the
2352 // visualArea of the flickable, which again will cause any attached scrollbars to adjust
2353 // the position of the handle. Note the latter will cause the viewport to move once more.
2355 }
2356}
2357
2359{
2361 const qreal accColumnSpacing = (tableSize.width() - 1) * cellSpacing.width();
2363 } else {
2364 const qreal accColumnSpacing = (loadedColumns.count() - 1) * cellSpacing.width();
2366 }
2367}
2368
2370{
2372 const qreal accRowSpacing = (tableSize.height() - 1) * cellSpacing.height();
2374 } else {
2375 const qreal accRowSpacing = (loadedRows.count() - 1) * cellSpacing.height();
2377 }
2378}
2379
2381{
2382 const QPoint topLeft = QPoint(leftColumn(), topRow());
2383 const QPoint bottomRight = QPoint(rightColumn(), bottomRow());
2384 QRectF topLeftRect = loadedTableItem(topLeft)->geometry();
2385 QRectF bottomRightRect = loadedTableItem(bottomRight)->geometry();
2386 loadedTableOuterRect = QRectF(topLeftRect.topLeft(), bottomRightRect.bottomRight());
2387 loadedTableInnerRect = QRectF(topLeftRect.bottomRight(), bottomRightRect.topLeft());
2388}
2389
2391{
2392 // Move the tracked table rects to the new position. For this to
2393 // take visual effect (move the delegate items to be inside the table
2394 // rect), it needs to be followed by a relayoutTableItems().
2395 // Also note that the position of the viewport needs to be adjusted
2396 // separately for it to overlap the loaded table.
2398 loadedTableOuterRect.moveTopLeft(newPosition);
2399 loadedTableInnerRect.moveTopLeft(newPosition + innerDiff);
2400}
2401
2402QQuickTableViewPrivate::RebuildOptions QQuickTableViewPrivate::checkForVisibilityChanges()
2403{
2404 // This function will check if there are any visibility changes among
2405 // the _already loaded_ rows and columns. Note that there can be rows
2406 // and columns to the bottom or right that was not loaded, but should
2407 // now become visible (in case there is free space around the table).
2408 if (loadedItems.isEmpty()) {
2409 // Report no changes
2410 return RebuildOption::None;
2411 }
2412
2413 RebuildOptions rebuildOptions = RebuildOption::None;
2414
2415 if (loadedTableOuterRect.x() == origin.x() && leftColumn() != 0) {
2416 // Since the left column is at the origin of the viewport, but still not the first
2417 // column in the model, we need to calculate a new left column since there might be
2418 // columns in front of it that used to be hidden, but should now be visible (QTBUG-93264).
2421 } else {
2422 // Go through all loaded columns from first to last, find the columns that used
2423 // to be hidden and not loaded, and check if they should become visible
2424 // (and vice versa). If there is a change, we need to rebuild.
2425 for (int column = leftColumn(); column <= rightColumn(); ++column) {
2426 const bool wasVisibleFromBefore = loadedColumns.contains(column);
2427 const bool isVisibleNow = !qFuzzyIsNull(getColumnWidth(column));
2428 if (wasVisibleFromBefore == isVisibleNow)
2429 continue;
2430
2431 // A column changed visibility. This means that it should
2432 // either be loaded or unloaded. So we need a rebuild.
2433 qCDebug(lcTableViewDelegateLifecycle) << "Column" << column << "changed visibility to" << isVisibleNow;
2435 if (column == leftColumn()) {
2436 // The first loaded column should now be hidden. This means that we
2437 // need to calculate which column should now be first instead.
2439 }
2440 break;
2441 }
2442 }
2443
2444 if (loadedTableOuterRect.y() == origin.y() && topRow() != 0) {
2445 // Since the top row is at the origin of the viewport, but still not the first
2446 // row in the model, we need to calculate a new top row since there might be
2447 // rows in front of it that used to be hidden, but should now be visible (QTBUG-93264).
2450 } else {
2451 // Go through all loaded rows from first to last, find the rows that used
2452 // to be hidden and not loaded, and check if they should become visible
2453 // (and vice versa). If there is a change, we need to rebuild.
2454 for (int row = topRow(); row <= bottomRow(); ++row) {
2455 const bool wasVisibleFromBefore = loadedRows.contains(row);
2456 const bool isVisibleNow = !qFuzzyIsNull(getRowHeight(row));
2457 if (wasVisibleFromBefore == isVisibleNow)
2458 continue;
2459
2460 // A row changed visibility. This means that it should
2461 // either be loaded or unloaded. So we need a rebuild.
2462 qCDebug(lcTableViewDelegateLifecycle) << "Row" << row << "changed visibility to" << isVisibleNow;
2464 if (row == topRow())
2466 break;
2467 }
2468 }
2469
2470 return rebuildOptions;
2471}
2472
2474{
2476 RebuildOptions rebuildOptions = RebuildOption::None;
2477
2478 const QSize actualTableSize = calculateTableSize();
2479 if (tableSize != actualTableSize) {
2480 // This can happen if the app is calling forceLayout while
2481 // the model is updated, but before we're notified about it.
2483 } else {
2484 // Resizing a column (or row) can result in the table going from being
2485 // e.g completely inside the viewport to go outside. And in the latter
2486 // case, the user needs to be able to scroll the viewport, also if
2487 // flags such as Flickable.StopAtBounds is in use. So we need to
2488 // update contentWidth/Height to support that case.
2493 }
2494
2496
2497 if (immediate) {
2498 auto rootView = rootSyncView();
2499 const bool updated = rootView->d_func()->updateTableRecursive();
2500 if (!updated) {
2501 qWarning() << "TableView::forceLayout(): Cannot do an immediate re-layout during an ongoing layout!";
2502 rootView->polish();
2503 }
2504 }
2505}
2506
2508{
2509 if (loadRequest.edge() == Qt::Edge(0)) {
2510 // No edge means we're loading the top-left item
2513 return;
2514 }
2515
2516 switch (loadRequest.edge()) {
2517 case Qt::LeftEdge:
2518 case Qt::RightEdge:
2520 break;
2521 case Qt::TopEdge:
2522 case Qt::BottomEdge:
2524 break;
2525 }
2526}
2527
2529{
2530 const int modelIndex = modelIndexAtCell(cell);
2531 Q_TABLEVIEW_ASSERT(loadedItems.contains(modelIndex), modelIndex << cell);
2532 return loadedItems.value(modelIndex);
2533}
2534
2536{
2537 Q_Q(QQuickTableView);
2538
2539 bool ownItem = false;
2540 int modelIndex = modelIndexAtCell(cell);
2541
2542 QObject* object = model->object(modelIndex, incubationMode);
2543 if (!object) {
2544 if (model->incubationStatus(modelIndex) == QQmlIncubator::Loading) {
2545 // Item is incubating. Return nullptr for now, and let the table call this
2546 // function again once we get a callback to itemCreatedCallback().
2547 return nullptr;
2548 }
2549
2550 qWarning() << "TableView: failed loading index:" << modelIndex;
2551 object = new QQuickItem();
2552 ownItem = true;
2553 }
2554
2556 if (!item) {
2557 // The model could not provide an QQuickItem for the
2558 // given index, so we create a placeholder instead.
2559 qWarning() << "TableView: delegate is not an item:" << modelIndex;
2560 model->release(object);
2561 item = new QQuickItem();
2562 ownItem = true;
2563 } else {
2566 qmlWarning(item) << "TableView: detected anchors on delegate with index: " << modelIndex
2567 << ". Use implicitWidth and implicitHeight instead.";
2568 }
2569
2570 if (ownItem) {
2571 // Parent item is normally set early on from initItemCallback (to
2572 // allow bindings to the parent property). But if we created the item
2573 // within this function, we need to set it explicit.
2574 item->setImplicitWidth(kDefaultColumnWidth);
2575 item->setImplicitHeight(kDefaultRowHeight);
2576 item->setParentItem(q->contentItem());
2577 }
2578 Q_TABLEVIEW_ASSERT(item->parentItem() == q->contentItem(), item->parentItem());
2579
2580 FxTableItem *fxTableItem = new FxTableItem(item, q, ownItem);
2581 fxTableItem->setVisible(false);
2582 fxTableItem->cell = cell;
2583 fxTableItem->index = modelIndex;
2584 return fxTableItem;
2585}
2586
2588{
2589#ifdef QT_DEBUG
2590 // Since TableView needs to work flawlessly when e.g incubating inside an async
2591 // loader, being able to override all loading to async while debugging can be helpful.
2592 static const bool forcedAsync = forcedIncubationMode == QLatin1String("async");
2593 if (forcedAsync)
2594 incubationMode = QQmlIncubator::Asynchronous;
2595#endif
2596
2597 // Note that even if incubation mode is asynchronous, the item might
2598 // be ready immediately since the model has a cache of items.
2600 auto item = createFxTableItem(cell, incubationMode);
2601 qCDebug(lcTableViewDelegateLifecycle) << cell << "ready?" << bool(item);
2602 return item;
2603}
2604
2606 // Make a copy and clear the list of items first to avoid destroyed
2607 // items being accessed during the loop (QTBUG-61294)
2608 auto const tmpList = loadedItems;
2610 for (FxTableItem *item : tmpList)
2612}
2613
2615{
2616 Q_Q(QQuickTableView);
2617 // Note that fxTableItem->item might already have been destroyed, in case
2618 // the item is owned by the QML context rather than the model (e.g ObjectModel etc).
2619 auto item = fxTableItem->item;
2620
2621 if (fxTableItem->ownItem) {
2622 Q_TABLEVIEW_ASSERT(item, fxTableItem->index);
2623 delete item;
2624 } else if (item) {
2625 auto releaseFlag = model->release(item, reusableFlag);
2626 if (releaseFlag == QQmlInstanceModel::Pooled) {
2627 fxTableItem->setVisible(false);
2628
2629 // If the item (or a descendant) has focus, remove it, so
2630 // that the item doesn't enter with focus when it's reused.
2631 if (QQuickWindow *window = item->window()) {
2632 const auto focusItem = qobject_cast<QQuickItem *>(window->focusObject());
2633 if (focusItem) {
2634 const bool hasFocus = item == focusItem || item->isAncestorOf(focusItem);
2635 if (hasFocus) {
2636 const auto focusChild = QQuickItemPrivate::get(q)->subFocusItem;
2638 }
2639 }
2640 }
2641 }
2642 }
2643
2644 delete fxTableItem;
2645}
2646
2648{
2649 const int modelIndex = modelIndexAtCell(cell);
2650 Q_TABLEVIEW_ASSERT(loadedItems.contains(modelIndex), modelIndex << cell);
2652}
2653
2654bool QQuickTableViewPrivate::canLoadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const
2655{
2656 switch (tableEdge) {
2657 case Qt::LeftEdge:
2658 return loadedTableOuterRect.left() > fillRect.left() + cellSpacing.width();
2659 case Qt::RightEdge:
2660 return loadedTableOuterRect.right() < fillRect.right() - cellSpacing.width();
2661 case Qt::TopEdge:
2662 return loadedTableOuterRect.top() > fillRect.top() + cellSpacing.height();
2663 case Qt::BottomEdge:
2664 return loadedTableOuterRect.bottom() < fillRect.bottom() - cellSpacing.height();
2665 }
2666
2667 return false;
2668}
2669
2671{
2672 // Note: if there is only one row or column left, we cannot unload, since
2673 // they are needed as anchor point for further layouting. We also skip
2674 // unloading in the direction we're currently scrolling.
2675
2676 switch (tableEdge) {
2677 case Qt::LeftEdge:
2678 if (loadedColumns.count() <= 1)
2679 return false;
2681 const qreal to = positionXAnimation.to().toFloat();
2682 if (to < viewportRect.x())
2683 return false;
2684 }
2685 return loadedTableInnerRect.left() <= fillRect.left();
2686 case Qt::RightEdge:
2687 if (loadedColumns.count() <= 1)
2688 return false;
2690 const qreal to = positionXAnimation.to().toFloat();
2691 if (to > viewportRect.x())
2692 return false;
2693 }
2694 return loadedTableInnerRect.right() >= fillRect.right();
2695 case Qt::TopEdge:
2696 if (loadedRows.count() <= 1)
2697 return false;
2699 const qreal to = positionYAnimation.to().toFloat();
2700 if (to < viewportRect.y())
2701 return false;
2702 }
2703 return loadedTableInnerRect.top() <= fillRect.top();
2704 case Qt::BottomEdge:
2705 if (loadedRows.count() <= 1)
2706 return false;
2708 const qreal to = positionYAnimation.to().toFloat();
2709 if (to > viewportRect.y())
2710 return false;
2711 }
2712 return loadedTableInnerRect.bottom() >= fillRect.bottom();
2713 }
2714 Q_TABLEVIEW_UNREACHABLE(tableEdge);
2715 return false;
2716}
2717
2719{
2720 for (Qt::Edge edge : allTableEdges) {
2721 if (!canLoadTableEdge(edge, rect))
2722 continue;
2723 const int nextIndex = nextVisibleEdgeIndexAroundLoadedTable(edge);
2724 if (nextIndex == kEdgeIndexAtEnd)
2725 continue;
2726 return edge;
2727 }
2728
2729 return Qt::Edge(0);
2730}
2731
2733{
2734 for (Qt::Edge edge : allTableEdges) {
2735 if (canUnloadTableEdge(edge, rect))
2736 return edge;
2737 }
2738 return Qt::Edge(0);
2739}
2740
2742{
2743 // Using an items width directly is not an option, since we change
2744 // it during layout (which would also cause problems when recycling items).
2745 auto const cellItem = loadedTableItem(cell)->item;
2746 return cellItem->implicitWidth();
2747}
2748
2750{
2751 // Using an items height directly is not an option, since we change
2752 // it during layout (which would also cause problems when recycling items).
2753 auto const cellItem = loadedTableItem(cell)->item;
2754 return cellItem->implicitHeight();
2755}
2756
2758{
2759 // Find the widest cell in the column, and return its width
2760 qreal columnWidth = 0;
2761 for (const int row : loadedRows)
2762 columnWidth = qMax(columnWidth, cellWidth(QPoint(column, row)));
2763
2764 return columnWidth;
2765}
2766
2768{
2769 // Find the highest cell in the row, and return its height
2770 qreal rowHeight = 0;
2771 for (const int column : loadedColumns)
2772 rowHeight = qMax(rowHeight, cellHeight(QPoint(column, row)));
2773 return rowHeight;
2774}
2775
2777{
2778 // tableSize is the same as row and column count, and will always
2779 // be the same as the number of rows and columns in the model.
2780 Q_Q(QQuickTableView);
2781
2782 const QSize prevTableSize = tableSize;
2784
2785 if (prevTableSize.width() != tableSize.width())
2786 emit q->columnsChanged();
2787 if (prevTableSize.height() != tableSize.height())
2788 emit q->rowsChanged();
2789}
2790
2792{
2793 QSize size(0, 0);
2794 if (tableModel)
2796 else if (model)
2797 size = QSize(1, model->count());
2798
2799 return isTransposed ? size.transposed() : size;
2800}
2801
2803{
2804 // Return the column width specified by the application, or go
2805 // through the loaded items and calculate it as a fallback. For
2806 // layouting, the width can never be zero (or negative), as this
2807 // can lead us to be stuck in an infinite loop trying to load and
2808 // fill out the empty viewport space with empty columns.
2809 const qreal explicitColumnWidth = getColumnWidth(column);
2810 if (explicitColumnWidth >= 0)
2811 return explicitColumnWidth;
2812
2813 if (syncHorizontally) {
2814 if (syncView->d_func()->loadedColumns.contains(column))
2815 return syncView->d_func()->getColumnLayoutWidth(column);
2816 }
2817
2818 // Iterate over the currently visible items in the column. The downside
2819 // of doing that, is that the column width will then only be based on the implicit
2820 // width of the currently loaded items (which can be different depending on which
2821 // row you're at when the column is flicked in). The upshot is that you don't have to
2822 // bother setting columnWidthProvider for small tables, or if the implicit width doesn't vary.
2823 qreal columnWidth = sizeHintForColumn(column);
2824
2825 if (qIsNaN(columnWidth) || columnWidth <= 0) {
2826 if (!layoutWarningIssued) {
2827 layoutWarningIssued = true;
2828 qmlWarning(q_func()) << "the delegate's implicitWidth needs to be greater than zero";
2829 }
2830 columnWidth = kDefaultColumnWidth;
2831 }
2832
2833 return columnWidth;
2834}
2835
2837{
2838 // Return y pos of row after layout
2840 return loadedTableItem(QPoint(leftColumn(), row))->geometry().y();
2841}
2842
2844{
2845 // Return row height after layout
2848}
2849
2851{
2852 // Return x pos of column after layout
2854 return loadedTableItem(QPoint(column, topRow()))->geometry().x();
2855}
2856
2858{
2859 // Return column width after layout
2862}
2863
2865{
2866 // Return the row height specified by the application, or go
2867 // through the loaded items and calculate it as a fallback. For
2868 // layouting, the height can never be zero (or negative), as this
2869 // can lead us to be stuck in an infinite loop trying to load and
2870 // fill out the empty viewport space with empty rows.
2871 const qreal explicitRowHeight = getRowHeight(row);
2872 if (explicitRowHeight >= 0)
2873 return explicitRowHeight;
2874
2875 if (syncVertically) {
2876 if (syncView->d_func()->loadedRows.contains(row))
2877 return syncView->d_func()->getRowLayoutHeight(row);
2878 }
2879
2880 // Iterate over the currently visible items in the row. The downside
2881 // of doing that, is that the row height will then only be based on the implicit
2882 // height of the currently loaded items (which can be different depending on which
2883 // column you're at when the row is flicked in). The upshot is that you don't have to
2884 // bother setting rowHeightProvider for small tables, or if the implicit height doesn't vary.
2885 qreal rowHeight = sizeHintForRow(row);
2886
2887 if (qIsNaN(rowHeight) || rowHeight <= 0) {
2888 if (!layoutWarningIssued) {
2889 layoutWarningIssued = true;
2890 qmlWarning(q_func()) << "the delegate's implicitHeight needs to be greater than zero";
2891 }
2892 rowHeight = kDefaultRowHeight;
2893 }
2894
2895 return rowHeight;
2896}
2897
2899{
2900 // Return the width of the given column, if explicitly set. Return 0 if the column
2901 // is hidden, and -1 if the width is not set (which means that the width should
2902 // instead be calculated from the implicit size of the delegate items. This function
2903 // can be overridden by e.g HeaderView to provide the column widths by other means.
2904 Q_Q(const QQuickTableView);
2905
2906 const int noExplicitColumnWidth = -1;
2907
2909 return cachedColumnWidth.size;
2910
2911 if (syncHorizontally)
2912 return syncView->d_func()->getColumnWidth(column);
2913
2915 // We only respect explicit column widths when no columnWidthProvider
2916 // is set. Otherwise it's the responsibility of the provider to e.g
2917 // call explicitColumnWidth() (and implicitColumnWidth()), if needed.
2918 qreal explicitColumnWidth = q->explicitColumnWidth(column);
2919 if (explicitColumnWidth >= 0)
2920 return explicitColumnWidth;
2921 return noExplicitColumnWidth;
2922 }
2923
2924 qreal columnWidth = noExplicitColumnWidth;
2925
2927 auto const columnAsArgument = QJSValueList() << QJSValue(column);
2928 columnWidth = columnWidthProvider.call(columnAsArgument).toNumber();
2929 if (qIsNaN(columnWidth) || columnWidth < 0)
2930 columnWidth = noExplicitColumnWidth;
2931 } else {
2932 if (!layoutWarningIssued) {
2933 layoutWarningIssued = true;
2934 qmlWarning(q_func()) << "columnWidthProvider doesn't contain a function";
2935 }
2936 columnWidth = noExplicitColumnWidth;
2937 }
2938
2940 cachedColumnWidth.size = columnWidth;
2941 return columnWidth;
2942}
2943
2945{
2946 // Return the height of the given row, if explicitly set. Return 0 if the row
2947 // is hidden, and -1 if the height is not set (which means that the height should
2948 // instead be calculated from the implicit size of the delegate items. This function
2949 // can be overridden by e.g HeaderView to provide the row heights by other means.
2950 Q_Q(const QQuickTableView);
2951
2952 const int noExplicitRowHeight = -1;
2953
2955 return cachedRowHeight.size;
2956
2957 if (syncVertically)
2958 return syncView->d_func()->getRowHeight(row);
2959
2961 // We only resepect explicit row heights when no rowHeightProvider
2962 // is set. Otherwise it's the responsibility of the provider to e.g
2963 // call explicitRowHeight() (and implicitRowHeight()), if needed.
2964 qreal explicitRowHeight = q->explicitRowHeight(row);
2965 if (explicitRowHeight >= 0)
2966 return explicitRowHeight;
2967 return noExplicitRowHeight;
2968 }
2969
2970 qreal rowHeight = noExplicitRowHeight;
2971
2973 auto const rowAsArgument = QJSValueList() << QJSValue(row);
2974 rowHeight = rowHeightProvider.call(rowAsArgument).toNumber();
2975 if (qIsNaN(rowHeight) || rowHeight < 0)
2976 rowHeight = noExplicitRowHeight;
2977 } else {
2978 if (!layoutWarningIssued) {
2979 layoutWarningIssued = true;
2980 qmlWarning(q_func()) << "rowHeightProvider doesn't contain a function";
2981 }
2982 rowHeight = noExplicitRowHeight;
2983 }
2984
2986 cachedRowHeight.size = rowHeight;
2987 return rowHeight;
2988}
2989
2991{
2992 Q_Q(QQuickTableView);
2993
2994 qreal contentX = 0;
2995 const int columnX = getEffectiveColumnX(column);
2996
2997 if (subRect.isValid()) {
2999 // Special case: Align to the right as long as the left
3000 // edge of the cell remains visible. Otherwise align to the left.
3001 alignment = subRect.width() > q->width() ? Qt::AlignLeft : Qt::AlignRight;
3002 }
3003
3004 if (alignment & Qt::AlignLeft) {
3005 contentX = columnX + subRect.x() + offset;
3006 } else if (alignment & Qt::AlignRight) {
3007 contentX = columnX + subRect.right() - viewportRect.width() + offset;
3008 } else if (alignment & Qt::AlignHCenter) {
3009 const qreal centerDistance = (viewportRect.width() - subRect.width()) / 2;
3010 contentX = columnX + subRect.x() - centerDistance + offset;
3011 }
3012 } else {
3013 const int columnWidth = getEffectiveColumnWidth(column);
3015 alignment = columnWidth > q->width() ? Qt::AlignLeft : Qt::AlignRight;
3016
3017 if (alignment & Qt::AlignLeft) {
3018 contentX = columnX + offset;
3019 } else if (alignment & Qt::AlignRight) {
3020 contentX = columnX + columnWidth - viewportRect.width() + offset;
3021 } else if (alignment & Qt::AlignHCenter) {
3022 const qreal centerDistance = (viewportRect.width() - columnWidth) / 2;
3023 contentX = columnX - centerDistance + offset;
3024 }
3025 }
3026
3027 // Don't overshoot
3028 contentX = qBound(-q->minXExtent(), contentX, -q->maxXExtent());
3029
3030 return contentX;
3031}
3032
3034{
3035 Q_Q(QQuickTableView);
3036
3037 qreal contentY = 0;
3038 const int rowY = getEffectiveRowY(row);
3039
3040 if (subRect.isValid()) {
3042 // Special case: Align to the bottom as long as the top
3043 // edge of the cell remains visible. Otherwise align to the top.
3044 alignment = subRect.height() > q->height() ? Qt::AlignTop : Qt::AlignBottom;
3045 }
3046
3047 if (alignment & Qt::AlignTop) {
3048 contentY = rowY + subRect.y() + offset;
3049 } else if (alignment & Qt::AlignBottom) {
3050 contentY = rowY + subRect.bottom() - viewportRect.height() + offset;
3051 } else if (alignment & Qt::AlignVCenter) {
3052 const qreal centerDistance = (viewportRect.height() - subRect.height()) / 2;
3053 contentY = rowY + subRect.y() - centerDistance + offset;
3054 }
3055 } else {
3056 const int rowHeight = getEffectiveRowHeight(row);
3058 alignment = rowHeight > q->height() ? Qt::AlignTop : Qt::AlignBottom;
3059
3060 if (alignment & Qt::AlignTop) {
3061 contentY = rowY + offset;
3062 } else if (alignment & Qt::AlignBottom) {
3063 contentY = rowY + rowHeight - viewportRect.height() + offset;
3064 } else if (alignment & Qt::AlignVCenter) {
3065 const qreal centerDistance = (viewportRect.height() - rowHeight) / 2;
3066 contentY = rowY - centerDistance + offset;
3067 }
3068 }
3069
3070 // Don't overshoot
3071 contentY = qBound(-q->minYExtent(), contentY, -q->maxYExtent());
3072
3073 return contentY;
3074}
3075
3077{
3078 // A column is hidden if the width is explicit set to zero (either by
3079 // using a columnWidthProvider, or by overriding getColumnWidth()).
3081}
3082
3084{
3085 // A row is hidden if the height is explicit set to zero (either by
3086 // using a rowHeightProvider, or by overriding getRowHeight()).
3087 return qFuzzyIsNull(getRowHeight(row));
3088}
3089
3091{
3092 qCDebug(lcTableViewDelegateLifecycle);
3093
3094 if (viewportRect.isEmpty()) {
3095 // This can happen if TableView was resized down to have a zero size
3096 qCDebug(lcTableViewDelegateLifecycle()) << "Skipping relayout, viewport has zero size";
3097 return;
3098 }
3099
3100 qreal nextColumnX = loadedTableOuterRect.x();
3101 qreal nextRowY = loadedTableOuterRect.y();
3102
3103 for (const int column : loadedColumns) {
3104 // Adjust the geometry of all cells in the current column
3106
3107 for (const int row : loadedRows) {
3109 QRectF geometry = item->geometry();
3110 geometry.moveLeft(nextColumnX);
3111 geometry.setWidth(width);
3112 item->setGeometry(geometry);
3113 }
3114
3115 if (width > 0)
3116 nextColumnX += width + cellSpacing.width();
3117 }
3118
3119 for (const int row : loadedRows) {
3120 // Adjust the geometry of all cells in the current row
3122
3123 for (const int column : loadedColumns) {
3125 QRectF geometry = item->geometry();
3126 geometry.moveTop(nextRowY);
3127 geometry.setHeight(height);
3128 item->setGeometry(geometry);
3129 }
3130
3131 if (height > 0)
3132 nextRowY += height + cellSpacing.height();
3133 }
3134
3135 if (Q_UNLIKELY(lcTableViewDelegateLifecycle().isDebugEnabled())) {
3136 for (const int column : loadedColumns) {
3137 for (const int row : loadedRows) {
3138 QPoint cell = QPoint(column, row);
3139 qCDebug(lcTableViewDelegateLifecycle()) << "relayout item:" << cell << loadedTableItem(cell)->geometry();
3140 }
3141 }
3142 }
3143}
3144
3146{
3147 int columnThatNeedsLayout;
3148 int neighbourColumn;
3149 qreal columnX;
3150 qreal columnWidth;
3151
3152 if (tableEdge == Qt::LeftEdge) {
3153 columnThatNeedsLayout = leftColumn();
3154 neighbourColumn = loadedColumns.values().at(1);
3155 columnWidth = getColumnLayoutWidth(columnThatNeedsLayout);
3156 const auto neighbourItem = loadedTableItem(QPoint(neighbourColumn, topRow()));
3157 columnX = neighbourItem->geometry().left() - cellSpacing.width() - columnWidth;
3158 } else {
3159 columnThatNeedsLayout = rightColumn();
3160 neighbourColumn = loadedColumns.values().at(loadedColumns.count() - 2);
3161 columnWidth = getColumnLayoutWidth(columnThatNeedsLayout);
3162 const auto neighbourItem = loadedTableItem(QPoint(neighbourColumn, topRow()));
3163 columnX = neighbourItem->geometry().right() + cellSpacing.width();
3164 }
3165
3166 for (const int row : loadedRows) {
3167 auto fxTableItem = loadedTableItem(QPoint(columnThatNeedsLayout, row));
3168 auto const neighbourItem = loadedTableItem(QPoint(neighbourColumn, row));
3169 const qreal rowY = neighbourItem->geometry().y();
3170 const qreal rowHeight = neighbourItem->geometry().height();
3171
3172 fxTableItem->setGeometry(QRectF(columnX, rowY, columnWidth, rowHeight));
3173 fxTableItem->setVisible(true);
3174
3175 qCDebug(lcTableViewDelegateLifecycle()) << "layout item:" << QPoint(columnThatNeedsLayout, row) << fxTableItem->geometry();
3176 }
3177}
3178
3180{
3181 int rowThatNeedsLayout;
3182 int neighbourRow;
3183
3184 if (tableEdge == Qt::TopEdge) {
3185 rowThatNeedsLayout = topRow();
3186 neighbourRow = loadedRows.values().at(1);
3187 } else {
3188 rowThatNeedsLayout = bottomRow();
3189 neighbourRow = loadedRows.values().at(loadedRows.count() - 2);
3190 }
3191
3192 // Set the width first, since text items in QtQuick will calculate
3193 // implicitHeight based on the text items width.
3194 for (const int column : loadedColumns) {
3195 auto fxTableItem = loadedTableItem(QPoint(column, rowThatNeedsLayout));
3196 auto const neighbourItem = loadedTableItem(QPoint(column, neighbourRow));
3197 const qreal columnX = neighbourItem->geometry().x();
3198 const qreal columnWidth = neighbourItem->geometry().width();
3199 fxTableItem->item->setX(columnX);
3200 fxTableItem->item->setWidth(columnWidth);
3201 }
3202
3203 qreal rowY;
3204 qreal rowHeight;
3205 if (tableEdge == Qt::TopEdge) {
3206 rowHeight = getRowLayoutHeight(rowThatNeedsLayout);
3207 const auto neighbourItem = loadedTableItem(QPoint(leftColumn(), neighbourRow));
3208 rowY = neighbourItem->geometry().top() - cellSpacing.height() - rowHeight;
3209 } else {
3210 rowHeight = getRowLayoutHeight(rowThatNeedsLayout);
3211 const auto neighbourItem = loadedTableItem(QPoint(leftColumn(), neighbourRow));
3212 rowY = neighbourItem->geometry().bottom() + cellSpacing.height();
3213 }
3214
3215 for (const int column : loadedColumns) {
3216 auto fxTableItem = loadedTableItem(QPoint(column, rowThatNeedsLayout));
3217 fxTableItem->item->setY(rowY);
3218 fxTableItem->item->setHeight(rowHeight);
3219 fxTableItem->setVisible(true);
3220
3221 qCDebug(lcTableViewDelegateLifecycle()) << "layout item:" << QPoint(column, rowThatNeedsLayout) << fxTableItem->geometry();
3222 }
3223}
3224
3226{
3227 const QPoint cell(loadRequest.column(), loadRequest.row());
3228 auto topLeftItem = loadedTableItem(cell);
3229 auto item = topLeftItem->item;
3230
3231 item->setPosition(loadRequest.startPosition());
3232 item->setSize(QSizeF(getColumnLayoutWidth(cell.x()), getRowLayoutHeight(cell.y())));
3233 topLeftItem->setVisible(true);
3234 qCDebug(lcTableViewDelegateLifecycle) << "geometry:" << topLeftItem->geometry();
3235}
3236
3238{
3239 if (loadRequest.edge() == Qt::Edge(0)) {
3240 // No edge means we're loading the top-left item
3242 return;
3243 }
3244
3245 switch (loadRequest.edge()) {
3246 case Qt::LeftEdge:
3247 case Qt::RightEdge:
3249 break;
3250 case Qt::TopEdge:
3251 case Qt::BottomEdge:
3253 break;
3254 }
3255}
3256
3258{
3259 Q_Q(QQuickTableView);
3261
3262 while (loadRequest.hasCurrentCell()) {
3264 FxTableItem *fxTableItem = loadFxTableItem(cell, loadRequest.incubationMode());
3265
3266 if (!fxTableItem) {
3267 // Requested item is not yet ready. Just leave, and wait for this
3268 // function to be called again when the item is ready.
3269 return;
3270 }
3271
3272 loadedItems.insert(modelIndexAtCell(cell), fxTableItem);
3274 }
3275
3276 qCDebug(lcTableViewDelegateLifecycle()) << "all items loaded!";
3277
3281
3283 // Loading of this edge was not done as a part of a rebuild, but
3284 // instead as an incremental build after e.g a flick.
3285 updateExtents();
3287
3288 switch (loadRequest.edge()) {
3289 case Qt::LeftEdge:
3290 emit q->leftColumnChanged();
3291 break;
3292 case Qt::RightEdge:
3293 emit q->rightColumnChanged();
3294 break;
3295 case Qt::TopEdge:
3296 emit q->topRowChanged();
3297 break;
3298 case Qt::BottomEdge:
3299 emit q->bottomRowChanged();
3300 break;
3301 }
3302
3303 if (editIndex.isValid())
3305
3306 emit q->layoutChanged();
3307 }
3308
3310
3311 qCDebug(lcTableViewDelegateLifecycle()) << "current table:" << tableLayoutToString();
3312 qCDebug(lcTableViewDelegateLifecycle()) << "Load request completed!";
3313 qCDebug(lcTableViewDelegateLifecycle()) << "****************************************";
3314}
3315
3317{
3318 Q_Q(QQuickTableView);
3319
3321 if (Q_UNLIKELY(lcTableViewDelegateLifecycle().isDebugEnabled())) {
3322 qCDebug(lcTableViewDelegateLifecycle()) << "begin rebuild:" << q;
3324 qCDebug(lcTableViewDelegateLifecycle()) << "RebuildOption::All, options:" << rebuildOptions;
3326 qCDebug(lcTableViewDelegateLifecycle()) << "RebuildOption::ViewportOnly, options:" << rebuildOptions;
3328 qCDebug(lcTableViewDelegateLifecycle()) << "RebuildOption::LayoutOnly, options:" << rebuildOptions;
3329 else
3331 }
3332
3334 : QMargins(q->leftColumn(), q->topRow(), q->rightColumn(), q->bottomRow());
3335 }
3336
3338
3342 return;
3343 }
3344
3346 if (loadedItems.isEmpty()) {
3347 qCDebug(lcTableViewDelegateLifecycle()) << "no items loaded!";
3351 } else if (!moveToNextRebuildState()) {
3352 return;
3353 }
3354 }
3355
3360 return;
3361 }
3362
3367 return;
3368 }
3369
3373 return;
3374 }
3375
3376 const bool preload = (rebuildOptions & RebuildOption::All
3378
3380 if (preload && !atTableEnd(Qt::RightEdge))
3383 return;
3384 }
3385
3387 if (preload && !atTableEnd(Qt::BottomEdge))
3390 return;
3391 }
3392
3394 while (Qt::Edge edge = nextEdgeToUnload(viewportRect))
3395 unloadEdge(edge);
3397 return;
3398 }
3399
3401 if (edgesBeforeRebuild.left() != q->leftColumn())
3402 emit q->leftColumnChanged();
3403 if (edgesBeforeRebuild.right() != q->rightColumn())
3404 emit q->rightColumnChanged();
3405 if (edgesBeforeRebuild.top() != q->topRow())
3406 emit q->topRowChanged();
3407 if (edgesBeforeRebuild.bottom() != q->bottomRow())
3408 emit q->bottomRowChanged();
3409
3410 if (editIndex.isValid())
3413
3414 emit q->layoutChanged();
3415
3416 qCDebug(lcTableViewDelegateLifecycle()) << "current table:" << tableLayoutToString();
3417 qCDebug(lcTableViewDelegateLifecycle()) << "rebuild completed!";
3418 qCDebug(lcTableViewDelegateLifecycle()) << "################################################";
3419 qCDebug(lcTableViewDelegateLifecycle());
3420 }
3421
3423}
3424
3426{
3427 if (loadRequest.isActive()) {
3428 // Items are still loading async, which means
3429 // that the current state is not yet done.
3430 return false;
3431 }
3432
3436 else
3438
3439 qCDebug(lcTableViewDelegateLifecycle()) << int(rebuildState);
3440 return true;
3441}
3442
3444{
3445 if (tableSize.isEmpty()) {
3446 // There is no cell that can be top left
3447 topLeftCell.rx() = kEdgeIndexAtEnd;
3448 topLeftCell.ry() = kEdgeIndexAtEnd;
3449 return;
3450 }
3451
3453 const auto syncView_d = syncView->d_func();
3454
3455 if (syncView_d->loadedItems.isEmpty()) {
3456 topLeftCell.rx() = 0;
3457 topLeftCell.ry() = 0;
3458 return;
3459 }
3460
3461 // Get sync view top left, and use that as our own top left (if possible)
3462 const QPoint syncViewTopLeftCell(syncView_d->leftColumn(), syncView_d->topRow());
3463 const auto syncViewTopLeftFxItem = syncView_d->loadedTableItem(syncViewTopLeftCell);
3464 const QPointF syncViewTopLeftPos = syncViewTopLeftFxItem->geometry().topLeft();
3465
3466 if (syncHorizontally) {
3467 topLeftCell.rx() = syncViewTopLeftCell.x();
3468 topLeftPos.rx() = syncViewTopLeftPos.x();
3469
3470 if (topLeftCell.x() >= tableSize.width()) {
3471 // Top left is outside our own model.
3472 topLeftCell.rx() = kEdgeIndexAtEnd;
3473 topLeftPos.rx() = kEdgeIndexAtEnd;
3474 }
3475 }
3476
3477 if (syncVertically) {
3478 topLeftCell.ry() = syncViewTopLeftCell.y();
3479 topLeftPos.ry() = syncViewTopLeftPos.y();
3480
3481 if (topLeftCell.y() >= tableSize.height()) {
3482 // Top left is outside our own model.
3483 topLeftCell.ry() = kEdgeIndexAtEnd;
3484 topLeftPos.ry() = kEdgeIndexAtEnd;
3485 }
3486 }
3487
3489 // We have a valid top left, so we're done
3490 return;
3491 }
3492 }
3493
3494 // Since we're not sync-ing both horizontal and vertical, calculate the missing
3495 // dimention(s) ourself. If we rebuild all, we find the first visible top-left
3496 // item starting from cell(0, 0). Otherwise, guesstimate which row or column that
3497 // should be the new top-left given the geometry of the viewport.
3498
3499 if (!syncHorizontally) {
3501 // Find the first visible column from the beginning
3502 topLeftCell.rx() = nextVisibleEdgeIndex(Qt::RightEdge, 0);
3503 if (topLeftCell.x() == kEdgeIndexAtEnd) {
3504 // No visible column found
3505 return;
3506 }
3508 // Guesstimate new top left
3509 const int newColumn = int(viewportRect.x() / (averageEdgeSize.width() + cellSpacing.width()));
3510 topLeftCell.rx() = qBound(0, newColumn, tableSize.width() - 1);
3511 topLeftPos.rx() = topLeftCell.x() * (averageEdgeSize.width() + cellSpacing.width());
3513 topLeftCell.rx() = qBound(0, positionViewAtColumnAfterRebuild, tableSize.width() - 1);
3514 topLeftPos.rx() = qFloor(topLeftCell.x()) * (averageEdgeSize.width() + cellSpacing.width());
3515 } else {
3516 // Keep the current top left, unless it's outside model
3517 topLeftCell.rx() = qBound(0, leftColumn(), tableSize.width() - 1);
3518 // We begin by loading the columns where the viewport is at
3519 // now. But will move the whole table and the viewport
3520 // later, when we do a layoutAfterLoadingInitialTable().
3521 topLeftPos.rx() = loadedTableOuterRect.x();
3522 }
3523 }
3524
3525 if (!syncVertically) {
3527 // Find the first visible row from the beginning
3528 topLeftCell.ry() = nextVisibleEdgeIndex(Qt::BottomEdge, 0);
3529 if (topLeftCell.y() == kEdgeIndexAtEnd) {
3530 // No visible row found
3531 return;
3532 }
3534 // Guesstimate new top left
3535 const int newRow = int(viewportRect.y() / (averageEdgeSize.height() + cellSpacing.height()));
3536 topLeftCell.ry() = qBound(0, newRow, tableSize.height() - 1);
3537 topLeftPos.ry() = topLeftCell.y() * (averageEdgeSize.height() + cellSpacing.height());
3539 topLeftCell.ry() = qBound(0, positionViewAtRowAfterRebuild, tableSize.height() - 1);
3540 topLeftPos.ry() = qFloor(topLeftCell.y()) * (averageEdgeSize.height() + cellSpacing.height());
3541 } else {
3542 topLeftCell.ry() = qBound(0, topRow(), tableSize.height() - 1);
3543 topLeftPos.ry() = loadedTableOuterRect.y();
3544 }
3545 }
3546}
3547
3549{
3551
3556 }
3557
3562 }
3563
3564 QPoint topLeft;
3565 QPointF topLeftPos;
3566 calculateTopLeft(topLeft, topLeftPos);
3567 qCDebug(lcTableViewDelegateLifecycle()) << "initial viewport rect:" << viewportRect;
3568 qCDebug(lcTableViewDelegateLifecycle()) << "initial top left cell:" << topLeft << ", pos:" << topLeftPos;
3569
3570 if (!loadedItems.isEmpty()) {
3575 }
3576
3578 origin = QPointF(0, 0);
3579 endExtent = QSizeF(0, 0);
3583 }
3584
3586 loadedRows.clear();
3590
3591 if (syncHorizontally)
3593
3594 if (syncVertically)
3596
3598 setLocalViewportX(topLeftPos.x());
3599
3601 setLocalViewportY(topLeftPos.y());
3602
3604
3605 if (!model) {
3606 qCDebug(lcTableViewDelegateLifecycle()) << "no model found, leaving table empty";
3607 return;
3608 }
3609
3610 if (model->count() == 0) {
3611 qCDebug(lcTableViewDelegateLifecycle()) << "empty model found, leaving table empty";
3612 return;
3613 }
3614
3615 if (tableModel && !tableModel->delegate()) {
3616 qCDebug(lcTableViewDelegateLifecycle()) << "no delegate found, leaving table empty";
3617 return;
3618 }
3619
3620 if (topLeft.x() == kEdgeIndexAtEnd || topLeft.y() == kEdgeIndexAtEnd) {
3621 qCDebug(lcTableViewDelegateLifecycle()) << "no visible row or column found, leaving table empty";
3622 return;
3623 }
3624
3625 if (topLeft.x() == kEdgeIndexNotSet || topLeft.y() == kEdgeIndexNotSet) {
3626 qCDebug(lcTableViewDelegateLifecycle()) << "could not resolve top-left item, leaving table empty";
3627 return;
3628 }
3629
3630 if (viewportRect.isEmpty()) {
3631 qCDebug(lcTableViewDelegateLifecycle()) << "viewport has zero size, leaving table empty";
3632 return;
3633 }
3634
3635 // Load top-left item. After loaded, loadItemsInsideRect() will take
3636 // care of filling out the rest of the table.
3640}
3641
3643{
3644 const bool allColumnsLoaded = atTableEnd(Qt::LeftEdge) && atTableEnd(Qt::RightEdge);
3645 if (rebuildOptions.testFlag(RebuildOption::CalculateNewContentWidth) || allColumnsLoaded) {
3648 }
3649
3650 const bool allRowsLoaded = atTableEnd(Qt::TopEdge) && atTableEnd(Qt::BottomEdge);
3651 if (rebuildOptions.testFlag(RebuildOption::CalculateNewContentHeight) || allRowsLoaded) {
3654 }
3655
3656 updateExtents();
3657}
3658
3660{
3664
3666
3669}
3670
3672{
3673 // Check if we are supposed to position the viewport at a certain column
3675 return;
3676 // The requested column might have been hidden or is outside model bounds
3678 return;
3679
3680 const qreal newContentX = getAlignmentContentX(
3685
3686 setLocalViewportX(newContentX);
3688}
3689
3691{
3692 // Check if we are supposed to position the viewport at a certain row
3694 return;
3695 // The requested row might have been hidden or is outside model bounds
3697 return;
3698
3699 const qreal newContentY = getAlignmentContentY(
3704
3705 setLocalViewportY(newContentY);
3707}
3708
3710{
3711 Q_Q(QQuickTableView);
3712
3713 // Note: we only want to cancel overshoot from a rebuild if we're supposed to position
3714 // the view on a specific cell. The app is allowed to overshoot by setting contentX and
3715 // contentY manually. Also, if this view is a sync child, we should always stay in sync
3716 // with the syncView, so then we don't do anything.
3717 const bool positionVertically = rebuildOptions.testFlag(RebuildOption::PositionViewAtRow);
3718 const bool positionHorizontally = rebuildOptions.testFlag(RebuildOption::PositionViewAtColumn);
3719 const bool cancelVertically = positionVertically && !syncVertically;
3720 const bool cancelHorizontally = positionHorizontally && !syncHorizontally;
3721
3722 if (cancelHorizontally && !qFuzzyIsNull(q->horizontalOvershoot())) {
3723 qCDebug(lcTableViewDelegateLifecycle()) << "cancelling overshoot horizontally:" << q->horizontalOvershoot();
3724 setLocalViewportX(q->horizontalOvershoot() < 0 ? -q->minXExtent() : -q->maxXExtent());
3726 }
3727
3728 if (cancelVertically && !qFuzzyIsNull(q->verticalOvershoot())) {
3729 qCDebug(lcTableViewDelegateLifecycle()) << "cancelling overshoot vertically:" << q->verticalOvershoot();
3730 setLocalViewportY(q->verticalOvershoot() < 0 ? -q->minYExtent() : -q->maxYExtent());
3732 }
3733}
3734
3736{
3737 Q_Q(QQuickTableView);
3738 qCDebug(lcTableViewDelegateLifecycle) << edge;
3739
3740 switch (edge) {
3741 case Qt::LeftEdge: {
3742 const int column = leftColumn();
3743 for (int row : loadedRows)
3748 emit q->leftColumnChanged();
3749 break; }
3750 case Qt::RightEdge: {
3751 const int column = rightColumn();
3752 for (int row : loadedRows)
3757 emit q->rightColumnChanged();
3758 break; }
3759 case Qt::TopEdge: {
3760 const int row = topRow();
3761 for (int col : loadedColumns)
3762 unloadItem(QPoint(col, row));
3766 emit q->topRowChanged();
3767 break; }
3768 case Qt::BottomEdge: {
3769 const int row = bottomRow();
3770 for (int col : loadedColumns)
3771 unloadItem(QPoint(col, row));
3775 emit q->bottomRowChanged();
3776 break; }
3777 }
3778
3780 emit q->layoutChanged();
3781
3782 qCDebug(lcTableViewDelegateLifecycle) << tableLayoutToString();
3783}
3784
3786{
3787 const int edgeIndex = nextVisibleEdgeIndexAroundLoadedTable(edge);
3788 qCDebug(lcTableViewDelegateLifecycle) << edge << edgeIndex << q_func();
3789
3790 const auto &visibleCells = edge & (Qt::LeftEdge | Qt::RightEdge)
3792 loadRequest.begin(edge, edgeIndex, visibleCells, incubationMode);
3794}
3795
3797{
3798 // Unload table edges that have been moved outside the visible part of the
3799 // table (including buffer area), and load new edges that has been moved inside.
3800 // Note: an important point is that we always keep the table rectangular
3801 // and without holes to reduce complexity (we never leave the table in
3802 // a half-loaded state, or keep track of multiple patches).
3803 // We load only one edge (row or column) at a time. This is especially
3804 // important when loading into the buffer, since we need to be able to
3805 // cancel the buffering quickly if the user starts to flick, and then
3806 // focus all further loading on the edges that are flicked into view.
3807
3808 if (loadRequest.isActive()) {
3809 // Don't start loading more edges while we're
3810 // already waiting for another one to load.
3811 return;
3812 }
3813
3814 if (loadedItems.isEmpty()) {
3815 // We need at least the top-left item to be loaded before we can
3816 // start loading edges around it. Not having a top-left item at
3817 // this point means that the model is empty (or no delegate).
3818 return;
3819 }
3820
3821 bool tableModified;
3822
3823 do {
3824 tableModified = false;
3825
3827 tableModified = true;
3828 unloadEdge(edge);
3829 }
3830
3831 if (Qt::Edge edge = nextEdgeToLoad(viewportRect)) {
3832 tableModified = true;
3833 loadEdge(edge, incubationMode);
3834 if (loadRequest.isActive())
3835 return;
3836 }
3837 } while (tableModified);
3838
3839}
3840
3842{
3843 Q_Q(QQuickTableView);
3844
3846 return;
3847
3848 if (!qFuzzyIsNull(q->verticalOvershoot()) || !qFuzzyIsNull(q->horizontalOvershoot())) {
3849 // Don't drain while we're overshooting, since this will fill up the
3850 // pool, but we expect to reuse them all once the content item moves back.
3851 return;
3852 }
3853
3854 // When loading edges, we don't want to drain the reuse pool too aggressively. Normally,
3855 // all the items in the pool are reused rapidly as the content view is flicked around
3856 // anyway. Even if the table is temporarily flicked to a section that contains fewer
3857 // cells than what used to be (e.g if the flicked-in rows are taller than average), it
3858 // still makes sense to keep all the items in circulation; Chances are, that soon enough,
3859 // thinner rows are flicked back in again (meaning that we can fit more items into the
3860 // view). But at the same time, if a delegate chooser is in use, the pool might contain
3861 // items created from different delegates. And some of those delegates might be used only
3862 // occasionally. So to avoid situations where an item ends up in the pool for too long, we
3863 // call drain after each load request, but with a sufficiently large pool time. (If an item
3864 // in the pool has a large pool time, it means that it hasn't been reused for an equal
3865 // amount of load cycles, and should be released).
3866 //
3867 // We calculate an appropriate pool time by figuring out what the minimum time must be to
3868 // not disturb frequently reused items. Since the number of items in a row might be higher
3869 // than in a column (or vice versa), the minimum pool time should take into account that
3870 // you might be flicking out a single row (filling up the pool), before you continue
3871 // flicking in several new columns (taking them out again, but now in smaller chunks). This
3872 // will increase the number of load cycles items are kept in the pool (poolTime), but still,
3873 // we shouldn't release them, as they are still being reused frequently.
3874 // To get a flexible maxValue (that e.g tolerates rows and columns being flicked
3875 // in with varying sizes, causing some items not to be resued immediately), we multiply the
3876 // value by 2. Note that we also add an extra +1 to the column count, because the number of
3877 // visible columns will fluctuate between +1/-1 while flicking.
3878 const int w = loadedColumns.count();
3879 const int h = loadedRows.count();
3880 const int minTime = int(std::ceil(w > h ? qreal(w + 1) / h : qreal(h + 1) / w));
3881 const int maxTime = minTime * 2;
3883}
3884
3886 if (!q_func()->isComponentComplete()) {
3887 // We'll rebuild the table once complete anyway
3888 return;
3889 }
3890
3891 scheduledRebuildOptions |= options;
3892 q_func()->polish();
3893}
3894
3896{
3897 QQuickTableView *root = const_cast<QQuickTableView *>(q_func());
3898 while (QQuickTableView *view = root->d_func()->syncView)
3899 root = view;
3900 return root;
3901}
3902
3904{
3905 // We always start updating from the top of the syncView tree, since
3906 // the layout of a syncView child will depend on the layout of the syncView.
3907 // E.g when a new column is flicked in, the syncView should load and layout
3908 // the column first, before any syncChildren gets a chance to do the same.
3909 Q_TABLEVIEW_ASSERT(!polishing, "recursive updatePolish() calls are not allowed!");
3910 rootSyncView()->d_func()->updateTableRecursive();
3911}
3912
3914{
3915 if (polishing) {
3916 // We're already updating the Table in this view, so
3917 // we cannot continue. Signal this back by returning false.
3918 // The caller can then choose to call "polish()" instead, to
3919 // do the update later.
3920 return false;
3921 }
3922
3923 const bool updateComplete = updateTable();
3924 if (!updateComplete)
3925 return false;
3926
3927 const auto children = syncChildren;
3928 for (auto syncChild : children) {
3929 auto syncChild_d = syncChild->d_func();
3930 const int mask =
3935 syncChild_d->scheduledRebuildOptions |= rebuildOptions & ~mask;
3936
3937 const bool descendantUpdateComplete = syncChild_d->updateTableRecursive();
3938 if (!descendantUpdateComplete)
3939 return false;
3940 }
3941
3943
3944 return true;
3945}
3946
3948{
3949 // Whenever something changes, e.g viewport moves, spacing is set to a
3950 // new value, model changes etc, this function will end up being called. Here
3951 // we check what needs to be done, and load/unload cells accordingly.
3952 // If we cannot complete the update (because we need to wait for an item
3953 // to load async), we return false.
3954
3955 Q_TABLEVIEW_ASSERT(!polishing, "recursive updatePolish() calls are not allowed!");
3956 QBoolBlocker polishGuard(polishing, true);
3957
3958 if (loadRequest.isActive()) {
3959 // We're currently loading items async to build a new edge in the table. We see the loading
3960 // as an atomic operation, which means that we don't continue doing anything else until all
3961 // items have been received and laid out. Note that updatePolish is then called once more
3962 // after the loadRequest has completed to handle anything that might have occurred in-between.
3963 return false;
3964 }
3965
3969 }
3970
3972
3976 }
3977
3978 if (loadedItems.isEmpty())
3979 return !loadRequest.isActive();
3980
3983
3984 return !loadRequest.isActive();
3985}
3986
3988{
3989 if (inUpdateContentSize) {
3990 // We update the content size dynamically as we load and unload edges.
3991 // Unfortunately, this also triggers a call to this function. The base
3992 // implementation will do things like start a momentum animation or move
3993 // the content view somewhere else, which causes glitches. This can
3994 // especially happen if flicking on one of the syncView children, which triggers
3995 // an update to our content size. In that case, the base implementation don't know
3996 // that the view is being indirectly dragged, and will therefore do strange things as
3997 // it tries to 'fixup' the geometry. So we use a guard to prevent this from happening.
3998 return;
3999 }
4000
4001 QQuickFlickablePrivate::fixup(data, minExtent, maxExtent);
4002}
4003
4005{
4006 const auto data = QQmlData::get(q_func());
4007 if (!data || !data->propertyCache)
4008 return QTypeRevision::zero();
4009
4010 const auto cppMetaObject = data->propertyCache->firstCppMetaObject();
4011 const auto qmlTypeView = QQmlMetaType::qmlType(cppMetaObject);
4012
4013 // TODO: did we rather want qmlTypeView.revision() here?
4014 return qmlTypeView.metaObjectRevision();
4015}
4016
4018{
4019 Q_Q(QQuickTableView);
4020 // When the assigned model is not an instance model, we create a wrapper
4021 // model (QQmlTableInstanceModel) that keeps a pointer to both the
4022 // assigned model and the assigned delegate. This model will give us a
4023 // common interface to any kind of model (js arrays, QAIM, number etc), and
4024 // help us create delegate instances.
4027 model = tableModel;
4028}
4029
4031{
4032 if (!selectionModel)
4033 return false;
4034
4036 if (!model)
4037 return false;
4038
4039 return selectionModel->isSelected(q_func()->modelIndex(cell));
4040}
4041
4043{
4044 if (!selectionModel)
4045 return false;
4046
4048 if (!model)
4049 return false;
4050
4051 return selectionModel->currentIndex() == q_func()->modelIndex(cell);
4052}
4053
4055{
4056 if (!selectionModel->hasSelection()) {
4057 // Ensure that we cancel any ongoing key/mouse-based selections
4058 // if selectionModel.clearSelection() is called.
4060 }
4061
4062 const auto &selectedIndexes = selected.indexes();
4063 const auto &deselectedIndexes = deselected.indexes();
4064 for (int i = 0; i < selectedIndexes.size(); ++i)
4065 setSelectedOnDelegateItem(selectedIndexes.at(i), true);
4066 for (int i = 0; i < deselectedIndexes.size(); ++i)
4067 setSelectedOnDelegateItem(deselectedIndexes.at(i), false);
4068}
4069
4071{
4072 const int cellIndex = modelIndexToCellIndex(modelIndex);
4073 if (!loadedItems.contains(cellIndex))
4074 return;
4075 const QPoint cell = cellAtModelIndex(cellIndex);
4078}
4079
4081{
4082 // If modelAsVariant wraps a qaim, return it
4083 if (modelAsVariant.userType() == qMetaTypeId<QJSValue>())
4084 modelAsVariant = modelAsVariant.value<QJSValue>().toVariant();
4085 return qvariant_cast<QAbstractItemModel *>(modelAsVariant);
4086}
4087
4089{
4091
4092 for (auto it = loadedItems.keyBegin(), end = loadedItems.keyEnd(); it != end; ++it) {
4093 const int cellIndex = *it;
4094 const QPoint cell = cellAtModelIndex(cellIndex);
4095 const bool selected = selectedInSelectionModel(cell);
4096 const bool current = currentInSelectionModel(cell);
4098 const bool editing = editIndex == q_func()->modelIndex(cell);
4102 }
4103}
4104
4106{
4107 // Warn if the source models are not the same
4108 const QAbstractItemModel *qaimInSelection = selectionModel ? selectionModel->model() : nullptr;
4109 const QAbstractItemModel *qaimInTableView = qaim(modelImpl());
4110 if (qaimInSelection && qaimInSelection != qaimInTableView)
4111 qmlWarning(q_func()) << "TableView.selectionModel.model differs from TableView.model";
4112
4114 setCurrentOnDelegateItem(previous, false);
4115 setCurrentOnDelegateItem(current, true);
4116}
4117
4119{
4120 Q_Q(QQuickTableView);
4121
4123 const QPoint currentCell = q->cellAtIndex(currentIndex);
4124 if (currentCell.x() != currentColumn) {
4125 currentColumn = currentCell.x();
4126 emit q->currentColumnChanged();
4127 }
4128
4129 if (currentCell.y() != currentRow) {
4130 currentRow = currentCell.y();
4131 emit q->currentRowChanged();
4132 }
4133}
4134
4136{
4137 const int cellIndex = modelIndexToCellIndex(index);
4138 if (!loadedItems.contains(cellIndex))
4139 return;
4140
4141 const QPoint cell = cellAtModelIndex(cellIndex);
4144}
4145
4147{
4149 return;
4150
4151 qCDebug(lcTableViewDelegateLifecycle) << "item done loading:"
4152 << cellAtModelIndex(modelIndex);
4153
4154 // Since the item we waited for has finished incubating, we can
4155 // continue with the load request. processLoadRequest will
4156 // ask the model for the requested item once more, which will be
4157 // quick since the model has cached it.
4160 updatePolish();
4161}
4162
4164{
4165 Q_Q(QQuickTableView);
4166
4167 auto item = qobject_cast<QQuickItem*>(object);
4168 if (!item)
4169 return;
4170
4171 item->setParentItem(q->contentItem());
4172 item->setZ(1);
4173
4174 const QPoint cell = cellAtModelIndex(modelIndex);
4175 const bool current = currentInSelectionModel(cell);
4176 const bool selected = selectedInSelectionModel(cell);
4177 setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(current), modelIndex, object, true);
4178 setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(selected), modelIndex, object, true);
4180
4181 if (auto attached = getAttachedObject(object))
4182 attached->setView(q);
4183}
4184
4186{
4187 Q_UNUSED(modelIndex);
4188
4189 if (auto attached = getAttachedObject(object))
4190 emit attached->pooled();
4191}
4192
4194{
4195 const QPoint cell = cellAtModelIndex(modelIndex);
4196 const bool current = currentInSelectionModel(cell);
4197 const bool selected = selectedInSelectionModel(cell);
4198 setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(current), modelIndex, object, false);
4199 setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(selected), modelIndex, object, false);
4200 // Note: the edit item will never be reused, so no reason to set kRequiredProperty_editing
4201
4202 if (auto item = qobject_cast<QQuickItem*>(object))
4204
4205 if (auto attached = getAttachedObject(object))
4206 emit attached->reused();
4207}
4208
4210{
4211 // The application can change properties like the model or the delegate while
4212 // we're e.g in the middle of e.g loading a new row. Since this will lead to
4213 // unpredicted behavior, and possibly a crash, we need to postpone taking
4214 // such assignments into effect until we're in a state that allows it.
4215
4217 syncModel();
4218 syncDelegate();
4219 syncSyncView();
4221
4223}
4224
4226{
4228 return;
4229
4233
4234 if (loadedItems.isEmpty())
4236
4237 // Some options are exclusive:
4238 if (rebuildOptions.testFlag(RebuildOption::All)) {
4243 } else if (rebuildOptions.testFlag(RebuildOption::ViewportOnly)) {
4245 }
4246
4249
4252}
4253
4255{
4256 if (!tableModel) {
4257 // Only the tableModel uses the delegate assigned to a
4258 // TableView. DelegateModel has it's own delegate, and
4259 // ObjectModel etc. doesn't use one.
4260 return;
4261 }
4262
4265}
4266
4268{
4269 return assignedModel;
4270}
4271
4273{
4274 if (newModel == assignedModel)
4275 return;
4276
4277 assignedModel = newModel;
4279 emit q_func()->modelChanged();
4280}
4281
4283{
4285 return;
4286
4287 if (model) {
4290 }
4291
4293 QVariant effectiveModelVariant = modelVariant;
4294 if (effectiveModelVariant.userType() == qMetaTypeId<QJSValue>())
4295 effectiveModelVariant = effectiveModelVariant.value<QJSValue>().toVariant();
4296
4297 const auto instanceModel = qobject_cast<QQmlInstanceModel *>(qvariant_cast<QObject*>(effectiveModelVariant));
4298
4299 if (instanceModel) {
4300 if (tableModel) {
4301 delete tableModel;
4302 tableModel = nullptr;
4303 }
4304 model = instanceModel;
4305 } else {
4306 if (!tableModel)
4308 tableModel->setModel(effectiveModelVariant);
4309 }
4310
4312}
4313
4315{
4316 Q_Q(QQuickTableView);
4317
4318 if (assignedSyncView != syncView) {
4319 if (syncView)
4320 syncView->d_func()->syncChildren.removeOne(q);
4321
4322 if (assignedSyncView) {
4324
4325 while (view) {
4326 if (view == q) {
4327 if (!layoutWarningIssued) {
4328 layoutWarningIssued = true;
4329 qmlWarning(q) << "TableView: recursive syncView connection detected!";
4330 }
4331 syncView = nullptr;
4332 return;
4333 }
4334 view = view->d_func()->syncView;
4335 }
4336
4337 assignedSyncView->d_func()->syncChildren.append(q);
4339 }
4340
4342 }
4343
4346
4347 if (syncHorizontally) {
4348 QBoolBlocker fixupGuard(inUpdateContentSize, true);
4349 q->setColumnSpacing(syncView->columnSpacing());
4350 q->setLeftMargin(syncView->leftMargin());
4351 q->setRightMargin(syncView->rightMargin());
4353
4354 if (syncView->leftColumn() != q->leftColumn()) {
4355 // The left column is no longer the same as the left
4356 // column in syncView. This requires a rebuild.
4359 }
4360 }
4361
4362 if (syncVertically) {
4363 QBoolBlocker fixupGuard(inUpdateContentSize, true);
4364 q->setRowSpacing(syncView->rowSpacing());
4365 q->setTopMargin(syncView->topMargin());
4366 q->setBottomMargin(syncView->bottomMargin());
4368
4369 if (syncView->topRow() != q->topRow()) {
4370 // The top row is no longer the same as the top
4371 // row in syncView. This requires a rebuild.
4374 }
4375 }
4376
4377 if (syncView && loadedItems.isEmpty() && !tableSize.isEmpty()) {
4378 // When we have a syncView, we can sometimes temporarily end up with no loaded items.
4379 // This can happen if the syncView has a model with more rows or columns than us, in
4380 // which case the viewport can end up in a place where we have no rows or columns to
4381 // show. In that case, check now if the viewport has been flicked back again, and
4382 // that we can rebuild the table with a visible top-left cell.
4383 const auto syncView_d = syncView->d_func();
4384 if (!syncView_d->loadedItems.isEmpty()) {
4385 if (syncHorizontally && syncView_d->leftColumn() <= tableSize.width() - 1)
4387 else if (syncVertically && syncView_d->topRow() <= tableSize.height() - 1)
4389 }
4390 }
4391}
4392
4394{
4395 // Only positionViewAtRowAfterRebuild/positionViewAtColumnAfterRebuild are critical
4396 // to sync before a rebuild to avoid them being overwritten
4397 // by the setters while building. The other position properties
4398 // can change without it causing trouble.
4401}
4402
4404{
4405 Q_Q(QQuickTableView);
4407
4410 QObjectPrivate::connect(model, &QQmlTableInstanceModel::itemPooled, this, &QQuickTableViewPrivate::itemPooledCallback);
4411 QObjectPrivate::connect(model, &QQmlTableInstanceModel::itemReused, this, &QQuickTableViewPrivate::itemReusedCallback);
4412
4413 // Connect atYEndChanged to a function that fetches data if more is available
4415
4416 if (auto const aim = model->abstractItemModel()) {
4417 // When the model exposes a QAIM, we connect to it directly. This means that if the current model is
4418 // a QQmlDelegateModel, we just ignore all the change sets it emits. In most cases, the model will instead
4419 // be our own QQmlTableInstanceModel, which doesn't bother creating change sets at all. For models that are
4420 // not based on QAIM (like QQmlObjectModel, QQmlListModel, javascript arrays etc), there is currently no way
4421 // to modify the model at runtime without also re-setting the model on the view.
4430 } else {
4432 }
4433}
4434
4436{
4437 Q_Q(QQuickTableView);
4439
4442 QObjectPrivate::disconnect(model, &QQmlTableInstanceModel::itemPooled, this, &QQuickTableViewPrivate::itemPooledCallback);
4443 QObjectPrivate::disconnect(model, &QQmlTableInstanceModel::itemReused, this, &QQuickTableViewPrivate::itemReusedCallback);
4444
4446
4447 if (auto const aim = model->abstractItemModel()) {
4456 } else {
4458 }
4459}
4460
4462{
4463 Q_UNUSED(changeSet);
4464 Q_UNUSED(reset);
4465
4470}
4471
4473{
4474 if (parent != QModelIndex())
4475 return;
4476
4478}
4479
4481{
4482 if (parent != QModelIndex())
4483 return;
4484
4486}
4487
4489{
4490 if (parent != QModelIndex())
4491 return;
4492
4494}
4495
4497{
4498 Q_Q(QQuickTableView);
4499
4500 if (parent != QModelIndex())
4501 return;
4502
4503 // If editIndex was a part of the removed rows, it will now be invalid.
4504 if (!editIndex.isValid() && editItem)
4505 q->closeEditor();
4506
4508}
4509
4511{
4512 if (parent != QModelIndex())
4513 return;
4514
4515 // Adding a column (or row) can result in the table going from being
4516 // e.g completely inside the viewport to go outside. And in the latter
4517 // case, the user needs to be able to scroll the viewport, also if
4518 // flags such as Flickable.StopAtBounds is in use. So we need to
4519 // update contentWidth to support that case.
4521}
4522
4524{
4525 Q_Q(QQuickTableView);
4526
4527 if (parent != QModelIndex())
4528 return;
4529
4530 // If editIndex was a part of the removed columns, it will now be invalid.
4531 if (!editIndex.isValid() && editItem)
4532 q->closeEditor();
4533
4535}
4536
4538{
4539 Q_UNUSED(parents);
4540 Q_UNUSED(hint);
4541
4543}
4544
4546{
4550 }
4551}
4552
4554{
4555 Q_Q(QQuickTableView);
4556 q->closeEditor();
4558}
4559
4561{
4562 Qt::Alignment verticalAlignment = alignment & (Qt::AlignTop | Qt::AlignVCenter | Qt::AlignBottom);
4563 Q_TABLEVIEW_ASSERT(verticalAlignment, alignment);
4564
4565 if (syncHorizontally) {
4566 syncView->d_func()->positionViewAtRow(row, verticalAlignment, offset, subRect);
4567 } else {
4568 if (!scrollToRow(row, verticalAlignment, offset, subRect)) {
4569 // Could not scroll, so rebuild instead
4571 positionViewAtRowAlignment = verticalAlignment;
4573 positionViewAtRowSubRect = subRect;
4576 }
4577 }
4578}
4579
4581{
4582 Qt::Alignment horizontalAlignment = alignment & (Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight);
4583 Q_TABLEVIEW_ASSERT(horizontalAlignment, alignment);
4584
4585 if (syncVertically) {
4586 syncView->d_func()->positionViewAtColumn(column, horizontalAlignment, offset, subRect);
4587 } else {
4588 if (!scrollToColumn(column, horizontalAlignment, offset, subRect)) {
4589 // Could not scroll, so rebuild instead
4591 positionViewAtColumnAlignment = horizontalAlignment;
4596 }
4597 }
4598}
4599
4600bool QQuickTableViewPrivate::scrollToRow(int row, Qt::Alignment alignment, qreal offset, const QRectF subRect)
4601{
4602 Q_Q(QQuickTableView);
4603
4604 // This function will only scroll to rows that are loaded (since we
4605 // don't know the location of unloaded rows). But as an exception, to
4606 // allow moving currentIndex out of the viewport, we support scrolling
4607 // to a row that is adjacent to the loaded table. So start by checking
4608 // if we should load en extra row.
4609 if (row < topRow()) {
4611 return false;
4613 } else if (row > bottomRow()) {
4615 return false;
4617 } else if (row < topRow() || row > bottomRow()) {
4618 return false;
4619 }
4620
4621 if (!loadedRows.contains(row))
4622 return false;
4623
4624 const qreal newContentY = getAlignmentContentY(row, alignment, offset, subRect);
4625 if (qFuzzyCompare(newContentY, q->contentY()))
4626 return true;
4627
4628 if (animate) {
4629 const qreal diffY = qAbs(newContentY - q->contentY());
4630 const qreal duration = qBound(700., diffY * 5, 1500.);
4631 positionYAnimation.setTo(newContentY);
4634 } else {
4636 q->setContentY(newContentY);
4637 }
4638
4639 return true;
4640}
4641
4643{
4644 Q_Q(QQuickTableView);
4645
4646 // This function will only scroll to columns that are loaded (since we
4647 // don't know the location of unloaded columns). But as an exception, to
4648 // allow moving currentIndex out of the viewport, we support scrolling
4649 // to a column that is adjacent to the loaded table. So start by checking
4650 // if we should load en extra column.
4651 if (column < leftColumn()) {
4653 return false;
4655 } else if (column > rightColumn()) {
4657 return false;
4659 } else if (column < leftColumn() || column > rightColumn()) {
4660 return false;
4661 }
4662
4664 return false;
4665
4666 const qreal newContentX = getAlignmentContentX(column, alignment, offset, subRect);
4667 if (qFuzzyCompare(newContentX, q->contentX()))
4668 return true;
4669
4670 if (animate) {
4671 const qreal diffX = qAbs(newContentX - q->contentX());
4672 const qreal duration = qBound(700., diffX * 5, 1500.);
4673 positionXAnimation.setTo(newContentX);
4676 } else {
4678 q->setContentX(newContentX);
4679 }
4680
4681 return true;
4682}
4683
4685{
4686 Q_Q(QQuickTableView);
4687 // If the viewport has moved more than one page vertically or horizontally, we switch
4688 // strategy from refilling edges around the current table to instead rebuild the table
4689 // from scratch inside the new viewport. This will greatly improve performance when flicking
4690 // a long distance in one go, which can easily happen when dragging on scrollbars.
4691 // Note that we don't want to update the content size in this case, since first of all, the
4692 // content size should logically not change as a result of flicking. But more importantly, updating
4693 // the content size in combination with fast-flicking has a tendency to cause flicker in the viewport.
4694
4695 // Check the viewport moved more than one page vertically
4696 if (!viewportRect.intersects(QRectF(viewportRect.x(), q->contentY(), 1, q->height()))) {
4699 }
4700
4701 // Check the viewport moved more than one page horizontally
4702 if (!viewportRect.intersects(QRectF(q->contentX(), viewportRect.y(), q->width(), 1))) {
4705 }
4706}
4707
4709{
4710 // Set the new viewport position if changed, but don't trigger any
4711 // rebuilds or updates. We use this function internally to distinguish
4712 // external flicking from internal sync-ing of the content view.
4713 Q_Q(QQuickTableView);
4714 QBoolBlocker blocker(inSetLocalViewportPos, true);
4715
4716 if (qFuzzyCompare(contentX, q->contentX()))
4717 return;
4718
4719 q->setContentX(contentX);
4720}
4721
4723{
4724 // Set the new viewport position if changed, but don't trigger any
4725 // rebuilds or updates. We use this function internally to distinguish
4726 // external flicking from internal sync-ing of the content view.
4727 Q_Q(QQuickTableView);
4728 QBoolBlocker blocker(inSetLocalViewportPos, true);
4729
4730 if (qFuzzyCompare(contentY, q->contentY()))
4731 return;
4732
4733 q->setContentY(contentY);
4734}
4735
4737{
4738 // Sync viewportRect so that it contains the actual geometry of the viewport.
4739 // Since the column (and row) size of a sync child is decided by the column size
4740 // of its sync view, the viewport width of a sync view needs to be the maximum of
4741 // the sync views width, and its sync childrens width. This to ensure that no sync
4742 // child loads a column which is not yet loaded by the sync view, since then the
4743 // implicit column size cannot be resolved.
4744 Q_Q(QQuickTableView);
4745
4746 qreal w = q->width();
4747 qreal h = q->height();
4748
4749 for (auto syncChild : std::as_const(syncChildren)) {
4750 auto syncChild_d = syncChild->d_func();
4751 if (syncChild_d->syncHorizontally)
4752 w = qMax(w, syncChild->width());
4753 if (syncChild_d->syncHorizontally)
4754 h = qMax(h, syncChild->height());
4755 }
4756
4757 viewportRect = QRectF(q->contentX(), q->contentY(), w, h);
4758}
4759
4761{
4762 Q_Q(QQuickTableView);
4763
4765 q->setActiveFocusOnTab(true);
4766
4770
4774
4775 auto tapHandler = new QQuickTableViewTapHandler(q);
4776
4781
4782 // To allow for a more snappy UX, we try to change the current index already upon
4783 // receiving a pointer press. But we should only do that if the view is not interactive
4784 // (so that it doesn't interfere with flicking), and if the resizeHandler is not
4785 // being hovered/dragged. For those cases, we fall back to setting the current index
4786 // on tap instead. A double tap on a resize area should also revert the section size
4787 // back to its implicit size.
4788 QObject::connect(tapHandler, &QQuickTapHandler::pressedChanged, [this, q, tapHandler] {
4789 if (!tapHandler->isPressed())
4790 return;
4791
4794
4795 if (!q->isInteractive())
4796 handleTap(tapHandler->point());
4797 });
4798
4799 QObject::connect(tapHandler, &QQuickTapHandler::singleTapped, [this, q, tapHandler] {
4800 if (q->isInteractive())
4801 handleTap(tapHandler->point());
4802 });
4803
4804 QObject::connect(tapHandler, &QQuickTapHandler::doubleTapped, [this, q, tapHandler] {
4805 const bool resizeRow = resizableRows && hoverHandler->m_row != -1;
4806 const bool resizeColumn = resizableColumns && hoverHandler->m_column != -1;
4807
4808 if (resizeRow || resizeColumn) {
4809 if (resizeRow)
4810 q->setRowHeight(hoverHandler->m_row, -1);
4811 if (resizeColumn)
4812 q->setColumnWidth(hoverHandler->m_column, -1);
4814 const QPointF pos = tapHandler->point().pressPosition();
4815 const QPoint cell = q->cellAtPosition(pos);
4816 const QModelIndex index = q->modelIndex(cell);
4817 if (canEdit(index, false))
4818 q->edit(index);
4819 }
4820 });
4821}
4822
4824{
4825 Q_Q(QQuickTableView);
4826
4828 q->forceActiveFocus(Qt::MouseFocusReason);
4829
4830 if (point.modifiers() != Qt::NoModifier)
4831 return;
4832 if (resizableRows && hoverHandler->m_row != -1)
4833 return;
4835 return;
4837 return;
4838
4839 const QModelIndex tappedIndex = q->modelIndex(q->cellAtPosition(point.position()));
4840 bool tappedCellIsSelected = false;
4841
4842 if (selectionModel)
4843 tappedCellIsSelected = selectionModel->isSelected(tappedIndex);
4844
4845 if (canEdit(tappedIndex, false)) {
4849 q->edit(tappedIndex);
4850 return;
4851 } else if (editTriggers & QQuickTableView::SelectedTapped && tappedCellIsSelected) {
4852 q->edit(tappedIndex);
4853 return;
4854 }
4855 }
4856
4857 // Since the tap didn't result in selecting or editing cells, we clear
4858 // the current selection and move the current index instead.
4860 q->closeEditor();
4864 }
4865}
4866
4867bool QQuickTableViewPrivate::canEdit(const QModelIndex tappedIndex, bool warn)
4868{
4869 // Check that a call to edit(tappedIndex) would not
4870 // result in warnings being printed.
4871 Q_Q(QQuickTableView);
4872
4873 if (!tappedIndex.isValid()) {
4874 if (warn)
4875 qmlWarning(q) << "cannot edit: index is not valid!";
4876 return false;
4877 }
4878
4879 if (auto const qaim = model->abstractItemModel()) {
4880 if (!(qaim->flags(tappedIndex) & Qt::ItemIsEditable)) {
4881 if (warn)
4882 qmlWarning(q) << "cannot edit: QAbstractItemModel::flags(index) doesn't contain Qt::ItemIsEditable";
4883 return false;
4884 }
4885 }
4886
4887 const QPoint cell = q->cellAtIndex(tappedIndex);
4888 const QQuickItem *cellItem = q->itemAtCell(cell);
4889 if (!cellItem) {
4890 if (warn)
4891 qmlWarning(q) << "cannot edit: the cell to edit is not inside the viewport!";
4892 return false;
4893 }
4894
4895 auto attached = getAttachedObject(cellItem);
4896 if (!attached || !attached->editDelegate()) {
4897 if (warn)
4898 qmlWarning(q) << "cannot edit: no TableView.editDelegate set!";
4899 return false;
4900 }
4901
4902 return true;
4903}
4904
4906{
4907 Q_Q(QQuickTableView);
4908 QBoolBlocker recursionGuard(inSyncViewportPosRecursive, true);
4909
4910 if (syncView) {
4911 auto syncView_d = syncView->d_func();
4912 if (!syncView_d->inSyncViewportPosRecursive) {
4913 if (syncHorizontally)
4914 syncView_d->setLocalViewportX(q->contentX());
4915 if (syncVertically)
4916 syncView_d->setLocalViewportY(q->contentY());
4917 syncView_d->syncViewportPosRecursive();
4918 }
4919 }
4920
4921 for (auto syncChild : std::as_const(syncChildren)) {
4922 auto syncChild_d = syncChild->d_func();
4923 if (!syncChild_d->inSyncViewportPosRecursive) {
4924 if (syncChild_d->syncHorizontally)
4925 syncChild_d->setLocalViewportX(q->contentX());
4926 if (syncChild_d->syncVertically)
4927 syncChild_d->setLocalViewportY(q->contentY());
4928 syncChild_d->syncViewportPosRecursive();
4929 }
4930 }
4931}
4932
4934{
4935 Q_Q(QQuickTableView);
4936
4937 const QPoint cell = q->cellAtPosition(pos);
4938 if (!cellIsValid(cell))
4939 return;
4940
4941 setCurrentIndex(cell);
4942}
4943
4945{
4946 if (!selectionModel)
4947 return;
4948
4949 const auto index = q_func()->modelIndex(cell);
4951}
4952
4954{
4955 Q_Q(QQuickTableView);
4956
4958 return false;
4959
4960 const QModelIndex currentIndex = selectionModel->currentIndex();
4961 const QPoint currentCell = q->cellAtIndex(currentIndex);
4962 const bool select = (e->modifiers() & Qt::ShiftModifier) && (e->key() != Qt::Key_Backtab);
4963
4964 if (!q->activeFocusOnTab()) {
4965 switch (e->key()) {
4966 case Qt::Key_Tab:
4967 case Qt::Key_Backtab:
4968 return false;
4969 }
4970 }
4971
4972 if (!cellIsValid(currentCell)) {
4973 switch (e->key()) {
4974 case Qt::Key_Up:
4975 case Qt::Key_Down:
4976 case Qt::Key_Left:
4977 case Qt::Key_Right:
4978 case Qt::Key_PageUp:
4979 case Qt::Key_PageDown:
4980 case Qt::Key_Home:
4981 case Qt::Key_End:
4982 case Qt::Key_Tab:
4983 case Qt::Key_Backtab:
4984 // Special case: the current index doesn't map to a cell in the view (perhaps
4985 // because it isn't set yet). In that case, we set it to be the top-left cell.
4986 const QModelIndex topLeftIndex = q->index(topRow(), leftColumn());
4988 return true;
4989 }
4990 return false;
4991 }
4992
4993 auto beginMoveCurrentIndex = [&](){
4994 if (!select) {
4996 } else if (selectionRectangle().isEmpty()) {
4997 const int serializedStartIndex = modelIndexToCellIndex(selectionModel->currentIndex());
4998 if (loadedItems.contains(serializedStartIndex)) {
4999 const QRectF startGeometry = loadedItems.value(serializedStartIndex)->geometry();
5000 setSelectionStartPos(startGeometry.center());
5001 }
5002 }
5003 };
5004
5005 auto endMoveCurrentIndex = [&](const QPoint &cell){
5006 if (select) {
5007 if (polishScheduled)
5008 forceLayout(true);
5009 const int serializedEndIndex = modelIndexAtCell(cell);
5010 if (loadedItems.contains(serializedEndIndex)) {
5011 const QRectF endGeometry = loadedItems.value(serializedEndIndex)->geometry();
5012 setSelectionEndPos(endGeometry.center());
5013 }
5014 }
5016 };
5017
5018 switch (e->key()) {
5019 case Qt::Key_Up: {
5020 beginMoveCurrentIndex();
5021 const int nextRow = nextVisibleEdgeIndex(Qt::TopEdge, currentCell.y() - 1);
5022 if (nextRow == kEdgeIndexAtEnd)
5023 break;
5024 const qreal marginY = atTableEnd(Qt::TopEdge, nextRow - 1) ? -q->topMargin() : 0;
5025 q->positionViewAtRow(nextRow, QQuickTableView::Contain, marginY);
5026 endMoveCurrentIndex({currentCell.x(), nextRow});
5027 break; }
5028 case Qt::Key_Down: {
5029 beginMoveCurrentIndex();
5030 const int nextRow = nextVisibleEdgeIndex(Qt::BottomEdge, currentCell.y() + 1);
5031 if (nextRow == kEdgeIndexAtEnd)
5032 break;
5033 const qreal marginY = atTableEnd(Qt::BottomEdge, nextRow + 1) ? q->bottomMargin() : 0;
5034 q->positionViewAtRow(nextRow, QQuickTableView::Contain, marginY);
5035 endMoveCurrentIndex({currentCell.x(), nextRow});
5036 break; }
5037 case Qt::Key_Left: {
5038 beginMoveCurrentIndex();
5039 const int nextColumn = nextVisibleEdgeIndex(Qt::LeftEdge, currentCell.x() - 1);
5040 if (nextColumn == kEdgeIndexAtEnd)
5041 break;
5042 const qreal marginX = atTableEnd(Qt::LeftEdge, nextColumn - 1) ? -q->leftMargin() : 0;
5043 q->positionViewAtColumn(nextColumn, QQuickTableView::Contain, marginX);
5044 endMoveCurrentIndex({nextColumn, currentCell.y()});
5045 break; }
5046 case Qt::Key_Right: {
5047 beginMoveCurrentIndex();
5048 const int nextColumn = nextVisibleEdgeIndex(Qt::RightEdge, currentCell.x() + 1);
5049 if (nextColumn == kEdgeIndexAtEnd)
5050 break;
5051 const qreal marginX = atTableEnd(Qt::RightEdge, nextColumn + 1) ? q->rightMargin() : 0;
5052 q->positionViewAtColumn(nextColumn, QQuickTableView::Contain, marginX);
5053 endMoveCurrentIndex({nextColumn, currentCell.y()});
5054 break; }
5055 case Qt::Key_PageDown: {
5056 int newBottomRow = -1;
5057 beginMoveCurrentIndex();
5058 if (currentCell.y() < bottomRow()) {
5059 // The first PageDown should just move currentIndex to the bottom
5060 newBottomRow = bottomRow();
5061 q->positionViewAtRow(newBottomRow, QQuickTableView::AlignBottom, 0);
5062 } else {
5063 q->positionViewAtRow(bottomRow(), QQuickTableView::AlignTop, 0);
5065 newBottomRow = topRow() != bottomRow() ? bottomRow() : bottomRow() + 1;
5066 const qreal marginY = atTableEnd(Qt::BottomEdge, newBottomRow + 1) ? q->bottomMargin() : 0;
5067 q->positionViewAtRow(newBottomRow, QQuickTableView::AlignTop | QQuickTableView::AlignBottom, marginY);
5069 }
5070 endMoveCurrentIndex(QPoint(currentCell.x(), newBottomRow));
5071 break; }
5072 case Qt::Key_PageUp: {
5073 int newTopRow = -1;
5074 beginMoveCurrentIndex();
5075 if (currentCell.y() > topRow()) {
5076 // The first PageUp should just move currentIndex to the top
5077 newTopRow = topRow();
5078 q->positionViewAtRow(newTopRow, QQuickTableView::AlignTop, 0);
5079 } else {
5080 q->positionViewAtRow(topRow(), QQuickTableView::AlignBottom, 0);
5082 newTopRow = topRow() != bottomRow() ? topRow() : topRow() - 1;
5083 const qreal marginY = atTableEnd(Qt::TopEdge, newTopRow - 1) ? -q->topMargin() : 0;
5084 q->positionViewAtRow(newTopRow, QQuickTableView::AlignTop, marginY);
5086 }
5087 endMoveCurrentIndex(QPoint(currentCell.x(), newTopRow));
5088 break; }
5089 case Qt::Key_Home: {
5090 beginMoveCurrentIndex();
5091 const int firstColumn = nextVisibleEdgeIndex(Qt::RightEdge, 0);
5092 q->positionViewAtColumn(firstColumn, QQuickTableView::AlignLeft, -q->leftMargin());
5093 endMoveCurrentIndex(QPoint(firstColumn, currentCell.y()));
5094 break; }
5095 case Qt::Key_End: {
5096 beginMoveCurrentIndex();
5097 const int lastColumn = nextVisibleEdgeIndex(Qt::LeftEdge, tableSize.width() - 1);
5098 q->positionViewAtColumn(lastColumn, QQuickTableView::AlignRight, q->rightMargin());
5099 endMoveCurrentIndex(QPoint(lastColumn, currentCell.y()));
5100 break; }
5101 case Qt::Key_Tab: {
5102 beginMoveCurrentIndex();
5103 int nextRow = currentCell.y();
5104 int nextColumn = nextVisibleEdgeIndex(Qt::RightEdge, currentCell.x() + 1);
5105 if (nextColumn == kEdgeIndexAtEnd) {
5106 nextRow = nextVisibleEdgeIndex(Qt::BottomEdge, currentCell.y() + 1);
5107 if (nextRow == kEdgeIndexAtEnd)
5109 nextColumn = nextVisibleEdgeIndex(Qt::RightEdge, 0);
5110 const qreal marginY = atTableEnd(Qt::BottomEdge, nextRow + 1) ? q->bottomMargin() : 0;
5111 q->positionViewAtRow(nextRow, QQuickTableView::Contain, marginY);
5112 }
5113
5114 qreal marginX = 0;
5115 if (atTableEnd(Qt::RightEdge, nextColumn + 1))
5116 marginX = q->leftMargin();
5117 else if (atTableEnd(Qt::LeftEdge, nextColumn - 1))
5118 marginX = -q->leftMargin();
5119
5120 q->positionViewAtColumn(nextColumn, QQuickTableView::Contain, marginX);
5121 endMoveCurrentIndex({nextColumn, nextRow});
5122 break; }
5123 case Qt::Key_Backtab: {
5124 beginMoveCurrentIndex();
5125 int nextRow = currentCell.y();
5126 int nextColumn = nextVisibleEdgeIndex(Qt::LeftEdge, currentCell.x() - 1);
5127 if (nextColumn == kEdgeIndexAtEnd) {
5128 nextRow = nextVisibleEdgeIndex(Qt::TopEdge, currentCell.y() - 1);
5129 if (nextRow == kEdgeIndexAtEnd)
5131 nextColumn = nextVisibleEdgeIndex(Qt::LeftEdge, tableSize.width() - 1);
5132 const qreal marginY = atTableEnd(Qt::TopEdge, nextRow - 1) ? -q->topMargin() : 0;
5133 q->positionViewAtRow(nextRow, QQuickTableView::Contain, marginY);
5134 }
5135
5136 qreal marginX = 0;
5137 if (atTableEnd(Qt::RightEdge, nextColumn + 1))
5138 marginX = q->leftMargin();
5139 else if (atTableEnd(Qt::LeftEdge, nextColumn - 1))
5140 marginX = -q->leftMargin();
5141
5142 q->positionViewAtColumn(nextColumn, QQuickTableView::Contain, marginX);
5143 endMoveCurrentIndex({nextColumn, nextRow});
5144 break; }
5145 default:
5146 return false;
5147 }
5148
5149 return true;
5150}
5151
5153{
5154 Q_Q(QQuickTableView);
5155
5157 return false;
5159 return false;
5160
5162 const QPoint cell = q->cellAtIndex(index);
5163 const QQuickItem *cellItem = q->itemAtCell(cell);
5164 if (!cellItem)
5165 return false;
5166
5167 auto attached = getAttachedObject(cellItem);
5168 if (!attached || !attached->editDelegate())
5169 return false;
5170
5171 bool anyKeyPressed = false;
5172 bool editKeyPressed = false;
5173
5174 switch (e->key()) {
5175 case Qt::Key_Return:
5176 case Qt::Key_Enter:
5177#ifndef Q_OS_MACOS
5178 case Qt::Key_F2:
5179#endif
5180 anyKeyPressed = true;
5181 editKeyPressed = true;
5182 break;
5183 case Qt::Key_Shift:
5184 case Qt::Key_Alt:
5185 case Qt::Key_Control:
5186 case Qt::Key_Meta:
5187 case Qt::Key_Tab:
5188 case Qt::Key_Backtab:
5189 break;
5190 default:
5191 anyKeyPressed = true;
5192 }
5193
5194 const bool anyKeyAccepted = anyKeyPressed && (editTriggers & QQuickTableView::AnyKeyPressed);
5195 const bool editKeyAccepted = editKeyPressed && (editTriggers & QQuickTableView::EditKeyPressed);
5196
5197 if (!(editKeyAccepted || anyKeyAccepted))
5198 return false;
5199
5200 if (!canEdit(index, false)) {
5201 // If canEdit() returns false at this point (e.g because currentIndex is not
5202 // editable), we still want to eat the key event, to keep a consistent behavior
5203 // when some cells are editable, but others not.
5204 return true;
5205 }
5206
5207 q->edit(index);
5208
5209 if (editIndex.isValid() && anyKeyAccepted && !editKeyPressed) {
5210 // Replay the key event to the focus object (which should at this point
5211 // be the edit item, or an item inside the edit item).
5213 }
5214
5215 return true;
5216}
5217
5218#if QT_CONFIG(cursor)
5219void QQuickTableViewPrivate::updateCursor()
5220{
5221 int row = resizableRows ? hoverHandler->m_row : -1;
5223
5224 const auto resizeState = resizeHandler->state();
5226 || resizeState == QQuickTableViewResizeHandler::Dragging) {
5227 // Don't change the cursor while resizing, even if
5228 // the pointer is not actually hovering the grid.
5231 }
5232
5233 if (row != -1 || column != -1) {
5234 Qt::CursorShape shape;
5235 if (row != -1 && column != -1)
5236 shape = Qt::SizeFDiagCursor;
5237 else if (row != -1)
5238 shape = Qt::SplitVCursor;
5239 else
5240 shape = Qt::SplitHCursor;
5241
5242 if (m_cursorSet)
5243 qApp->changeOverrideCursor(shape);
5244 else
5245 qApp->setOverrideCursor(shape);
5246
5247 m_cursorSet = true;
5248 } else if (m_cursorSet) {
5249 qApp->restoreOverrideCursor();
5250 m_cursorSet = false;
5251 }
5252}
5253#endif
5254
5256{
5257 Q_Q(QQuickTableView);
5258
5259 if (!editItem)
5260 return;
5261
5262 const QPoint cell = q->cellAtIndex(editIndex);
5263 auto cellItem = q->itemAtCell(cell);
5264 if (!cellItem) {
5265 // The delegate item that is being edited has left the viewport. But since we
5266 // added an extra reference to it when editing began, the delegate item has
5267 // not been unloaded! It's therefore still on the content item (outside the
5268 // viewport), but its position will no longer be updated until the row and column
5269 // it's a part of enters the viewport again. To avoid glitches related to the
5270 // item showing up on wrong places (e.g after resizing a column in front of it),
5271 // we move it far out of the viewport. This way it will be "hidden", but continue
5272 // to have edit focus. When the row and column that it's a part of are eventually
5273 // flicked back in again, a relayout will move it back to the correct place.
5274 editItem->parentItem()->setX(-editItem->width() - 10000);
5275 }
5276}
5277
5280{
5281 d_func()->init();
5282}
5283
5285 : QQuickFlickable(dd, parent)
5286{
5287 d_func()->init();
5288}
5289
5291{
5292}
5293
5295{
5296 // componentComplete() is called on us after all static values have been assigned, but
5297 // before bindings to any anchestors has been evaluated. Especially this means that
5298 // if our size is bound to the parents size, it will still be empty at that point.
5299 // And we cannot build the table without knowing our own size. We could wait until we
5300 // got the first updatePolish() callback, but at that time, any asynchronous loaders that we
5301 // might be inside have already finished loading, which means that we would load all
5302 // the delegate items synchronously instead of asynchronously. We therefore use componentFinalized
5303 // which gets called after all the bindings we rely on has been evaluated.
5304 // When receiving this call, we load the delegate items (and build the table).
5305
5306 // Now that all bindings are evaluated, and we know
5307 // our final geometery, we can build the table.
5308 Q_D(QQuickTableView);
5309 qCDebug(lcTableViewDelegateLifecycle);
5310 d->updatePolish();
5311}
5312
5314{
5315 return QQuickFlickable::minXExtent() - d_func()->origin.x();
5316}
5317
5319{
5320 return QQuickFlickable::maxXExtent() - d_func()->endExtent.width();
5321}
5322
5324{
5325 return QQuickFlickable::minYExtent() - d_func()->origin.y();
5326}
5327
5329{
5330 return QQuickFlickable::maxYExtent() - d_func()->endExtent.height();
5331}
5332
5334{
5335 return d_func()->tableSize.height();
5336}
5337
5339{
5340 return d_func()->tableSize.width();
5341}
5342
5344{
5345 return d_func()->cellSpacing.height();
5346}
5347
5349{
5350 Q_D(QQuickTableView);
5352 return;
5353 if (qFuzzyCompare(d->cellSpacing.height(), spacing))
5354 return;
5355
5356 d->cellSpacing.setHeight(spacing);
5360}
5361
5363{
5364 return d_func()->cellSpacing.width();
5365}
5366
5368{
5369 Q_D(QQuickTableView);
5371 return;
5372 if (qFuzzyCompare(d->cellSpacing.width(), spacing))
5373 return;
5374
5375 d->cellSpacing.setWidth(spacing);
5379}
5380
5382{
5383 return d_func()->rowHeightProvider;
5384}
5385
5387{
5388 Q_D(QQuickTableView);
5389 if (provider.strictlyEquals(d->rowHeightProvider))
5390 return;
5391
5392 d->rowHeightProvider = provider;
5396}
5397
5399{
5400 return d_func()->columnWidthProvider;
5401}
5402
5404{
5405 Q_D(QQuickTableView);
5406 if (provider.strictlyEquals(d->columnWidthProvider))
5407 return;
5408
5409 d->columnWidthProvider = provider;
5413}
5414
5416{
5417 return d_func()->modelImpl();
5418}
5419
5421{
5422 Q_D(QQuickTableView);
5423
5424 closeEditor();
5425 d->setModelImpl(newModel);
5426
5427 if (d->selectionModel)
5428 d->selectionModel->setModel(d->qaim(newModel));
5429}
5430
5432{
5433 return d_func()->assignedDelegate;
5434}
5435
5437{
5438 Q_D(QQuickTableView);
5439 if (newDelegate == d->assignedDelegate)
5440 return;
5441
5442 d->assignedDelegate = newDelegate;
5443 d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::All);
5444
5446}
5447
5448QQuickTableView::EditTriggers QQuickTableView::editTriggers() const
5449{
5450 return d_func()->editTriggers;
5451}
5452
5453void QQuickTableView::setEditTriggers(QQuickTableView::EditTriggers editTriggers)
5454{
5455 Q_D(QQuickTableView);
5456 if (editTriggers == d->editTriggers)
5457 return;
5458
5459 d->editTriggers = editTriggers;
5460
5461 emit editTriggersChanged();
5462}
5463
5465{
5466 return bool(d_func()->reusableFlag == QQmlTableInstanceModel::Reusable);
5467}
5468
5470{
5471 Q_D(QQuickTableView);
5472 if (reuseItems() == reuse)
5473 return;
5474
5476
5477 if (!reuse && d->tableModel) {
5478 // When we're told to not reuse items, we
5479 // immediately, as documented, drain the pool.
5480 d->tableModel->drainReusableItemsPool(0);
5481 }
5482
5484}
5485
5487{
5488 Q_D(QQuickTableView);
5489 d->explicitContentWidth = width;
5491}
5492
5494{
5495 Q_D(QQuickTableView);
5496 d->explicitContentHeight = height;
5498}
5499
5518{
5519 return d_func()->assignedSyncView;
5520}
5521
5523{
5524 Q_D(QQuickTableView);
5525 if (d->assignedSyncView == view)
5526 return;
5527
5528 d->assignedSyncView = view;
5530
5531 emit syncViewChanged();
5532}
5533
5552Qt::Orientations QQuickTableView::syncDirection() const
5553{
5554 return d_func()->assignedSyncDirection;
5555}
5556
5558{
5559 Q_D(QQuickTableView);
5560 if (d->assignedSyncDirection == direction)
5561 return;
5562
5563 d->assignedSyncDirection = direction;
5564 if (d->assignedSyncView)
5566
5567 emit syncDirectionChanged();
5568}
5569
5571{
5572 return d_func()->selectionModel;
5573}
5574
5576{
5577 Q_D(QQuickTableView);
5578 if (d->selectionModel == selectionModel)
5579 return;
5580
5581 // Note: There is no need to rebuild the table when the selection model
5582 // changes, since selections only affect the internals of the delegate
5583 // items, and not the layout of the TableView.
5584
5585 if (d->selectionModel) {
5590 }
5591
5592 d->selectionModel = selectionModel;
5593
5594 if (d->selectionModel) {
5595 d->selectionModel->setModel(d->qaim(d->modelImpl()));
5600 }
5601
5602 d->updateSelectedOnAllDelegateItems();
5603
5604 emit selectionModelChanged();
5605}
5606
5608{
5609 return d_func()->animate;
5610}
5611
5613{
5614 Q_D(QQuickTableView);
5615 if (d->animate == animate)
5616 return;
5617
5618 d->animate = animate;
5619 if (!animate) {
5620 d->positionXAnimation.stop();
5621 d->positionYAnimation.stop();
5622 }
5623
5624 emit animateChanged();
5625}
5626
5628{
5629 return d_func()->keyNavigationEnabled;
5630}
5631
5633{
5634 Q_D(QQuickTableView);
5635 if (d->keyNavigationEnabled == enabled)
5636 return;
5637
5638 d->keyNavigationEnabled = enabled;
5639
5640 emit keyNavigationEnabledChanged();
5641}
5642
5644{
5645 return d_func()->pointerNavigationEnabled;
5646}
5647
5649{
5650 Q_D(QQuickTableView);
5651 if (d->pointerNavigationEnabled == enabled)
5652 return;
5653
5654 d->pointerNavigationEnabled = enabled;
5655
5656 emit pointerNavigationEnabledChanged();
5657}
5658
5660{
5661 Q_D(const QQuickTableView);
5662 return d->loadedItems.isEmpty() ? -1 : d_func()->leftColumn();
5663}
5664
5666{
5667 Q_D(const QQuickTableView);
5668 return d->loadedItems.isEmpty() ? -1 : d_func()->rightColumn();
5669}
5670
5672{
5673 Q_D(const QQuickTableView);
5674 return d->loadedItems.isEmpty() ? -1 : d_func()->topRow();
5675}
5676
5678{
5679 Q_D(const QQuickTableView);
5680 return d->loadedItems.isEmpty() ? -1 : d_func()->bottomRow();
5681}
5682
5684{
5685 return d_func()->currentRow;
5686}
5687
5689{
5690 return d_func()->currentColumn;
5691}
5692
5693void QQuickTableView::positionViewAtRow(int row, PositionMode mode, qreal offset, const QRectF &subRect)
5694{
5695 Q_D(QQuickTableView);
5696 if (row < 0 || row >= rows() || d->loadedRows.isEmpty())
5697 return;
5698
5699 // Note: PositionMode::Contain is from here on translated to (Qt::AlignTop | Qt::AlignBottom).
5700 // This is an internal (unsupported) combination which means "align bottom if the whole cell
5701 // fits inside the viewport, otherwise align top".
5702
5703 if (mode & (AlignTop | AlignBottom | AlignVCenter)) {
5705 d->positionViewAtRow(row, Qt::Alignment(int(mode)), offset, subRect);
5706 } else if (mode == Contain) {
5707 if (row < topRow()) {
5708 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
5709 } else if (row > bottomRow()) {
5710 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5711 } else if (row == topRow()) {
5712 if (!subRect.isValid()) {
5713 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
5714 } else {
5715 const qreal subRectTop = d->loadedTableOuterRect.top() + subRect.top();
5716 const qreal subRectBottom = d->loadedTableOuterRect.top() + subRect.bottom();
5717 if (subRectTop < d->viewportRect.y())
5718 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
5719 else if (subRectBottom > d->viewportRect.bottom())
5720 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5721 }
5722 } else if (row == bottomRow()) {
5723 if (!subRect.isValid()) {
5724 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5725 } else {
5726 // Note: entering here means that topRow() != bottomRow(). So at least two rows are
5727 // visible in the viewport, which means that the top side of the subRect is visible.
5728 const qreal subRectBottom = d->loadedTableInnerRect.bottom() + subRect.bottom();
5729 if (subRectBottom > d->viewportRect.bottom())
5730 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5731 }
5732 }
5733 } else if (mode == Visible) {
5734 if (row < topRow()) {
5735 d->positionViewAtRow(row, Qt::AlignTop, -offset, subRect);
5736 } else if (row > bottomRow()) {
5737 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5738 } else if (subRect.isValid()) {
5739 if (row == topRow()) {
5740 const qreal subRectTop = d->loadedTableOuterRect.top() + subRect.top();
5741 const qreal subRectBottom = d->loadedTableOuterRect.top() + subRect.bottom();
5742 if (subRectBottom < d->viewportRect.top())
5743 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
5744 else if (subRectTop > d->viewportRect.bottom())
5745 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5746 } else if (row == bottomRow()) {
5747 // Note: entering here means that topRow() != bottomRow(). So at least two rows are
5748 // visible in the viewport, which means that the top side of the subRect is visible.
5749 const qreal subRectTop = d->loadedTableInnerRect.bottom() + subRect.top();
5750 if (subRectTop > d->viewportRect.bottom())
5751 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5752 }
5753 }
5754 } else {
5755 qmlWarning(this) << "Unsupported mode:" << int(mode);
5756 }
5757}
5758
5759void QQuickTableView::positionViewAtColumn(int column, PositionMode mode, qreal offset, const QRectF &subRect)
5760{
5761 Q_D(QQuickTableView);
5762 if (column < 0 || column >= columns() || d->loadedColumns.isEmpty())
5763 return;
5764
5765 // Note: PositionMode::Contain is from here on translated to (Qt::AlignLeft | Qt::AlignRight).
5766 // This is an internal (unsupported) combination which means "align right if the whole cell
5767 // fits inside the viewport, otherwise align left".
5768
5769 if (mode & (AlignLeft | AlignRight | AlignHCenter)) {
5771 d->positionViewAtColumn(column, Qt::Alignment(int(mode)), offset, subRect);
5772 } else if (mode == Contain) {
5773 if (column < leftColumn()) {
5774 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
5775 } else if (column > rightColumn()) {
5776 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
5777 } else if (column == leftColumn()) {
5778 if (!subRect.isValid()) {
5779 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
5780 } else {
5781 const qreal subRectLeft = d->loadedTableOuterRect.left() + subRect.left();
5782 const qreal subRectRight = d->loadedTableOuterRect.left() + subRect.right();
5783 if (subRectLeft < d->viewportRect.left())
5784 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
5785 else if (subRectRight > d->viewportRect.right())
5786 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
5787 }
5788 } else if (column == rightColumn()) {
5789 if (!subRect.isValid()) {
5790 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
5791 } else {
5792 // Note: entering here means that leftColumn() != rightColumn(). So at least two columns
5793 // are visible in the viewport, which means that the left side of the subRect is visible.
5794 const qreal subRectRight = d->loadedTableInnerRect.right() + subRect.right();
5795 if (subRectRight > d->viewportRect.right())
5796 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
5797 }
5798 }
5799 } else if (mode == Visible) {
5800 if (column < leftColumn()) {
5801 d->positionViewAtColumn(column, Qt::AlignLeft, -offset, subRect);
5802 } else if (column > rightColumn()) {
5803 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
5804 } else if (subRect.isValid()) {
5805 if (column == leftColumn()) {
5806 const qreal subRectLeft = d->loadedTableOuterRect.left() + subRect.left();
5807 const qreal subRectRight = d->loadedTableOuterRect.left() + subRect.right();
5808 if (subRectRight < d->viewportRect.left())
5809 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
5810 else if (subRectLeft > d->viewportRect.right())
5811 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
5812 } else if (column == rightColumn()) {
5813 // Note: entering here means that leftColumn() != rightColumn(). So at least two columns
5814 // are visible in the viewport, which means that the left side of the subRect is visible.
5815 const qreal subRectLeft = d->loadedTableInnerRect.right() + subRect.left();
5816 if (subRectLeft > d->viewportRect.right())
5817 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
5818 }
5819 }
5820 } else {
5821 qmlWarning(this) << "Unsupported mode:" << int(mode);
5822 }
5823}
5824
5825void QQuickTableView::positionViewAtCell(const QPoint &cell, PositionMode mode, const QPointF &offset, const QRectF &subRect)
5826{
5827 PositionMode horizontalMode = mode & ~(AlignTop | AlignBottom | AlignVCenter);
5828 PositionMode verticalMode = mode & ~(AlignLeft | AlignRight | AlignHCenter);
5829 if (!horizontalMode && !verticalMode) {
5830 qmlWarning(this) << "Unsupported mode:" << int(mode);
5831 return;
5832 }
5833
5834 if (horizontalMode)
5835 positionViewAtColumn(cell.x(), horizontalMode, offset.x(), subRect);
5836 if (verticalMode)
5837 positionViewAtRow(cell.y(), verticalMode, offset.y(), subRect);
5838}
5839
5840void QQuickTableView::positionViewAtIndex(const QModelIndex &index, PositionMode mode, const QPointF &offset, const QRectF &subRect)
5841{
5842 PositionMode horizontalMode = mode & ~(AlignTop | AlignBottom | AlignVCenter);
5843 PositionMode verticalMode = mode & ~(AlignLeft | AlignRight | AlignHCenter);
5844 if (!horizontalMode && !verticalMode) {
5845 qmlWarning(this) << "Unsupported mode:" << int(mode);
5846 return;
5847 }
5848
5849 if (horizontalMode)
5850 positionViewAtColumn(columnAtIndex(index), horizontalMode, offset.x(), subRect);
5851 if (verticalMode)
5852 positionViewAtRow(rowAtIndex(index), verticalMode, offset.y(), subRect);
5853}
5854
5855#if QT_DEPRECATED_SINCE(6, 5)
5856void QQuickTableView::positionViewAtCell(int column, int row, PositionMode mode, const QPointF &offset, const QRectF &subRect)
5857{
5858 PositionMode horizontalMode = mode & ~(AlignTop | AlignBottom | AlignVCenter);
5859 PositionMode verticalMode = mode & ~(AlignLeft | AlignRight | AlignHCenter);
5860 if (!horizontalMode && !verticalMode) {
5861 qmlWarning(this) << "Unsupported mode:" << int(mode);
5862 return;
5863 }
5864
5865 if (horizontalMode)
5866 positionViewAtColumn(column, horizontalMode, offset.x(), subRect);
5867 if (verticalMode)
5868 positionViewAtRow(row, verticalMode, offset.y(), subRect);
5869}
5870#endif
5871
5873{
5874 Q_D(const QQuickTableView);
5875 const int modelIndex = d->modelIndexAtCell(cell);
5876 if (!d->loadedItems.contains(modelIndex))
5877 return nullptr;
5878 return d->loadedItems.value(modelIndex)->item;
5879}
5880
5881#if QT_DEPRECATED_SINCE(6, 5)
5883{
5884 return itemAtCell(QPoint(column, row));
5885}
5886#endif
5887
5888QQuickItem *QQuickTableView::itemAtIndex(const QModelIndex &index) const
5889{
5890 Q_D(const QQuickTableView);
5891 const int serializedIndex = d->modelIndexToCellIndex(index);
5892 if (!d->loadedItems.contains(serializedIndex))
5893 return nullptr;
5894 return d->loadedItems.value(serializedIndex)->item;
5895}
5896
5897#if QT_DEPRECATED_SINCE(6, 4)
5898QPoint QQuickTableView::cellAtPos(qreal x, qreal y, bool includeSpacing) const
5899{
5900 return cellAtPosition(mapToItem(contentItem(), {x, y}), includeSpacing);
5901}
5902
5903QPoint QQuickTableView::cellAtPos(const QPointF &position, bool includeSpacing) const
5904{
5905 return cellAtPosition(mapToItem(contentItem(), position), includeSpacing);
5906}
5907#endif
5908
5909QPoint QQuickTableView::cellAtPosition(qreal x, qreal y, bool includeSpacing) const
5910{
5911 return cellAtPosition(QPoint(x, y), includeSpacing);
5912}
5913
5914QPoint QQuickTableView::cellAtPosition(const QPointF &position, bool includeSpacing) const
5915{
5916 Q_D(const QQuickTableView);
5917
5918 if (!d->loadedTableOuterRect.contains(position))
5919 return QPoint(-1, -1);
5920
5921 const qreal hSpace = d->cellSpacing.width();
5922 const qreal vSpace = d->cellSpacing.height();
5923 qreal currentColumnEnd = d->loadedTableOuterRect.x();
5924 qreal currentRowEnd = d->loadedTableOuterRect.y();
5925
5926 int foundColumn = -1;
5927 int foundRow = -1;
5928
5929 for (const int column : d->loadedColumns) {
5930 currentColumnEnd += d->getEffectiveColumnWidth(column);
5931 if (position.x() < currentColumnEnd) {
5932 foundColumn = column;
5933 break;
5934 }
5935 currentColumnEnd += hSpace;
5936 if (!includeSpacing && position.x() < currentColumnEnd) {
5937 // Hit spacing
5938 return QPoint(-1, -1);
5939 } else if (includeSpacing && position.x() < currentColumnEnd - (hSpace / 2)) {
5940 foundColumn = column;
5941 break;
5942 }
5943 }
5944
5945 for (const int row : d->loadedRows) {
5946 currentRowEnd += d->getEffectiveRowHeight(row);
5947 if (position.y() < currentRowEnd) {
5948 foundRow = row;
5949 break;
5950 }
5951 currentRowEnd += vSpace;
5952 if (!includeSpacing && position.y() < currentRowEnd) {
5953 // Hit spacing
5954 return QPoint(-1, -1);
5955 }
5956 if (includeSpacing && position.y() < currentRowEnd - (vSpace / 2)) {
5957 foundRow = row;
5958 break;
5959 }
5960 }
5961
5962 return QPoint(foundColumn, foundRow);
5963}
5964
5965bool QQuickTableView::isColumnLoaded(int column) const
5966{
5967 Q_D(const QQuickTableView);
5968 if (!d->loadedColumns.contains(column))
5969 return false;
5970
5971 if (d->rebuildState != QQuickTableViewPrivate::RebuildState::Done) {
5972 // TableView is rebuilding, and none of the rows and columns
5973 // are completely loaded until we reach the layout phase.
5975 return false;
5976 }
5977
5978 return true;
5979}
5980
5981bool QQuickTableView::isRowLoaded(int row) const
5982{
5983 Q_D(const QQuickTableView);
5984 if (!d->loadedRows.contains(row))
5985 return false;
5986
5987 if (d->rebuildState != QQuickTableViewPrivate::RebuildState::Done) {
5988 // TableView is rebuilding, and none of the rows and columns
5989 // are completely loaded until we reach the layout phase.
5991 return false;
5992 }
5993
5994 return true;
5995}
5996
5997qreal QQuickTableView::columnWidth(int column) const
5998{
5999 Q_D(const QQuickTableView);
6000 if (!isColumnLoaded(column))
6001 return -1;
6002
6003 return d->getEffectiveColumnWidth(column);
6004}
6005
6006qreal QQuickTableView::rowHeight(int row) const
6007{
6008 Q_D(const QQuickTableView);
6009 if (!isRowLoaded(row))
6010 return -1;
6011
6012 return d->getEffectiveRowHeight(row);
6013}
6014
6015qreal QQuickTableView::implicitColumnWidth(int column) const
6016{
6017 Q_D(const QQuickTableView);
6018 if (!isColumnLoaded(column))
6019 return -1;
6020
6021 return d->sizeHintForColumn(column);
6022}
6023
6024qreal QQuickTableView::implicitRowHeight(int row) const
6025{
6026 Q_D(const QQuickTableView);
6027 if (!isRowLoaded(row))
6028 return -1;
6029
6030 return d->sizeHintForRow(row);
6031}
6032
6033void QQuickTableView::setColumnWidth(int column, qreal size)
6034{
6035 Q_D(QQuickTableView);
6036 if (column < 0) {
6037 qmlWarning(this) << "column must be greather than, or equal to, zero";
6038 return;
6039 }
6040
6041 if (d->syncHorizontally) {
6042 d->syncView->setColumnWidth(column, size);
6043 return;
6044 }
6045
6046 if (qFuzzyCompare(explicitColumnWidth(column), size))
6047 return;
6048
6049 if (size < 0)
6050 d->explicitColumnWidths.remove(column);
6051 else
6052 d->explicitColumnWidths.insert(column, size);
6053
6054 if (d->loadedItems.isEmpty())
6055 return;
6056
6057 const bool allColumnsLoaded = d->atTableEnd(Qt::LeftEdge) && d->atTableEnd(Qt::RightEdge);
6058 if (column >= leftColumn() || column <= rightColumn() || allColumnsLoaded)
6059 d->forceLayout(false);
6060}
6061
6062void QQuickTableView::clearColumnWidths()
6063{
6064 Q_D(QQuickTableView);
6065
6066 if (d->syncHorizontally) {
6067 d->syncView->clearColumnWidths();
6068 return;
6069 }
6070
6071 if (d->explicitColumnWidths.isEmpty())
6072 return;
6073
6074 d->explicitColumnWidths.clear();
6075 d->forceLayout(false);
6076}
6077
6078qreal QQuickTableView::explicitColumnWidth(int column) const
6079{
6080 Q_D(const QQuickTableView);
6081
6082 if (d->syncHorizontally)
6083 return d->syncView->explicitColumnWidth(column);
6084
6085 const auto it = d->explicitColumnWidths.constFind(column);
6086 if (it != d->explicitColumnWidths.constEnd())
6087 return *it;
6088 return -1;
6089}
6090
6091void QQuickTableView::setRowHeight(int row, qreal size)
6092{
6093 Q_D(QQuickTableView);
6094 if (row < 0) {
6095 qmlWarning(this) << "row must be greather than, or equal to, zero";
6096 return;
6097 }
6098
6099 if (d->syncVertically) {
6100 d->syncView->setRowHeight(row, size);
6101 return;
6102 }
6103
6104 if (qFuzzyCompare(explicitRowHeight(row), size))
6105 return;
6106
6107 if (size < 0)
6108 d->explicitRowHeights.remove(row);
6109 else
6110 d->explicitRowHeights.insert(row, size);
6111
6112 if (d->loadedItems.isEmpty())
6113 return;
6114
6115 const bool allRowsLoaded = d->atTableEnd(Qt::TopEdge) && d->atTableEnd(Qt::BottomEdge);
6116 if (row >= topRow() || row <= bottomRow() || allRowsLoaded)
6117 d->forceLayout(false);
6118}
6119
6120void QQuickTableView::clearRowHeights()
6121{
6122 Q_D(QQuickTableView);
6123
6124 if (d->syncVertically) {
6125 d->syncView->clearRowHeights();
6126 return;
6127 }
6128
6129 if (d->explicitRowHeights.isEmpty())
6130 return;
6131
6132 d->explicitRowHeights.clear();
6133 d->forceLayout(false);
6134}
6135
6136qreal QQuickTableView::explicitRowHeight(int row) const
6137{
6138 Q_D(const QQuickTableView);
6139
6140 if (d->syncVertically)
6141 return d->syncView->explicitRowHeight(row);
6142
6143 const auto it = d->explicitRowHeights.constFind(row);
6144 if (it != d->explicitRowHeights.constEnd())
6145 return *it;
6146 return -1;
6147}
6148
6149QModelIndex QQuickTableView::modelIndex(const QPoint &cell) const
6150{
6151 Q_D(const QQuickTableView);
6152 if (cell.x() < 0 || cell.x() >= columns() || cell.y() < 0 || cell.y() >= rows())
6153 return {};
6154
6155 auto const qaim = d->model->abstractItemModel();
6156 if (!qaim)
6157 return {};
6158
6159 return qaim->index(cell.y(), cell.x());
6160}
6161
6162QPoint QQuickTableView::cellAtIndex(const QModelIndex &index) const
6163{
6164 if (!index.isValid() || index.parent().isValid())
6165 return {-1, -1};
6166 return {index.column(), index.row()};
6167}
6168
6169#if QT_DEPRECATED_SINCE(6, 4)
6170QModelIndex QQuickTableView::modelIndex(int row, int column) const
6171{
6172 static bool compat6_4 = qEnvironmentVariable("QT_QUICK_TABLEVIEW_COMPAT_VERSION") == QStringLiteral("6.4");
6173 if (compat6_4) {
6174 // In Qt 6.4.0 and 6.4.1, a source incompatible change led to row and column
6175 // being documented to be specified in the opposite order.
6176 // QT_QUICK_TABLEVIEW_COMPAT_VERSION can therefore be set to force tableview
6177 // to continue accepting calls to modelIndex(column, row).
6178 return modelIndex({row, column});
6179 } else {
6180 qmlWarning(this) << "modelIndex(row, column) is deprecated. "
6181 "Use index(row, column) instead. For more information, see "
6182 "https://doc.qt.io/qt-6/qml-qtquick-tableview-obsolete.html";
6183 return modelIndex({column, row});
6184 }
6185}
6186#endif
6187
6188QModelIndex QQuickTableView::index(int row, int column) const
6189{
6190 return modelIndex({column, row});
6191}
6192
6193int QQuickTableView::rowAtIndex(const QModelIndex &index) const
6194{
6195 return cellAtIndex(index).y();
6196}
6197
6198int QQuickTableView::columnAtIndex(const QModelIndex &index) const
6199{
6200 return cellAtIndex(index).x();
6201}
6202
6204{
6205 d_func()->forceLayout(true);
6206}
6207
6208void QQuickTableView::edit(const QModelIndex &index)
6209{
6210 Q_D(QQuickTableView);
6211
6212 if (!d->canEdit(index, true))
6213 return;
6214
6215 if (d->editIndex == index)
6216 return;
6217
6218 if (!d->tableModel)
6219 return;
6220
6221 if (!d->editModel) {
6222 d->editModel = new QQmlTableInstanceModel(qmlContext(this));
6223 d->editModel->useImportVersion(d->resolveImportVersion());
6225 [this, d] (int serializedModelIndex, QObject *object) {
6226 // initItemCallback will call setRequiredProperty for each required property in the
6227 // delegate, both for this class, but also also for any subclasses. setRequiredProperty
6228 // is currently dependent of the QQmlTableInstanceModel that was used to create the object
6229 // in order to initialize required properties, so we need to set the editItem variable
6230 // early on, so that we can use it in setRequiredProperty.
6231 d->editIndex = modelIndex(d->cellAtModelIndex(serializedModelIndex));
6232 d->editItem = qmlobject_cast<QQuickItem*>(object);
6233 if (!d->editItem)
6234 return;
6235 // Initialize required properties
6236 d->initItemCallback(serializedModelIndex, object);
6237 const auto cellItem = itemAtCell(cellAtIndex(d->editIndex));
6238 Q_ASSERT(cellItem);
6239 d->editItem->setParentItem(cellItem);
6240 // Move the cell item to the top of the other items, to ensure
6241 // that e.g a focus frame ends up on top of all the cells
6242 cellItem->setZ(2);
6243 });
6244 }
6245
6246 if (d->selectionModel)
6247 d->selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
6248
6249 if (d->editIndex.isValid())
6250 closeEditor();
6251
6252 const auto cellItem = itemAtCell(cellAtIndex(index));
6253 Q_ASSERT(cellItem);
6254 const auto attached = d->getAttachedObject(cellItem);
6255 Q_ASSERT(attached);
6256
6257 d->editModel->setModel(d->tableModel->model());
6258 d->editModel->setDelegate(attached->editDelegate());
6259
6260 const int cellIndex = d->modelIndexToCellIndex(index);
6261 QObject* object = d->editModel->object(cellIndex, QQmlIncubator::Synchronous);
6262 if (!object) {
6263 d->editIndex = QModelIndex();
6264 d->editItem = nullptr;
6265 qmlWarning(this) << "cannot edit: TableView.editDelegate could not be instantiated!";
6266 return;
6267 }
6268
6269 // Note: at this point, editIndex and editItem has been set from initItem!
6270
6271 if (!d->editItem) {
6272 qmlWarning(this) << "cannot edit: TableView.editDelegate is not an Item!";
6273 d->editItem = nullptr;
6274 d->editIndex = QModelIndex();
6275 d->editModel->release(object, QQmlInstanceModel::NotReusable);
6276 return;
6277 }
6278
6279 // Reference the cell item once more, so that it doesn't
6280 // get reused or deleted if it leaves the viewport.
6281 d->model->object(cellIndex, QQmlIncubator::Synchronous);
6282
6283 // Inform the delegate, and the edit delegate, that they're being edited
6284 d->setRequiredProperty(kRequiredProperty_editing, QVariant::fromValue(true), cellIndex, cellItem, false);
6285
6286 // Transfer focus to the edit item
6287 d->editItem->forceActiveFocus(Qt::MouseFocusReason);
6288
6289 // Install an event filter on the focus object to handle Enter and Tab.
6290 // Note that the focusObject doesn't need to be the editItem itself, in
6291 // case the editItem is a FocusScope.
6292 if (QObject *focusObject = d->editItem->window()->focusObject()) {
6293 QQuickItem *focusItem = qobject_cast<QQuickItem *>(focusObject);
6294 if (focusItem == d->editItem || d->editItem->isAncestorOf(focusItem))
6295 focusItem->installEventFilter(this);
6296 }
6297}
6298
6299void QQuickTableView::closeEditor()
6300{
6301 Q_D(QQuickTableView);
6302
6303 if (!d->editItem)
6304 return;
6305
6306 QQuickItem *cellItem = d->editItem->parentItem();
6307 d->editModel->release(d->editItem, QQmlInstanceModel::NotReusable);
6308 d->editItem = nullptr;
6309
6310 cellItem->setZ(1);
6311 const int cellIndex = d->modelIndexToCellIndex(d->editIndex);
6312 d->setRequiredProperty(kRequiredProperty_editing, QVariant::fromValue(false), cellIndex, cellItem, false);
6313 // Remove the extra reference we sat on the cell item from edit()
6314 d->model->release(cellItem, QQmlInstanceModel::NotReusable);
6315
6316 if (d->editIndex.isValid()) {
6317 // Note: we can have an invalid editIndex, even when we
6318 // have an editItem, if the model has changed (e.g been reset)!
6319 d->editIndex = QModelIndex();
6320 }
6321}
6322
6324{
6325 return new QQuickTableViewAttached(obj);
6326}
6327
6328void QQuickTableView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
6329{
6330 Q_D(QQuickTableView);
6331 QQuickFlickable::geometryChange(newGeometry, oldGeometry);
6332
6333 if (d->tableModel) {
6334 // When the view changes size, we force the pool to
6335 // shrink by releasing all pooled items.
6336 d->tableModel->drainReusableItemsPool(0);
6337 }
6338
6339 d->forceLayout(false);
6340}
6341
6342void QQuickTableView::viewportMoved(Qt::Orientations orientation)
6343{
6344 Q_D(QQuickTableView);
6345
6346 // If the new viewport position was set from the setLocalViewportXY()
6347 // functions, we just update the position silently and return. Otherwise, if
6348 // the viewport was flicked by the user, or some other control, we
6349 // recursively sync all the views in the hierarchy to the same position.
6350 QQuickFlickable::viewportMoved(orientation);
6351 if (d->inSetLocalViewportPos)
6352 return;
6353
6354 // Move all views in the syncView hierarchy to the same contentX/Y.
6355 // We need to start from this view (and not the root syncView) to
6356 // ensure that we respect all the individual syncDirection flags
6357 // between the individual views in the hierarchy.
6358 d->syncViewportPosRecursive();
6359
6360 auto rootView = d->rootSyncView();
6361 auto rootView_d = rootView->d_func();
6362
6363 rootView_d->scheduleRebuildIfFastFlick();
6364
6365 if (!rootView_d->polishScheduled) {
6366 if (rootView_d->scheduledRebuildOptions) {
6367 // When we need to rebuild, collecting several viewport
6368 // moves and do a single polish gives a quicker UI.
6369 rootView->polish();
6370 } else {
6371 // Updating the table right away when flicking
6372 // slowly gives a smoother experience.
6373 const bool updated = rootView->d_func()->updateTableRecursive();
6374 if (!updated) {
6375 // One, or more, of the views are already in an
6376 // update, so we need to wait a cycle.
6377 rootView->polish();
6378 }
6379 }
6380 }
6381}
6382
6384{
6385 Q_D(QQuickTableView);
6386
6387 if (!d->keyNavigationEnabled) {
6389 return;
6390 }
6391
6392 if (d->tableSize.isEmpty())
6393 return;
6394
6395 if (d->editIndex.isValid()) {
6396 // While editing, we limit the keys that we
6397 // handle to not interfere with editing.
6398 return;
6399 }
6400
6401 if (d->setCurrentIndexFromKeyEvent(e))
6402 return;
6403
6404 if (d->editFromKeyEvent(e))
6405 return;
6406
6408}
6409
6411{
6412 Q_D(QQuickTableView);
6413
6414 if (event->type() == QEvent::KeyPress) {
6415 Q_ASSERT(d->editItem);
6416 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
6417 switch (keyEvent->key()) {
6418 case Qt::Key_Enter:
6419 case Qt::Key_Return:
6420 if (auto attached = d->getAttachedObject(d->editItem))
6421 emit attached->commit();
6422 closeEditor();
6423 return true;
6424 case Qt::Key_Tab:
6425 case Qt::Key_Backtab:
6426 if (activeFocusOnTab()) {
6427 if (auto attached = d->getAttachedObject(d->editItem))
6428 emit attached->commit();
6429 closeEditor();
6430 if (d->setCurrentIndexFromKeyEvent(keyEvent)) {
6431 const QModelIndex currentIndex = d->selectionModel->currentIndex();
6432 if (d->canEdit(currentIndex, false))
6433 edit(currentIndex);
6434 }
6435 return true;
6436 }
6437 break;
6438 case Qt::Key_Escape:
6439 closeEditor();
6440 return true;
6441 }
6442 }
6443
6445}
6446
6448{
6449 return d_func()->alternatingRows;
6450}
6451
6452void QQuickTableView::setAlternatingRows(bool alternatingRows)
6453{
6454 Q_D(QQuickTableView);
6455 if (d->alternatingRows == alternatingRows)
6456 return;
6457
6458 d->alternatingRows = alternatingRows;
6459 emit alternatingRowsChanged();
6460}
6461
6463{
6464 return d_func()->selectionBehavior;
6465}
6466
6468{
6469 Q_D(QQuickTableView);
6470 if (d->selectionBehavior == selectionBehavior)
6471 return;
6472
6473 d->selectionBehavior = selectionBehavior;
6474 emit selectionBehaviorChanged();
6475}
6476
6478{
6479 return d_func()->selectionMode;
6480}
6481
6483{
6484 Q_D(QQuickTableView);
6485 if (d->selectionMode == selectionMode)
6486 return;
6487
6488 d->selectionMode = selectionMode;
6489 emit selectionModeChanged();
6490}
6491
6493{
6494 return d_func()->resizableColumns;
6495}
6496
6498{
6499 Q_D(QQuickTableView);
6500 if (d->resizableColumns == enabled)
6501 return;
6502
6503 d->resizableColumns = enabled;
6504 d->resizeHandler->setEnabled(d->resizableRows || d->resizableColumns);
6505 d->hoverHandler->setEnabled(d->resizableRows || d->resizableColumns);
6506
6507 emit resizableColumnsChanged();
6508}
6509
6511{
6512 return d_func()->resizableRows;
6513}
6514
6516{
6517 Q_D(QQuickTableView);
6518 if (d->resizableRows == enabled)
6519 return;
6520
6521 d->resizableRows = enabled;
6522 d->resizeHandler->setEnabled(d->resizableRows || d->resizableColumns);
6523 d->hoverHandler->setEnabled(d->resizableRows || d->resizableColumns);
6524
6525 emit resizableRowsChanged();
6526}
6527
6528// ----------------------------------------------
6529
6531 : QQuickHoverHandler(view->contentItem())
6532{
6533 setMargin(5);
6534
6536 if (!isHoveringGrid())
6537 return;
6538 m_row = -1;
6539 m_column = -1;
6540#if QT_CONFIG(cursor)
6541 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
6542 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
6543 tableViewPrivate->updateCursor();
6544#endif
6545 });
6546}
6547
6549{
6551
6552 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
6553#if QT_CONFIG(cursor)
6554 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
6555#endif
6556
6557 const QPoint cell = tableView->cellAtPosition(point.position(), true);
6558 const auto item = tableView->itemAtCell(cell);
6559 if (!item) {
6560 m_row = -1;
6561 m_column = -1;
6562#if QT_CONFIG(cursor)
6563 tableViewPrivate->updateCursor();
6564#endif
6565 return;
6566 }
6567
6568 const QPointF itemPos = item->mapFromItem(tableView->contentItem(), point.position());
6569 const bool hoveringRow = (itemPos.y() < margin() || itemPos.y() > item->height() - margin());
6570 const bool hoveringColumn = (itemPos.x() < margin() || itemPos.x() > item->width() - margin());
6571 m_row = hoveringRow ? itemPos.y() < margin() ? cell.y() - 1 : cell.y() : -1;
6572 m_column = hoveringColumn ? itemPos.x() < margin() ? cell.x() - 1 : cell.x() : -1;
6573#if QT_CONFIG(cursor)
6574 tableViewPrivate->updateCursor();
6575#endif
6576}
6577
6578// ----------------------------------------------
6579
6581 : QQuickSinglePointHandler(view->contentItem())
6582{
6583 setMargin(5);
6584 // Set a grab permission that stops the flickable, as well as
6585 // any drag handler inside the delegate, from stealing the drag.
6587 setObjectName("tableViewResizeHandler");
6588}
6589
6592 , QPointerEvent *ev
6593 , QEventPoint &point)
6594{
6595 QQuickSinglePointHandler::onGrabChanged(grabber, transition, ev, point);
6596
6597 switch (transition) {
6600 break;
6606 if (m_state == DraggingStarted || m_state == Dragging) {
6608 updateDrag(ev, point);
6609 }
6610 break;
6611 }
6612}
6613
6615{
6617 return false;
6618
6619 // When the user is flicking, we disable resizing, so that
6620 // he doesn't start to resize by accident.
6621 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
6622 return !tableView->isMoving();
6623}
6624
6626{
6627 // Resolve which state we're in first...
6629 // ...and act on it next
6631}
6632
6634{
6635 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
6636 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
6637
6640
6641 if (point.state() == QEventPoint::Pressed) {
6642 m_row = tableViewPrivate->resizableRows ? tableViewPrivate->hoverHandler->m_row : -1;
6643 m_column = tableViewPrivate->resizableColumns ? tableViewPrivate->hoverHandler->m_column : -1;
6644 if (m_row != -1 || m_column != -1)
6645 m_state = Tracking;
6646 } else if (point.state() == QEventPoint::Released) {
6649 else
6651 } else if (point.state() == QEventPoint::Updated) {
6652 switch (m_state) {
6653 case Listening:
6654 break;
6655 case Tracking: {
6656 const qreal distX = m_column != -1 ? point.position().x() - point.pressPosition().x() : 0;
6657 const qreal distY = m_row != -1 ? point.position().y() - point.pressPosition().y() : 0;
6658 const qreal dragDist = qSqrt(distX * distX + distY * distY);
6659 if (dragDist > qApp->styleHints()->startDragDistance())
6661 break;}
6662 case DraggingStarted:
6663 m_state = Dragging;
6664 break;
6665 case Dragging:
6666 break;
6667 case DraggingFinished:
6668 // Handled at the top of the function
6669 Q_UNREACHABLE();
6670 break;
6671 }
6672 }
6673}
6674
6676{
6677 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
6678#if QT_CONFIG(cursor)
6679 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
6680#endif
6681
6682 switch (m_state) {
6683 case Listening:
6684 break;
6685 case Tracking:
6686 setPassiveGrab(event, point, true);
6687 // Disable flicking while dragging. TableView uses filtering instead of
6688 // pointer handlers to do flicking, so setting an exclusive grab (together
6689 // with grab permissions) doens't work ATM.
6690 tableView->setFiltersChildMouseEvents(false);
6691 break;
6692 case DraggingStarted:
6695 m_columnStartWidth = tableView->columnWidth(m_column);
6697 m_rowStartHeight = tableView->rowHeight(m_row);
6698#if QT_CONFIG(cursor)
6699 tableViewPrivate->updateCursor();
6700#endif
6701 // fallthrough
6702 case Dragging: {
6703 const qreal distX = point.position().x() - m_columnStartX;
6704 const qreal distY = point.position().y() - m_rowStartY;
6705 if (m_column != -1)
6706 tableView->setColumnWidth(m_column, qMax(0.001, m_columnStartWidth + distX));
6707 if (m_row != -1)
6708 tableView->setRowHeight(m_row, qMax(0.001, m_rowStartHeight + distY));
6709 break; }
6710 case DraggingFinished: {
6711 tableView->setFiltersChildMouseEvents(true);
6712#if QT_CONFIG(cursor)
6713 tableViewPrivate->updateCursor();
6714#endif
6715 break; }
6716 }
6717}
6718
6719// ----------------------------------------------
6720
6722 : QQuickTapHandler(view->contentItem())
6723{
6724 setObjectName("tableViewTapHandler");
6725}
6726
6728{
6729 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
6730 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
6731 return tableViewPrivate->pointerNavigationEnabled && QQuickTapHandler::wantsEventPoint(event, point);
6732}
6733
6735
6736#include "moc_qquicktableview_p.cpp"
6737#include "moc_qquicktableview_p_p.cpp"
void rowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow, QPrivateSignal)
void columnsRemoved(const QModelIndex &parent, int first, int last, QPrivateSignal)
This signal is emitted after columns have been removed from the model.
LayoutChangeHint
This enum describes the way the model changes layout.
virtual Q_INVOKABLE Qt::ItemFlags flags(const QModelIndex &index) const
Returns the item flags for the given index.
void modelReset(QPrivateSignal)
void layoutChanged(const QList< QPersistentModelIndex > &parents=QList< QPersistentModelIndex >(), QAbstractItemModel::LayoutChangeHint hint=QAbstractItemModel::NoLayoutChangeHint)
void rowsInserted(const QModelIndex &parent, int first, int last, QPrivateSignal)
This signal is emitted after rows have been inserted into the model.
void columnsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationColumn, QPrivateSignal)
void columnsInserted(const QModelIndex &parent, int first, int last, QPrivateSignal)
This signal is emitted after columns have been inserted into the model.
virtual Q_INVOKABLE QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const =0
Returns the index of the item in the model specified by the given row, column and parent index.
void rowsRemoved(const QModelIndex &parent, int first, int last, QPrivateSignal)
This signal is emitted after rows have been removed from the model.
static bool sendEvent(QObject *receiver, QEvent *event)
Sends event event directly to receiver receiver, using the notify() function.
static QDir current()
Returns the application's current directory.
Definition qdir.h:216
QString absoluteFilePath(const QString &fileName) const
Returns the absolute path name of a file in the directory.
Definition qdir.cpp:809
The QEventPoint class provides information about a point in a QPointerEvent.
Definition qeventpoint.h:20
\inmodule QtCore
Definition qcoreevent.h:45
@ KeyPress
Definition qcoreevent.h:64
QGraphicsWidget * window() const
QPointF mapFromItem(const QGraphicsItem *item, const QPointF &point) const
Maps the point point, which is in item's coordinate system, to this item's coordinate system,...
void setParentItem(QGraphicsItem *parent)
Sets this item's parent item to newParent.
QGraphicsItem * parentItem() const
Returns a pointer to this item's parent item.
void setVisible(bool visible)
If visible is true, the item is made visible.
bool isAncestorOf(const QGraphicsItem *child) const
Returns true if this item is an ancestor of child (i.e., if this item is child's parent,...
static QObject * focusObject()
Returns the QObject in currently active window that will be final receiver of events tied to focus,...
key_iterator keyEnd() const noexcept
Definition qhash.h:1211
qsizetype size() const noexcept
Returns the number of items in the hash.
Definition qhash.h:925
QList< T > values() const
Returns a list containing all the values in the hash, in an arbitrary order.
Definition qhash.h:1088
T take(const Key &key)
Removes the item with the key from the hash and returns the value associated with it.
Definition qhash.h:975
bool contains(const Key &key) const noexcept
Returns true if the hash contains an item with the key; otherwise returns false.
Definition qhash.h:991
key_iterator keyBegin() const noexcept
Definition qhash.h:1210
T value(const Key &key) const noexcept
Definition qhash.h:1044
void clear() noexcept(std::is_nothrow_destructible< Node >::value)
Removes all items from the hash and frees up all memory used by it.
Definition qhash.h:949
bool isEmpty() const noexcept
Returns true if the hash contains no items; otherwise returns false.
Definition qhash.h:926
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1283
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
This signal is emitted whenever the selection changes.
void currentChanged(const QModelIndex &current, const QModelIndex &previous)
This signal is emitted whenever the current item changes.
Q_INVOKABLE bool isSelected(const QModelIndex &index) const
Returns true if the given model item index is selected.
virtual void setCurrentIndex(const QModelIndex &index, QItemSelectionModel::SelectionFlags command)
Sets the model item index to be the current item, and emits currentChanged().
virtual void select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command)
Selects the model item index using the specified command, and emits selectionChanged().
QAbstractItemModel * model
\inmodule QtCore
Q_CORE_EXPORT QModelIndexList indexes() const
Returns a list of model indexes that correspond to the selected items.
The QJSValue class acts as a container for Qt/JavaScript data types.
Definition qjsvalue.h:31
bool isCallable() const
Returns true if this QJSValue is a function, otherwise returns false.
Definition qjsvalue.cpp:445
QJSValue call(const QJSValueList &args=QJSValueList()) const
Calls this QJSValue as a function, passing args as arguments to the function, and using the globalObj...
Definition qjsvalue.cpp:681
double toNumber() const
Returns the number value of this QJSValue, as defined in \l{ECMA-262} section 9.3,...
Definition qjsvalue.cpp:505
bool isUndefined() const
Returns true if this QJSValue is of the primitive type Undefined or if the managed value has been cle...
Definition qjsvalue.cpp:349
bool strictlyEquals(const QJSValue &other) const
Returns true if this QJSValue is equal to other using strict comparison (no conversion),...
The QKeyEvent class describes a key event.
Definition qevent.h:423
int key() const
Returns the code of the key that was pressed or released.
Definition qevent.h:433
Definition qlist.h:74
const_reference at(qsizetype i) const noexcept
Definition qlist.h:429
\inmodule QtCore
Definition qmargins.h:23
constexpr int bottom() const noexcept
Returns the bottom margin.
Definition qmargins.h:119
constexpr int left() const noexcept
Returns the left margin.
Definition qmargins.h:110
constexpr int right() const noexcept
Returns the right margin.
Definition qmargins.h:116
constexpr int top() const noexcept
Returns the top margin.
Definition qmargins.h:113
const Container & values() const &
bool contains(const value_type &v) const
void remove(const value_type &v)
std::pair< iterator, bool > insert(value_type &&v)
\inmodule QtCore
constexpr bool isValid() const noexcept
Returns {true} if this model index is valid; otherwise returns {false}.
QDynamicMetaObjectData * metaObject
Definition qobject.h:77
QObject * parent
Definition qobject.h:61
static QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer< Func1 >::Object *sender, Func1 signal, const typename QtPrivate::FunctionPointer< Func2 >::Object *receiverPrivate, Func2 slot, Qt::ConnectionType type=Qt::AutoConnection)
Definition qobject_p.h:298
static bool disconnect(const typename QtPrivate::FunctionPointer< Func1 >::Object *sender, Func1 signal, const typename QtPrivate::FunctionPointer< Func2 >::Object *receiverPrivate, Func2 slot)
Definition qobject_p.h:327
\inmodule QtCore
Definition qobject.h:90
void installEventFilter(QObject *filterObj)
Installs an event filter filterObj on this object.
Definition qobject.cpp:2269
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
virtual bool eventFilter(QObject *watched, QEvent *event)
Filters events if this object has been installed as an event filter for the watched object.
Definition qobject.cpp:1518
QVariant property(const char *name) const
Returns the value of the object's name property.
Definition qobject.cpp:4187
Q_WEAK_OVERLOAD void setObjectName(const QString &name)
Sets the object's name to name.
Definition qobject.h:114
bool isValid() const
Returns {true} if this persistent model index is valid; otherwise returns {false}.
\inmodule QtCore\reentrant
Definition qpoint.h:214
constexpr qreal & ry() noexcept
Returns a reference to the y coordinate of this point.
Definition qpoint.h:358
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:333
constexpr qreal y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:338
constexpr qreal & rx() noexcept
Returns a reference to the x coordinate of this point.
Definition qpoint.h:353
\inmodule QtCore\reentrant
Definition qpoint.h:23
constexpr int & ry() noexcept
Returns a reference to the y coordinate of this point.
Definition qpoint.h:157
constexpr int & rx() noexcept
Returns a reference to the x coordinate of this point.
Definition qpoint.h:152
constexpr int x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:127
constexpr int y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:132
A base class for pointer events.
Definition qevent.h:73
GrabTransition
This enum represents a transition of exclusive or passive grab from one object (possibly nullptr) to ...
The QQmlChangeSet class stores an ordered list of notifications about changes to a linear data set.
The QQmlComponent class encapsulates a QML component definition.
static QQmlData * get(QObjectPrivate *priv, bool create)
Definition qqmldata_p.h:199
IncubationMode
Specifies the mode the incubator operates in.
void createdItem(int index, QObject *object)
void modelUpdated(const QQmlChangeSet &changeSet, bool reset)
virtual const QAbstractItemModel * abstractItemModel() const
virtual QQmlIncubator::Status incubationStatus(int index)=0
void initItem(int index, QObject *object)
virtual ReleaseFlags release(QObject *object, ReusableFlag reusableFlag=NotReusable)=0
static QQmlType qmlType(const QString &qualifiedName, QTypeRevision version)
Returns the type (if any) of URI-qualified named qualifiedName and version specified by version_major...
void drainReusableItemsPool(int maxPoolTime) override
bool setRequiredProperty(int index, const QString &name, const QVariant &value) final
void setModel(const QVariant &model)
QQmlComponent * delegate() const
void useImportVersion(QTypeRevision version)
void setDelegate(QQmlComponent *)
ReleaseFlags release(QObject *object, ReusableFlag reusable=NotReusable) override
bool isRunning() const
\qmlproperty bool QtQuick::Animation::running This property holds whether the animation is currently ...
void complete()
\qmlmethod QtQuick::Animation::complete()
void restart()
\qmlmethod QtQuick::Animation::restart()
void stop()
\qmlmethod QtQuick::Animation::stop()
Qt::Orientations activeDirections() const
void clearFocusInScope(QQuickItem *scope, QQuickItem *item, Qt::FocusReason reason, FocusOptions={ })
virtual void fixup(AxisData &data, qreal minExtent, qreal maxExtent)
void atYEndChanged()
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override
virtual qreal minYExtent() const
void setContentWidth(qreal)
virtual void viewportMoved(Qt::Orientations orient)
bool isMoving() const
\qmlproperty bool QtQuick::Flickable::moving \qmlproperty bool QtQuick::Flickable::movingHorizontally...
virtual qreal maxXExtent() const
virtual qreal maxYExtent() const
QQuickItem * contentItem
virtual qreal minXExtent() const
void setContentHeight(qreal)
Qt::KeyboardModifiers modifiers
void handleEventPoint(QPointerEvent *ev, QEventPoint &point) override
QQuickAnchors * _anchors
QQuickAnchors * anchors() const
\qmlpropertygroup QtQuick::Item::anchors \qmlproperty AnchorLine QtQuick::Item::anchors....
QPointer< QQuickItem > subFocusItem
QQuickWindow * window
QQuickDeliveryAgentPrivate * deliveryAgentPrivate()
QQmlListProperty< QQuickItem > children()
static QQuickItemPrivate * get(QQuickItem *item)
QPointer< QQuickItem > item
void setVisible(bool visible)
The QQuickItem class provides the most basic of all visual items in \l {Qt Quick}.
Definition qquickitem.h:64
qreal implicitWidth
Definition qquickitem.h:113
virtual void keyPressEvent(QKeyEvent *event)
This event handler can be reimplemented in a subclass to receive key press events for an item.
Q_INVOKABLE QPointF mapToItem(const QQuickItem *item, const QPointF &point) const
Maps the given point in this item's coordinate system to the equivalent point within item's coordinat...
qreal x
\qmlproperty real QtQuick::Item::x \qmlproperty real QtQuick::Item::y \qmlproperty real QtQuick::Item...
Definition qquickitem.h:73
bool activeFocusOnTab() const
\qmlproperty bool QtQuick::Item::activeFocusOnTab
virtual Q_INVOKABLE bool contains(const QPointF &point) const
\qmlmethod bool QtQuick::Item::contains(point point)
qreal width
This property holds the width of this item.
Definition qquickitem.h:76
QQuickItem * parentItem() const
QQuickItem * parent
\qmlproperty Item QtQuick::Item::parent This property holds the visual parent of the item.
Definition qquickitem.h:68
qreal implicitHeight
Definition qquickitem.h:114
QPointF position() const
qreal height
This property holds the height of this item.
Definition qquickitem.h:77
void setZ(qreal)
void setX(qreal)
bool enabled
\qmlproperty bool QtQuick::Item::enabled
Definition qquickitem.h:80
QQuickItem * parentItem() const
\qmlproperty Item QtQuick::PointerHandler::parent
void setMargin(qreal pointDistanceThreshold)
virtual bool wantsEventPoint(const QPointerEvent *event, const QEventPoint &point)
Returns true if the given point (as part of event) could be relevant at all to this handler,...
void setPassiveGrab(QPointerEvent *event, const QEventPoint &point, bool grab=true)
Acquire or give up a passive grab of the given point, according to the grab state.
void setGrabPermissions(GrabPermissions grabPermissions)
void setEnabled(bool enabled)
bool setExclusiveGrab(QPointerEvent *ev, const QEventPoint &point, bool grab=true)
Acquire or give up the exclusive grab of the given point, according to the grab state,...
void setTo(const QVariant &)
void setEasing(const QEasingCurve &)
virtual void setDuration(int)
void setProperty(const QString &)
void onGrabChanged(QQuickPointerHandler *grabber, QPointingDevice::GrabTransition transition, QPointerEvent *event, QEventPoint &point) override
Notification that the grab has changed in some way which is relevant to this handler.
void handleEventPoint(QPointerEvent *event, QEventPoint &point) override
QQuickTableViewHoverHandler(QQuickTableView *view)
bool containsIndex(Qt::Edge edge, int index)
QQmlIncubator::IncubationMode incubationMode() const
void begin(const QPoint &cell, const QPointF &pos, QQmlIncubator::IncubationMode incubationMode)
bool canUnloadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const
QQuickTableView::SelectionBehavior selectionBehavior
bool editFromKeyEvent(QKeyEvent *e)
bool canLoadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const
void calculateTopLeft(QPoint &topLeft, QPointF &topLeftPos)
QQuickItem * selectionPointerHandlerTarget() const override
QPoint cellAtModelIndex(int modelIndex) const
void releaseLoadedItems(QQmlTableInstanceModel::ReusableFlag reusableFlag)
void layoutVerticalEdge(Qt::Edge tableEdge)
virtual void itemCreatedCallback(int modelIndex, QObject *object)
void setCurrentOnDelegateItem(const QModelIndex &index, bool isCurrent)
bool setCurrentIndexFromKeyEvent(QKeyEvent *e)
qreal getEffectiveRowHeight(int row) const
void rowsMovedCallback(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)
virtual QVariant modelImpl() const
virtual void itemReusedCallback(int modelIndex, QObject *object)
FxTableItem * createFxTableItem(const QPoint &cell, QQmlIncubator::IncubationMode incubationMode)
qreal cellHeight(const QPoint &cell) const
qreal cellWidth(const QPoint &cell) const
QQuickTableView::SelectionMode selectionMode
qreal getAlignmentContentX(int column, Qt::Alignment alignment, const qreal offset, const QRectF &subRect)
void rowsRemovedCallback(const QModelIndex &parent, int begin, int end)
qreal getColumnWidth(int column) const
QSizeF scrollTowardsSelectionPoint(const QPointF &pos, const QSizeF &step) override
qreal getColumnLayoutWidth(int column)
void setSelectionEndPos(const QPointF &pos) override
Qt::Edge nextEdgeToUnload(const QRectF rect)
void forceLayout(bool immediate)
void releaseItem(FxTableItem *fxTableItem, QQmlTableInstanceModel::ReusableFlag reusableFlag)
void unloadItem(const QPoint &cell)
void selectionChangedInSelectionModel(const QItemSelection &selected, const QItemSelection &deselected)
RebuildOptions scheduledRebuildOptions
QQmlNullableValue< qreal > explicitContentWidth
bool atTableEnd(Qt::Edge edge) const
void setSelectionStartPos(const QPointF &pos) override
QRectF selectionRectangle() const override
QList< QPointer< QQuickTableView > > syncChildren
void scheduleRebuildTable(QQuickTableViewPrivate::RebuildOptions options)
QQuickTableView * rootSyncView() const
void currentChangedInSelectionModel(const QModelIndex &current, const QModelIndex &previous)
QQmlGuard< QQmlComponent > assignedDelegate
qreal getAlignmentContentY(int row, Qt::Alignment alignment, const qreal offset, const QRectF &subRect)
FxTableItem * loadedTableItem(const QPoint &cell) const
QQmlInstanceModel * model
bool selectedInSelectionModel(const QPoint &cell) const
bool scrollToColumn(int column, Qt::Alignment alignment, qreal offset, const QRectF subRect=QRectF())
void positionViewAtRow(int row, Qt::Alignment alignment, qreal offset, const QRectF subRect=QRectF())
int nextVisibleEdgeIndex(Qt::Edge edge, int startIndex) const
QQuickTableViewHoverHandler * hoverHandler
virtual void modelUpdated(const QQmlChangeSet &changeSet, bool reset)
EdgeRange cachedNextVisibleEdgeIndex[4]
static QQuickTableViewPrivate * get(QQuickTableView *q)
void fixup(AxisData &data, qreal minExtent, qreal maxExtent) override
QTypeRevision resolveImportVersion()
virtual void syncWithPendingChanges()
qreal getRowHeight(int row) const
void normalizeSelection() override
void layoutHorizontalEdge(Qt::Edge tableEdge)
bool isColumnHidden(int column) const
bool scrollToRow(int row, Qt::Alignment alignment, qreal offset, const QRectF subRect=QRectF())
QString tableLayoutToString() const
void loadEdge(Qt::Edge edge, QQmlIncubator::IncubationMode incubationMode)
virtual void initItemCallback(int modelIndex, QObject *item)
bool startSelection(const QPointF &pos) override
bool cellIsValid(const QPoint &cell) const
qreal sizeHintForRow(int row) const
virtual void updateSelection(const QRect &oldSelection, const QRect &newSelection)
Qt::Alignment positionViewAtColumnAlignment
bool currentInSelectionModel(const QPoint &cell) const
qreal getEffectiveColumnX(int column) const
QQuickTableView::EditTriggers editTriggers
QPointer< QQuickTableView > assignedSyncView
Qt::Edge nextEdgeToLoad(const QRectF rect)
Qt::Orientations assignedSyncDirection
Qt::Alignment positionViewAtRowAlignment
QQmlTableInstanceModel * editModel
int edgeToArrayIndex(Qt::Edge edge) const
FxTableItem * loadFxTableItem(const QPoint &cell, QQmlIncubator::IncubationMode incubationMode)
QQmlNullableValue< qreal > explicitContentHeight
void setLocalViewportX(qreal contentX)
TableEdgeLoadRequest loadRequest
RebuildOptions checkForVisibilityChanges()
void setCurrentIndexFromTap(const QPointF &pos)
QQmlTableInstanceModel::ReusableFlag reusableFlag
void layoutChangedCallback(const QList< QPersistentModelIndex > &parents, QAbstractItemModel::LayoutChangeHint hint)
bool isRowHidden(int row) const
void loadAndUnloadVisibleEdges(QQmlIncubator::IncubationMode incubationMode=QQmlIncubator::AsynchronousIfNested)
QQuickPropertyAnimation positionXAnimation
QMinimalFlatSet< int > loadedColumns
QHash< int, FxTableItem * > loadedItems
QAbstractItemModel * qaim(QVariant modelAsVariant) const
void setLocalViewportY(qreal contentY)
void rowsInsertedCallback(const QModelIndex &parent, int begin, int end)
QMinimalFlatSet< int > loadedRows
void setCurrentIndex(const QPoint &cell)
QQuickTableViewAttached * getAttachedObject(const QObject *object) const
virtual void setModelImpl(const QVariant &newModel)
qreal getEffectiveColumnWidth(int column) const
void unloadEdge(Qt::Edge edge)
void columnsRemovedCallback(const QModelIndex &parent, int begin, int end)
qreal sizeHintForColumn(int column) const
int modelIndexAtCell(const QPoint &cell) const
QPointer< QItemSelectionModel > selectionModel
qreal getRowLayoutHeight(int row)
qreal getEffectiveRowY(int row) const
void shiftLoadedTableRect(const QPointF newPosition)
int modelIndexToCellIndex(const QModelIndex &modelIndex) const
int nextVisibleEdgeIndexAroundLoadedTable(Qt::Edge edge) const
QPoint clampedCellAtPos(const QPointF &pos) const
QPointer< QQmlTableInstanceModel > tableModel
QPointer< QQuickTableView > syncView
void setRequiredProperty(const char *property, const QVariant &value, int serializedModelIndex, QObject *object, bool init)
QQuickPropertyAnimation positionYAnimation
bool canEdit(const QModelIndex tappedIndex, bool warn)
void setSelectedOnDelegateItem(const QModelIndex &modelIndex, bool select)
void columnsInsertedCallback(const QModelIndex &parent, int begin, int end)
virtual void itemPooledCallback(int modelIndex, QObject *object)
void handleTap(const QQuickHandlerPoint &point)
void positionViewAtColumn(int column, Qt::Alignment alignment, qreal offset, const QRectF subRect=QRectF())
QPersistentModelIndex editIndex
QQuickTableViewResizeHandler * resizeHandler
void columnsMovedCallback(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int column)
void onGrabChanged(QQuickPointerHandler *grabber, QPointingDevice::GrabTransition transition, QPointerEvent *ev, QEventPoint &point) override
Notification that the grab has changed in some way which is relevant to this handler.
void updateState(QEventPoint &point)
void updateDrag(QPointerEvent *event, QEventPoint &point)
QQuickTableViewResizeHandler(QQuickTableView *view)
void handleEventPoint(QPointerEvent *event, QEventPoint &point) override
bool wantsEventPoint(const QPointerEvent *event, const QEventPoint &point) override
Returns true if the given point (as part of event) could be relevant at all to this handler,...
bool wantsEventPoint(const QPointerEvent *event, const QEventPoint &point) override
Returns true if the given point (as part of event) could be relevant at all to this handler,...
QQuickTableViewTapHandler(QQuickTableView *view)
void setReuseItems(bool reuseItems)
FINALQt::Orientations syncDirection
Q_INVOKABLE void positionViewAtIndex(const QModelIndex &index, PositionMode mode, const QPointF &offset=QPointF(), const QRectF &subRect=QRectF())
bool eventFilter(QObject *obj, QEvent *event) override
Filters events if this object has been installed as an event filter for the watched object.
FINALSelectionBehavior selectionBehavior
void setSyncView(QQuickTableView *view)
FINALbool resizableColumns
Q_INVOKABLE void positionViewAtRow(int row, PositionMode mode, qreal offset=0, const QRectF &subRect=QRectF())
FINALSelectionMode selectionMode
void setSelectionBehavior(SelectionBehavior selectionBehavior)
void reuseItemsChanged()
void setResizableColumns(bool enabled)
void delegateChanged()
QQuickTableView(QQuickItem *parent=nullptr)
void setSelectionModel(QItemSelectionModel *selectionModel)
void setAnimate(bool animate)
void setDelegate(QQmlComponent *)
FINALbool keyNavigationEnabled
void setAlternatingRows(bool alternatingRows)
~QQuickTableView() override
void setSelectionMode(SelectionMode selectionMode)
qreal minYExtent() const override
void setEditTriggers(EditTriggers editTriggers)
void setResizableRows(bool enabled)
FINALQItemSelectionModel * selectionModel
void componentFinalized() override
The customization point provided by this interface.
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override
void setModel(const QVariant &newModel)
void setKeyNavigationEnabled(bool enabled)
Q_INVOKABLE void positionViewAtColumn(int column, PositionMode mode, qreal offset=0, const QRectF &subRect=QRectF())
QQuickTableView * syncView
void columnWidthProviderChanged()
FINALbool pointerNavigationEnabled
FINALEditTriggers editTriggers
void viewportMoved(Qt::Orientations orientation) override
qreal maxYExtent() const override
qreal minXExtent() const override
void setRowHeightProvider(const QJSValue &provider)
void rowHeightProviderChanged()
Q_INVOKABLE void forceLayout()
Q_INVOKABLE QQuickItem * itemAtCell(const QPoint &cell) const
void setColumnWidthProvider(const QJSValue &provider)
Q_INVOKABLE void positionViewAtCell(const QPoint &cell, PositionMode mode, const QPointF &offset=QPointF(), const QRectF &subRect=QRectF())
void setRowSpacing(qreal spacing)
void rowSpacingChanged()
QQmlComponent * delegate
void setSyncDirection(Qt::Orientations direction)
qreal maxXExtent() const override
void setPointerNavigationEnabled(bool enabled)
void setColumnSpacing(qreal spacing)
void setContentHeight(qreal height)
FINALbool alternatingRows
void keyPressEvent(QKeyEvent *e) override
This event handler can be reimplemented in a subclass to receive key press events for an item.
void setContentWidth(qreal width)
void columnSpacingChanged()
QJSValue columnWidthProvider
static QQuickTableViewAttached * qmlAttachedProperties(QObject *)
void doubleTapped(QEventPoint eventPoint, Qt::MouseButton)
void singleTapped(QEventPoint eventPoint, Qt::MouseButton)
bool wantsEventPoint(const QPointerEvent *event, const QEventPoint &point) override
Returns true if the given point (as part of event) could be relevant at all to this handler,...
\qmltype Window \instantiates QQuickWindow \inqmlmodule QtQuick
QObject * focusObject() const override
\inmodule QtCore\reentrant
Definition qrect.h:483
constexpr bool isEmpty() const noexcept
Returns true if the rectangle is empty, otherwise returns false.
Definition qrect.h:647
constexpr qreal bottom() const noexcept
Returns the y-coordinate of the rectangle's bottom edge.
Definition qrect.h:499
constexpr qreal y() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:658
constexpr qreal height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:718
constexpr qreal width() const noexcept
Returns the width of the rectangle.
Definition qrect.h:715
constexpr qreal x() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:655
constexpr void moveRight(qreal pos) noexcept
Moves the rectangle horizontally, leaving the rectangle's right edge at the given finite x coordinate...
Definition qrect.h:694
constexpr void moveTopLeft(const QPointF &p) noexcept
Moves the rectangle, leaving the top-left corner at the given position.
Definition qrect.h:700
constexpr qreal left() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:496
bool intersects(const QRectF &r) const noexcept
Returns true if this rectangle intersects with the given rectangle (i.e.
Definition qrect.cpp:2263
constexpr void setWidth(qreal w) noexcept
Sets the width of the rectangle to the given finite width.
Definition qrect.h:804
constexpr void moveBottom(qreal pos) noexcept
Moves the rectangle vertically, leaving the rectangle's bottom edge at the given finite y coordinate.
Definition qrect.h:697
constexpr QPointF topLeft() const noexcept
Returns the position of the rectangle's top-left corner.
Definition qrect.h:510
constexpr QPointF center() const noexcept
Returns the center point of the rectangle.
Definition qrect.h:685
constexpr void moveLeft(qreal pos) noexcept
Moves the rectangle horizontally, leaving the rectangle's left edge at the given finite x coordinate.
Definition qrect.h:688
constexpr QPointF bottomRight() const noexcept
Returns the position of the rectangle's bottom-right corner.
Definition qrect.h:511
constexpr qreal top() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:497
constexpr void setHeight(qreal h) noexcept
Sets the height of the rectangle to the given finite height.
Definition qrect.h:807
constexpr bool isValid() const noexcept
Returns true if the rectangle is valid, otherwise returns false.
Definition qrect.h:652
constexpr void moveTop(qreal pos) noexcept
Moves the rectangle vertically, leaving the rectangle's top line at the given finite y coordinate.
Definition qrect.h:691
constexpr qreal right() const noexcept
Returns the x-coordinate of the rectangle's right edge.
Definition qrect.h:498
\inmodule QtCore\reentrant
Definition qrect.h:30
constexpr int height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:238
QRect normalized() const noexcept
Returns a normalized rectangle; i.e., a rectangle that has a non-negative width and height.
Definition qrect.cpp:273
constexpr int x() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:184
constexpr int width() const noexcept
Returns the width of the rectangle.
Definition qrect.h:235
constexpr int y() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:187
\inmodule QtCore
Definition qsize.h:207
constexpr qreal & rwidth() noexcept
Returns a reference to the width.
Definition qsize.h:345
constexpr void setHeight(qreal h) noexcept
Sets the height to the given finite height.
Definition qsize.h:330
constexpr qreal & rheight() noexcept
Returns a reference to the height.
Definition qsize.h:348
constexpr void setWidth(qreal w) noexcept
Sets the width to the given finite width.
Definition qsize.h:327
constexpr qreal width() const noexcept
Returns the width.
Definition qsize.h:321
constexpr qreal height() const noexcept
Returns the height.
Definition qsize.h:324
\inmodule QtCore
Definition qsize.h:25
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:132
constexpr int width() const noexcept
Returns the width.
Definition qsize.h:129
constexpr bool isEmpty() const noexcept
Returns true if either of the width and height is less than or equal to 0; otherwise returns false.
Definition qsize.h:123
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5857
QString arg(qlonglong a, int fieldwidth=0, int base=10, QChar fillChar=u' ') const
Definition qstring.cpp:8606
\inmodule QtCore
static constexpr QTypeRevision zero()
Produces a QTypeRevision with major and minor version {0}.
\inmodule QtCore
Definition qvariant.h:64
T value() const &
Definition qvariant.h:511
qreal toReal(bool *ok=nullptr) const
Returns the variant as a qreal if the variant has userType() \l QMetaType::Double,...
int userType() const
Definition qvariant.h:336
float toFloat(bool *ok=nullptr) const
Returns the variant as a float if the variant has userType() \l QMetaType::Double,...
static auto fromValue(T &&value) noexcept(std::is_nothrow_copy_constructible_v< T > &&Private::CanUseInternalSpace< T >) -> std::enable_if_t< std::conjunction_v< std::is_copy_constructible< T >, std::is_destructible< T > >, QVariant >
Definition qvariant.h:531
QStringList toStringList() const
Returns the variant as a QStringList if the variant has userType() \l QMetaType::QStringList,...
qreal spacing
double e
QSet< QString >::iterator it
object setObjectName("A new object name")
rect
[4]
uint alignment
direction
Combined button and popup list for selecting options.
@ AlignRight
Definition qnamespace.h:145
@ AlignBottom
Definition qnamespace.h:153
@ AlignVCenter
Definition qnamespace.h:154
@ AlignTop
Definition qnamespace.h:152
@ AlignHCenter
Definition qnamespace.h:147
@ AlignLeft
Definition qnamespace.h:143
@ Horizontal
Definition qnamespace.h:98
@ Vertical
Definition qnamespace.h:99
CursorShape
@ SizeFDiagCursor
@ SplitVCursor
@ SplitHCursor
@ Key_Escape
Definition qnamespace.h:658
@ Key_Tab
Definition qnamespace.h:659
@ Key_Shift
Definition qnamespace.h:678
@ Key_Return
Definition qnamespace.h:662
@ Key_Right
Definition qnamespace.h:674
@ Key_Enter
Definition qnamespace.h:663
@ Key_PageUp
Definition qnamespace.h:676
@ Key_Backtab
Definition qnamespace.h:660
@ Key_Left
Definition qnamespace.h:672
@ Key_Control
Definition qnamespace.h:679
@ Key_Alt
Definition qnamespace.h:681
@ Key_Up
Definition qnamespace.h:673
@ Key_Down
Definition qnamespace.h:675
@ Key_F2
Definition qnamespace.h:686
@ Key_Meta
Definition qnamespace.h:680
@ Key_PageDown
Definition qnamespace.h:677
@ Key_Home
Definition qnamespace.h:670
@ Key_End
Definition qnamespace.h:671
@ ShiftModifier
@ NoModifier
@ RightEdge
@ TopEdge
@ BottomEdge
@ LeftEdge
@ ItemIsEditable
@ MouseFocusReason
@ OtherFocusReason
#define Q_UNLIKELY(x)
#define qApp
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:287
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:303
bool qIsNaN(qfloat16 f) noexcept
Definition qfloat16.h:238
qfloat16 qSqrt(qfloat16 f)
Definition qfloat16.h:243
#define forever
Definition qforeach.h:78
QList< QJSValue > QJSValueList
Definition qjsvalue.h:22
#define qWarning
Definition qlogging.h:162
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
int qFloor(T v)
Definition qmath.h:42
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
static Q_DECL_CONST_FUNCTION bool qt_is_nan(double d)
Definition qnumeric_p.h:106
static Q_DECL_CONST_FUNCTION bool qt_is_finite(double d)
Definition qnumeric_p.h:111
GLint GLint GLint GLint GLint x
[0]
GLenum mode
GLfloat GLfloat GLfloat w
[0]
GLint GLsizei GLsizei height
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint index
[2]
GLuint GLuint end
GLdouble GLdouble GLdouble GLdouble top
GLdouble GLdouble right
GLenum GLenum GLsizei const GLuint GLboolean enabled
GLint GLsizei width
GLint left
GLint GLint bottom
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint GLintptr offset
GLint GLint GLint GLint GLint GLint GLint GLbitfield mask
GLint y
GLfloat GLfloat GLfloat GLfloat h
GLenum GLenum GLsizei void GLsizei void * column
struct _cl_event * event
GLhandleARB obj
[2]
GLboolean reset
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLsizei const GLchar *const * path
GLenum GLenum GLsizei void * row
GLdouble s
[6]
Definition qopenglext.h:235
static int log2(uint i)
QQmlContext * qmlContext(const QObject *obj)
Definition qqml.cpp:71
QQuickItem * qmlobject_cast< QQuickItem * >(QObject *object)
Q_QML_EXPORT QQmlInfo qmlWarning(const QObject *me)
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
static QQuickAttachedPropertyPropagator * attachedObject(const QMetaObject *type, QObject *object, bool create=false)
QQuickItem * qobject_cast< QQuickItem * >(QObject *o)
Definition qquickitem.h:483
static const Qt::Edge allTableEdges[]
static const char * kRequiredProperties
#define Q_TABLEVIEW_ASSERT(cond, output)
#define Q_TABLEVIEW_UNREACHABLE(output)
\qmltype TableView \inqmlmodule QtQuick
static const char * kRequiredProperty_selected
static const char * kRequiredProperty_editing
static const char * kRequiredProperty_current
static const qreal kDefaultColumnWidth
static const int kEdgeIndexAtEnd
static QT_BEGIN_NAMESPACE const qreal kDefaultRowHeight
static const int kEdgeIndexNotSet
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define QStringLiteral(str)
static QT_BEGIN_NAMESPACE QVariant hint(QPlatformIntegration::StyleHint h)
QString qEnvironmentVariable(const char *varName, const QString &defaultValue)
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
#define emit
#define Q_UNUSED(x)
double qreal
Definition qtypes.h:92
static QVariant toVariant(const QV4::Value &value, QMetaType typeHint, bool createJSValueForObjectsAndSymbols, V4ObjectSet *visitedObjects)
const char property[13]
Definition qwizard.cpp:101
std::uniform_real_distribution dist(1, 2.5)
[2]
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
myObject disconnect()
[26]
QGraphicsItem * item
selection select(topLeft, bottomRight)
QQuickView * view
[0]
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent