Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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{
1465#define TV_REBUILDSTATE(STATE) \
1466 case QQuickTableViewPrivate::RebuildState::STATE: \
1467 dbg << QStringLiteral(#STATE); break;
1468
1469 switch (state) {
1470 TV_REBUILDSTATE(Begin);
1471 TV_REBUILDSTATE(LoadInitalTable);
1472 TV_REBUILDSTATE(VerifyTable);
1473 TV_REBUILDSTATE(LayoutTable);
1474 TV_REBUILDSTATE(CancelOvershoot);
1475 TV_REBUILDSTATE(UpdateContentSize);
1476 TV_REBUILDSTATE(PreloadColumns);
1477 TV_REBUILDSTATE(PreloadRows);
1478 TV_REBUILDSTATE(MovePreloadedItemsToPool);
1479 TV_REBUILDSTATE(Done);
1480 }
1481
1482 return dbg;
1483}
1484
1485QDebug operator<<(QDebug dbg, QQuickTableViewPrivate::RebuildOptions options)
1486{
1487#define TV_REBUILDOPTION(OPTION) \
1488 if (options & QQuickTableViewPrivate::RebuildOption::OPTION) \
1489 dbg << QStringLiteral(#OPTION)
1490
1492 dbg << QStringLiteral("None");
1493 } else {
1495 TV_REBUILDOPTION(LayoutOnly);
1496 TV_REBUILDOPTION(ViewportOnly);
1497 TV_REBUILDOPTION(CalculateNewTopLeftRow);
1498 TV_REBUILDOPTION(CalculateNewTopLeftColumn);
1499 TV_REBUILDOPTION(CalculateNewContentWidth);
1500 TV_REBUILDOPTION(CalculateNewContentHeight);
1501 TV_REBUILDOPTION(PositionViewAtRow);
1502 TV_REBUILDOPTION(PositionViewAtColumn);
1503 }
1504
1505 return dbg;
1506}
1507
1509 : startIndex(kEdgeIndexNotSet)
1510 , endIndex(kEdgeIndexNotSet)
1511 , size(0)
1512{}
1513
1515{
1516 if (startIndex == kEdgeIndexNotSet)
1517 return false;
1518
1519 if (endIndex == kEdgeIndexAtEnd) {
1520 switch (edge) {
1521 case Qt::LeftEdge:
1522 case Qt::TopEdge:
1523 return index <= startIndex;
1524 case Qt::RightEdge:
1525 case Qt::BottomEdge:
1526 return index >= startIndex;
1527 }
1528 }
1529
1530 const int s = std::min(startIndex, endIndex);
1531 const int e = std::max(startIndex, endIndex);
1532 return index >= s && index <= e;
1533}
1534
1539
1541{
1542 if (editItem) {
1543 QQuickItem *cellItem = editItem->parentItem();
1544 Q_ASSERT(cellItem);
1547 }
1548
1549 if (editModel)
1550 delete editModel;
1551
1552 for (auto *fxTableItem : loadedItems) {
1553 if (auto item = fxTableItem->item) {
1554 if (fxTableItem->ownItem)
1555 delete item;
1556 else if (tableModel)
1558 }
1559 delete fxTableItem;
1560 }
1561
1562 if (tableModel)
1563 delete tableModel;
1564}
1565
1567{
1568 if (loadedItems.isEmpty())
1569 return QLatin1String("table is empty!");
1570 return QString(QLatin1String("table cells: (%1,%2) -> (%3,%4), item count: %5, table rect: %6,%7 x %8,%9"))
1571 .arg(leftColumn()).arg(topRow())
1573 .arg(loadedItems.size())
1578}
1579
1581{
1582 auto listCopy = loadedItems.values();
1583 std::stable_sort(listCopy.begin(), listCopy.end(),
1584 [](const FxTableItem *lhs, const FxTableItem *rhs)
1585 { return lhs->index < rhs->index; });
1586
1587 qWarning() << QStringLiteral("******* TABLE DUMP *******");
1588 for (int i = 0; i < listCopy.size(); ++i)
1589 qWarning() << static_cast<FxTableItem *>(listCopy.at(i))->cell;
1591
1592 const QString filename = QStringLiteral("QQuickTableView_dumptable_capture.png");
1593 const QString path = QDir::current().absoluteFilePath(filename);
1594 if (q_func()->window() && q_func()->window()->grabWindow().save(path))
1595 qWarning() << "Window capture saved to:" << path;
1596}
1597
1599 const QVariant &value, int serializedModelIndex, QObject *object, bool init)
1600{
1601 Q_Q(QQuickTableView);
1602
1603 QQmlTableInstanceModel *tableInstanceModel = qobject_cast<QQmlTableInstanceModel *>(model);
1604 if (!tableInstanceModel) {
1605 // TableView only supports using required properties when backed by
1606 // a QQmlTableInstanceModel. This is almost always the case, except
1607 // if you assign it an ObjectModel or a DelegateModel (which are really
1608 // not supported by TableView, it expects a QAIM).
1609 return;
1610 }
1611
1612 // Attaching a property list to the delegate item is just a
1613 // work-around until QMetaProperty::isRequired() works (QTBUG-98846).
1614 const QString propertyName = QString::fromUtf8(property);
1615
1616 if (init) {
1617 bool wasRequired = false;
1618 if (object == editItem) {
1619 // Special case: the item that we should write to belongs to the edit
1620 // model rather than 'model' (which is used for normal delegate items).
1621 wasRequired = editModel->setRequiredProperty(serializedModelIndex, propertyName, value);
1622 } else {
1623 wasRequired = tableInstanceModel->setRequiredProperty(serializedModelIndex, propertyName, value);
1624 }
1625 if (wasRequired) {
1626 QStringList propertyList = object->property(kRequiredProperties).toStringList();
1627 object->setProperty(kRequiredProperties, propertyList << propertyName);
1628 }
1629 } else {
1630 {
1631 const QStringList propertyList = object->property(kRequiredProperties).toStringList();
1632 if (propertyList.contains(propertyName)) {
1633 const auto metaObject = object->metaObject();
1634 const int propertyIndex = metaObject->indexOfProperty(property);
1635 const auto metaProperty = metaObject->property(propertyIndex);
1636 metaProperty.write(object, value);
1637 }
1638 }
1639
1640 if (editItem) {
1641 // Whenever we're told to update a required property for a table item that has the
1642 // same model index as the edit item, we also mirror that update to the edit item.
1643 // As such, this function is never called for the edit item directly (except the
1644 // first time when it needs to be initialized).
1645 Q_TABLEVIEW_ASSERT(object != editItem, "");
1646 const QModelIndex modelIndex = q->modelIndex(cellAtModelIndex(serializedModelIndex));
1647 if (modelIndex == editIndex) {
1649 if (propertyList.contains(propertyName)) {
1650 const auto metaObject = editItem->metaObject();
1651 const int propertyIndex = metaObject->indexOfProperty(property);
1652 const auto metaProperty = metaObject->property(propertyIndex);
1653 metaProperty.write(editItem, value);
1654 }
1655 }
1656 }
1657
1658 }
1659}
1660
1662{
1663 return const_cast<QQuickTableView *>(q_func())->contentItem();
1664}
1665
1667{
1668 Q_Q(QQuickTableView);
1669 if (!selectionModel) {
1671 qmlWarning(q_func()) << "Cannot start selection: no SelectionModel assigned!";
1672 warnNoSelectionModel = false;
1673 return false;
1674 }
1675
1677 qmlWarning(q) << "Cannot start selection: TableView.selectionBehavior == TableView.SelectionDisabled";
1678 return false;
1679 }
1680
1681 // Only allow a selection if it doesn't conflict with resizing
1683 return false;
1684
1685 // For SingleSelection and ContiguousSelection, we should only allow one selection at a time
1689 else if (selectionModel)
1691
1692 // If pos is on top of an unselected cell, we start a session where the user selects which
1693 // cells to become selected. Otherwise, if pos is on top of an already selected cell and
1694 // ctrl is being held, we start a session where the user selects which selected cells to
1695 // become unselected.
1698 QPoint startCell = clampedCellAtPos(pos);
1699 const QModelIndex startIndex = q->index(startCell.y(), startCell.x());
1700 if (selectionModel->isSelected(startIndex))
1702 }
1703
1704 selectionStartCell = QPoint(-1, -1);
1705 selectionEndCell = QPoint(-1, -1);
1706 q->closeEditor();
1707 return true;
1708}
1709
1711{
1712 Q_Q(QQuickTableView);
1714 if (loadedItems.isEmpty())
1715 return;
1716 if (!selectionModel) {
1718 qmlWarning(q_func()) << "Cannot set selection: no SelectionModel assigned!";
1719 warnNoSelectionModel = false;
1720 return;
1721 }
1723 if (!qaim)
1724 return;
1725
1728 return;
1729 }
1730
1731 const QRect prevSelection = selection();
1732
1733 QPoint clampedCell;
1734 if (pos.x() == -1) {
1735 // Special case: use current cell as start cell
1736 clampedCell = q->cellAtIndex(selectionModel->currentIndex());
1737 } else {
1738 clampedCell = clampedCellAtPos(pos);
1739 if (cellIsValid(clampedCell))
1740 setCurrentIndex(clampedCell);
1741 }
1742
1743 if (!cellIsValid(clampedCell))
1744 return;
1745
1746 switch (selectionBehavior) {
1748 selectionStartCell = clampedCell;
1749 break;
1751 selectionStartCell = QPoint(0, clampedCell.y());
1752 break;
1754 selectionStartCell = QPoint(clampedCell.x(), 0);
1755 break;
1757 return;
1758 }
1759
1761 return;
1762
1763 // Update selection model
1764 QScopedValueRollback callbackGuard(inSelectionModelUpdate, true);
1765 updateSelection(prevSelection, selection());
1766}
1767
1769{
1771 if (loadedItems.isEmpty())
1772 return;
1773 if (!selectionModel) {
1775 qmlWarning(q_func()) << "Cannot set selection: no SelectionModel assigned!";
1776 warnNoSelectionModel = false;
1777 return;
1778 }
1780 if (!qaim)
1781 return;
1782
1783 const QRect prevSelection = selection();
1784
1785 QPoint clampedCell;
1787 clampedCell = selectionStartCell;
1788 } else {
1789 clampedCell = clampedCellAtPos(pos);
1790 if (!cellIsValid(clampedCell))
1791 return;
1792 }
1793
1794 setCurrentIndex(clampedCell);
1795
1796 switch (selectionBehavior) {
1798 selectionEndCell = clampedCell;
1799 break;
1801 selectionEndCell = QPoint(tableSize.width() - 1, clampedCell.y());
1802 break;
1804 selectionEndCell = QPoint(clampedCell.x(), tableSize.height() - 1);
1805 break;
1807 return;
1808 }
1809
1811 return;
1812
1813 // Update selection model
1814 QScopedValueRollback callbackGuard(inSelectionModelUpdate, true);
1815 updateSelection(prevSelection, selection());
1816}
1817
1819{
1820 Q_Q(const QQuickTableView);
1821
1822 // Note: pos should be relative to selectionPointerHandlerTarget()
1823 QPoint cell = q->cellAtPosition(pos, true);
1824 if (cellIsValid(cell))
1825 return cell;
1826
1827 // Clamp the cell to the loaded table and the viewport, whichever is the smallest
1828 QPointF clampedPos(
1831 QPointF clampedPosInView = q->mapFromItem(selectionPointerHandlerTarget(), clampedPos);
1832 clampedPosInView.rx() = qBound(0., clampedPosInView.x(), viewportRect.width());
1833 clampedPosInView.ry() = qBound(0., clampedPosInView.y(), viewportRect.height());
1834 clampedPos = q->mapToItem(selectionPointerHandlerTarget(), clampedPosInView);
1835
1836 return q->cellAtPosition(clampedPos, true);
1837}
1838
1839void QQuickTableViewPrivate::updateSelection(const QRect &oldSelection, const QRect &newSelection)
1840{
1842 const QRect oldRect = oldSelection.normalized();
1843 const QRect newRect = newSelection.normalized();
1844
1846 QItemSelection deselect;
1847
1848 // Select cells inside the new selection rect
1849 {
1850 const QModelIndex startIndex = qaim->index(newRect.y(), newRect.x());
1851 const QModelIndex endIndex = qaim->index(newRect.y() + newRect.height(), newRect.x() + newRect.width());
1852 select = QItemSelection(startIndex, endIndex);
1853 }
1854
1855 // Unselect cells in the new minus old rects
1856 if (oldRect.x() < newRect.x()) {
1857 const QModelIndex startIndex = qaim->index(oldRect.y(), oldRect.x());
1858 const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), newRect.x() - 1);
1859 deselect.merge(QItemSelection(startIndex, endIndex), QItemSelectionModel::Select);
1860 } else if (oldRect.x() + oldRect.width() > newRect.x() + newRect.width()) {
1861 const QModelIndex startIndex = qaim->index(oldRect.y(), newRect.x() + newRect.width() + 1);
1862 const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), oldRect.x() + oldRect.width());
1863 deselect.merge(QItemSelection(startIndex, endIndex), QItemSelectionModel::Select);
1864 }
1865
1866 if (oldRect.y() < newRect.y()) {
1867 const QModelIndex startIndex = qaim->index(oldRect.y(), oldRect.x());
1868 const QModelIndex endIndex = qaim->index(newRect.y() - 1, oldRect.x() + oldRect.width());
1869 deselect.merge(QItemSelection(startIndex, endIndex), QItemSelectionModel::Select);
1870 } else if (oldRect.y() + oldRect.height() > newRect.y() + newRect.height()) {
1871 const QModelIndex startIndex = qaim->index(newRect.y() + newRect.height() + 1, oldRect.x());
1872 const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), oldRect.x() + oldRect.width());
1873 deselect.merge(QItemSelection(startIndex, endIndex), QItemSelectionModel::Select);
1874 }
1875
1877 // Don't clear the selection that existed before the user started a new selection block
1882 QItemSelection oldSelection = existingSelection;
1886 } else {
1887 Q_UNREACHABLE();
1888 }
1889}
1890
1901
1903{
1904 if (!selectionModel)
1905 return;
1906 QScopedValueRollback callbackGuard(inSelectionModelUpdate, true);
1908}
1909
1911{
1912 // Normalize the selection if necessary, so that the start cell is to the left
1913 // and above the end cell. This is typically done after a selection drag has
1914 // finished so that the start and end positions up in sync with the handles.
1915 // This will not cause any changes to the selection itself.
1917 std::swap(selectionStartCell.rx(), selectionEndCell.rx());
1919 std::swap(selectionStartCell.ry(), selectionEndCell.ry());
1920}
1921
1923{
1924 Q_Q(const QQuickTableView);
1925
1926 QPoint topLeftCell = selectionStartCell;
1927 QPoint bottomRightCell = selectionEndCell;
1928 if (bottomRightCell.x() < topLeftCell.x())
1929 std::swap(topLeftCell.rx(), bottomRightCell.rx());
1930 if (selectionEndCell.y() < topLeftCell.y())
1931 std::swap(topLeftCell.ry(), bottomRightCell.ry());
1932
1933 const QPoint leftCell(topLeftCell.x(), topRow());
1934 const QPoint topCell(leftColumn(), topLeftCell.y());
1935 const QPoint rightCell(bottomRightCell.x(), topRow());
1936 const QPoint bottomCell(leftColumn(), bottomRightCell.y());
1937
1938 // If the corner cells of the selection are loaded, we can position the
1939 // selection rectangle at its exact location. Otherwise we extend it out
1940 // to the edges of the content item. This is not ideal, but the best we
1941 // can do while the location of the corner cells are unknown.
1942 // This will at least move the selection handles (and other overlay) out
1943 // of the viewport until the affected cells are eventually loaded.
1944 int left = 0;
1945 int top = 0;
1946 int right = 0;
1947 int bottom = 0;
1948
1949 if (loadedItems.contains(modelIndexAtCell(leftCell)))
1950 left = loadedTableItem(leftCell)->geometry().left();
1951 else if (leftCell.x() > rightColumn())
1952 left = q->contentWidth();
1953
1955 top = loadedTableItem(topCell)->geometry().top();
1956 else if (topCell.y() > bottomRow())
1957 top = q->contentHeight();
1958
1959 if (loadedItems.contains(modelIndexAtCell(rightCell)))
1960 right = loadedTableItem(rightCell)->geometry().right();
1961 else if (rightCell.x() > rightColumn())
1962 right = q->contentWidth();
1963
1964 if (loadedItems.contains(modelIndexAtCell(bottomCell)))
1965 bottom = loadedTableItem(bottomCell)->geometry().bottom();
1966 else if (bottomCell.y() > bottomRow())
1967 bottom = q->contentHeight();
1968
1969 return QRectF(left, top, right - left, bottom - top);
1970}
1971
1978
1980{
1981 Q_Q(QQuickTableView);
1982
1983 if (loadedItems.isEmpty())
1984 return QSizeF();
1985
1986 // Scroll the content item towards pos.
1987 // Return the distance in pixels from the edge of the viewport to pos.
1988 // The caller will typically use this information to throttle the scrolling speed.
1989 // If pos is already inside the viewport, or the viewport is scrolled all the way
1990 // to the end, we return 0.
1991 QSizeF dist(0, 0);
1992
1993 const bool outsideLeft = pos.x() < viewportRect.x();
1994 const bool outsideRight = pos.x() >= viewportRect.right() - 1;
1995 const bool outsideTop = pos.y() < viewportRect.y();
1996 const bool outsideBottom = pos.y() >= viewportRect.bottom() - 1;
1997
1998 if (outsideLeft) {
1999 const bool firstColumnLoaded = atTableEnd(Qt::LeftEdge);
2000 const qreal remainingDist = viewportRect.left() - loadedTableOuterRect.left();
2001 if (remainingDist > 0 || !firstColumnLoaded) {
2002 qreal stepX = step.width();
2003 if (firstColumnLoaded)
2004 stepX = qMin(stepX, remainingDist);
2005 q->setContentX(q->contentX() - stepX);
2006 dist.setWidth(pos.x() - viewportRect.left() - 1);
2007 }
2008 } else if (outsideRight) {
2009 const bool lastColumnLoaded = atTableEnd(Qt::RightEdge);
2010 const qreal remainingDist = loadedTableOuterRect.right() - viewportRect.right();
2011 if (remainingDist > 0 || !lastColumnLoaded) {
2012 qreal stepX = step.width();
2013 if (lastColumnLoaded)
2014 stepX = qMin(stepX, remainingDist);
2015 q->setContentX(q->contentX() + stepX);
2016 dist.setWidth(pos.x() - viewportRect.right() - 1);
2017 }
2018 }
2019
2020 if (outsideTop) {
2021 const bool firstRowLoaded = atTableEnd(Qt::TopEdge);
2022 const qreal remainingDist = viewportRect.top() - loadedTableOuterRect.top();
2023 if (remainingDist > 0 || !firstRowLoaded) {
2024 qreal stepY = step.height();
2025 if (firstRowLoaded)
2026 stepY = qMin(stepY, remainingDist);
2027 q->setContentY(q->contentY() - stepY);
2028 dist.setHeight(pos.y() - viewportRect.top() - 1);
2029 }
2030 } else if (outsideBottom) {
2031 const bool lastRowLoaded = atTableEnd(Qt::BottomEdge);
2032 const qreal remainingDist = loadedTableOuterRect.bottom() - viewportRect.bottom();
2033 if (remainingDist > 0 || !lastRowLoaded) {
2034 qreal stepY = step.height();
2035 if (lastRowLoaded)
2036 stepY = qMin(stepY, remainingDist);
2037 q->setContentY(q->contentY() + stepY);
2038 dist.setHeight(pos.y() - viewportRect.bottom() - 1);
2039 }
2040 }
2041
2042 return dist;
2043}
2044
2049
2051{
2052 QObject *attachedObject = qmlAttachedPropertiesObject<QQuickTableView>(object);
2053 return static_cast<QQuickTableViewAttached *>(attachedObject);
2054}
2055
2057{
2058 // QQmlTableInstanceModel expects index to be in column-major
2059 // order. This means that if the view is transposed (with a flipped
2060 // width and height), we need to calculate it in row-major instead.
2061 if (isTransposed) {
2062 int availableColumns = tableSize.width();
2063 return (cell.y() * availableColumns) + cell.x();
2064 } else {
2065 int availableRows = tableSize.height();
2066 return (cell.x() * availableRows) + cell.y();
2067 }
2068}
2069
2071{
2072 // QQmlTableInstanceModel expects index to be in column-major
2073 // order. This means that if the view is transposed (with a flipped
2074 // width and height), we need to calculate it in row-major instead.
2075 if (isTransposed) {
2076 int availableColumns = tableSize.width();
2077 int row = int(modelIndex / availableColumns);
2078 int column = modelIndex % availableColumns;
2079 return QPoint(column, row);
2080 } else {
2081 int availableRows = tableSize.height();
2082 int column = int(modelIndex / availableRows);
2083 int row = modelIndex % availableRows;
2084 return QPoint(column, row);
2085 }
2086}
2087
2089{
2090 // Convert QModelIndex to cell index. A cell index is just an
2091 // integer representation of a cell instead of using a QPoint.
2092 const QPoint cell = q_func()->cellAtIndex(modelIndex);
2093 if (!cellIsValid(cell))
2094 return -1;
2095 return modelIndexAtCell(cell);
2096}
2097
2099{
2100 return int(log2(float(edge)));
2101}
2102
2111
2113{
2114 // Find the next column (or row) around the loaded table that is
2115 // visible, and should be loaded next if the content item moves.
2116 int startIndex = -1;
2117 switch (edge) {
2118 case Qt::LeftEdge: startIndex = leftColumn() - 1; break;
2119 case Qt::RightEdge: startIndex = rightColumn() + 1; break;
2120 case Qt::TopEdge: startIndex = topRow() - 1; break;
2121 case Qt::BottomEdge: startIndex = bottomRow() + 1; break;
2122 }
2123
2124 return nextVisibleEdgeIndex(edge, startIndex);
2125}
2126
2128{
2129 // First check if we have already searched for the first visible index
2130 // after the given startIndex recently, and if so, return the cached result.
2131 // The cached result is valid if startIndex is inside the range between the
2132 // startIndex and the first visible index found after it.
2133 auto &cachedResult = cachedNextVisibleEdgeIndex[edgeToArrayIndex(edge)];
2134 if (cachedResult.containsIndex(edge, startIndex))
2135 return cachedResult.endIndex;
2136
2137 // Search for the first column (or row) in the direction of edge that is
2138 // visible, starting from the given column (startIndex).
2139 int foundIndex = kEdgeIndexNotSet;
2140 int testIndex = startIndex;
2141
2142 switch (edge) {
2143 case Qt::LeftEdge: {
2144 forever {
2145 if (testIndex < 0) {
2146 foundIndex = kEdgeIndexAtEnd;
2147 break;
2148 }
2149
2150 if (!isColumnHidden(testIndex)) {
2151 foundIndex = testIndex;
2152 break;
2153 }
2154
2155 --testIndex;
2156 }
2157 break; }
2158 case Qt::RightEdge: {
2159 forever {
2160 if (testIndex > tableSize.width() - 1) {
2161 foundIndex = kEdgeIndexAtEnd;
2162 break;
2163 }
2164
2165 if (!isColumnHidden(testIndex)) {
2166 foundIndex = testIndex;
2167 break;
2168 }
2169
2170 ++testIndex;
2171 }
2172 break; }
2173 case Qt::TopEdge: {
2174 forever {
2175 if (testIndex < 0) {
2176 foundIndex = kEdgeIndexAtEnd;
2177 break;
2178 }
2179
2180 if (!isRowHidden(testIndex)) {
2181 foundIndex = testIndex;
2182 break;
2183 }
2184
2185 --testIndex;
2186 }
2187 break; }
2188 case Qt::BottomEdge: {
2189 forever {
2190 if (testIndex > tableSize.height() - 1) {
2191 foundIndex = kEdgeIndexAtEnd;
2192 break;
2193 }
2194
2195 if (!isRowHidden(testIndex)) {
2196 foundIndex = testIndex;
2197 break;
2198 }
2199
2200 ++testIndex;
2201 }
2202 break; }
2203 }
2204
2205 cachedResult.startIndex = startIndex;
2206 cachedResult.endIndex = foundIndex;
2207 return foundIndex;
2208}
2209
2211{
2212 // Note that we actually never really know what the content size / size of the full table will
2213 // be. Even if e.g spacing changes, and we normally would assume that the size of the table
2214 // would increase accordingly, the model might also at some point have removed/hidden/resized
2215 // rows/columns outside the viewport. This would also affect the size, but since we don't load
2216 // rows or columns outside the viewport, this information is ignored. And even if we did, we
2217 // might also have been fast-flicked to a new location at some point, and started a new rebuild
2218 // there based on a new guesstimated top-left cell. So the calculated content size should always
2219 // be understood as a guesstimate, which sometimes can be really off (as a tradeoff for performance).
2220 // When this is not acceptable, the user can always set a custom content size explicitly.
2221 Q_Q(QQuickTableView);
2222
2223 if (syncHorizontally) {
2224 QBoolBlocker fixupGuard(inUpdateContentSize, true);
2225 q->QQuickFlickable::setContentWidth(syncView->contentWidth());
2226 return;
2227 }
2228
2230 // Don't calculate contentWidth when it
2231 // was set explicitly by the application.
2232 return;
2233 }
2234
2235 if (loadedItems.isEmpty()) {
2236 QBoolBlocker fixupGuard(inUpdateContentSize, true);
2237 if (model && model->count() > 0 && tableModel && tableModel->delegate())
2238 q->QQuickFlickable::setContentWidth(kDefaultColumnWidth);
2239 else
2240 q->QQuickFlickable::setContentWidth(0);
2241 return;
2242 }
2243
2245 const int columnsRemaining = nextColumn == kEdgeIndexAtEnd ? 0 : tableSize.width() - nextColumn;
2246 const qreal remainingColumnWidths = columnsRemaining * averageEdgeSize.width();
2247 const qreal remainingSpacing = columnsRemaining * cellSpacing.width();
2248 const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing;
2249 const qreal estimatedWidth = loadedTableOuterRect.right() + estimatedRemainingWidth;
2250
2251 QBoolBlocker fixupGuard(inUpdateContentSize, true);
2252 q->QQuickFlickable::setContentWidth(estimatedWidth);
2253}
2254
2256{
2257 Q_Q(QQuickTableView);
2258
2259 if (syncVertically) {
2260 QBoolBlocker fixupGuard(inUpdateContentSize, true);
2261 q->QQuickFlickable::setContentHeight(syncView->contentHeight());
2262 return;
2263 }
2264
2266 // Don't calculate contentHeight when it
2267 // was set explicitly by the application.
2268 return;
2269 }
2270
2271 if (loadedItems.isEmpty()) {
2272 QBoolBlocker fixupGuard(inUpdateContentSize, true);
2273 if (model && model->count() > 0 && tableModel && tableModel->delegate())
2274 q->QQuickFlickable::setContentHeight(kDefaultRowHeight);
2275 else
2276 q->QQuickFlickable::setContentHeight(0);
2277 return;
2278 }
2279
2281 const int rowsRemaining = nextRow == kEdgeIndexAtEnd ? 0 : tableSize.height() - nextRow;
2282 const qreal remainingRowHeights = rowsRemaining * averageEdgeSize.height();
2283 const qreal remainingSpacing = rowsRemaining * cellSpacing.height();
2284 const qreal estimatedRemainingHeight = remainingRowHeights + remainingSpacing;
2285 const qreal estimatedHeight = loadedTableOuterRect.bottom() + estimatedRemainingHeight;
2286
2287 QBoolBlocker fixupGuard(inUpdateContentSize, true);
2288 q->QQuickFlickable::setContentHeight(estimatedHeight);
2289}
2290
2292{
2293 // When rows or columns outside the viewport are removed or added, or a rebuild
2294 // forces us to guesstimate a new top-left, the edges of the table might end up
2295 // out of sync with the edges of the content view. We detect this situation here, and
2296 // move the origin to ensure that there will never be gaps at the end of the table.
2297 // Normally we detect that the size of the whole table is not going to be equal to the
2298 // size of the content view already when we load the last row/column, and especially
2299 // before it's flicked completely inside the viewport. For those cases we simply adjust
2300 // the origin/endExtent, to give a smooth flicking experience.
2301 // But if flicking fast (e.g with a scrollbar), it can happen that the viewport ends up
2302 // outside the end of the table in just one viewport update. To avoid a "blink" in the
2303 // viewport when that happens, we "move" the loaded table into the viewport to cover it.
2304 Q_Q(QQuickTableView);
2305
2306 bool tableMovedHorizontally = false;
2307 bool tableMovedVertically = false;
2308
2309 const int nextLeftColumn = nextVisibleEdgeIndexAroundLoadedTable(Qt::LeftEdge);
2310 const int nextRightColumn = nextVisibleEdgeIndexAroundLoadedTable(Qt::RightEdge);
2311 const int nextTopRow = nextVisibleEdgeIndexAroundLoadedTable(Qt::TopEdge);
2312 const int nextBottomRow = nextVisibleEdgeIndexAroundLoadedTable(Qt::BottomEdge);
2313
2314 if (syncHorizontally) {
2315 const auto syncView_d = syncView->d_func();
2316 origin.rx() = syncView_d->origin.x();
2317 endExtent.rwidth() = syncView_d->endExtent.width();
2319 } else if (nextLeftColumn == kEdgeIndexAtEnd) {
2320 // There are no more columns to load on the left side of the table.
2321 // In that case, we ensure that the origin match the beginning of the table.
2323 // We have a blank area at the left end of the viewport. In that case we don't have time to
2324 // wait for the viewport to move (after changing origin), since that will take an extra
2325 // update cycle, which will be visible as a blink. Instead, unless the blank spot is just
2326 // us overshooting, we brute force the loaded table inside the already existing viewport.
2327 if (loadedTableOuterRect.left() > origin.x()) {
2328 const qreal diff = loadedTableOuterRect.left() - origin.x();
2331 tableMovedHorizontally = true;
2332 }
2333 }
2336 } else if (loadedTableOuterRect.left() <= origin.x() + cellSpacing.width()) {
2337 // The table rect is at the origin, or outside, but we still have more
2338 // visible columns to the left. So we try to guesstimate how much space
2339 // the rest of the columns will occupy, and move the origin accordingly.
2340 const int columnsRemaining = nextLeftColumn + 1;
2341 const qreal remainingColumnWidths = columnsRemaining * averageEdgeSize.width();
2342 const qreal remainingSpacing = columnsRemaining * cellSpacing.width();
2343 const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing;
2344 origin.rx() = loadedTableOuterRect.left() - estimatedRemainingWidth;
2346 } else if (nextRightColumn == kEdgeIndexAtEnd) {
2347 // There are no more columns to load on the right side of the table.
2348 // In that case, we ensure that the end of the content view match the end of the table.
2350 // We have a blank area at the right end of the viewport. In that case we don't have time to
2351 // wait for the viewport to move (after changing endExtent), since that will take an extra
2352 // update cycle, which will be visible as a blink. Instead, unless the blank spot is just
2353 // us overshooting, we brute force the loaded table inside the already existing viewport.
2354 const qreal w = qMin(viewportRect.right(), q->contentWidth() + endExtent.width());
2355 if (loadedTableOuterRect.right() < w) {
2356 const qreal diff = loadedTableOuterRect.right() - w;
2359 tableMovedHorizontally = true;
2360 }
2361 }
2362 endExtent.rwidth() = loadedTableOuterRect.right() - q->contentWidth();
2364 } else if (loadedTableOuterRect.right() >= q->contentWidth() + endExtent.width() - cellSpacing.width()) {
2365 // The right-most column is outside the end of the content view, and we
2366 // still have more visible columns in the model. This can happen if the application
2367 // has set a fixed content width.
2368 const int columnsRemaining = tableSize.width() - nextRightColumn;
2369 const qreal remainingColumnWidths = columnsRemaining * averageEdgeSize.width();
2370 const qreal remainingSpacing = columnsRemaining * cellSpacing.width();
2371 const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing;
2372 const qreal pixelsOutsideContentWidth = loadedTableOuterRect.right() - q->contentWidth();
2373 endExtent.rwidth() = pixelsOutsideContentWidth + estimatedRemainingWidth;
2375 }
2376
2377 if (syncVertically) {
2378 const auto syncView_d = syncView->d_func();
2379 origin.ry() = syncView_d->origin.y();
2380 endExtent.rheight() = syncView_d->endExtent.height();
2382 } else if (nextTopRow == kEdgeIndexAtEnd) {
2383 // There are no more rows to load on the top side of the table.
2384 // In that case, we ensure that the origin match the beginning of the table.
2386 // We have a blank area at the top of the viewport. In that case we don't have time to
2387 // wait for the viewport to move (after changing origin), since that will take an extra
2388 // update cycle, which will be visible as a blink. Instead, unless the blank spot is just
2389 // us overshooting, we brute force the loaded table inside the already existing viewport.
2390 if (loadedTableOuterRect.top() > origin.y()) {
2391 const qreal diff = loadedTableOuterRect.top() - origin.y();
2394 tableMovedVertically = true;
2395 }
2396 }
2399 } else if (loadedTableOuterRect.top() <= origin.y() + cellSpacing.height()) {
2400 // The table rect is at the origin, or outside, but we still have more
2401 // visible rows at the top. So we try to guesstimate how much space
2402 // the rest of the rows will occupy, and move the origin accordingly.
2403 const int rowsRemaining = nextTopRow + 1;
2404 const qreal remainingRowHeights = rowsRemaining * averageEdgeSize.height();
2405 const qreal remainingSpacing = rowsRemaining * cellSpacing.height();
2406 const qreal estimatedRemainingHeight = remainingRowHeights + remainingSpacing;
2407 origin.ry() = loadedTableOuterRect.top() - estimatedRemainingHeight;
2409 } else if (nextBottomRow == kEdgeIndexAtEnd) {
2410 // There are no more rows to load on the bottom side of the table.
2411 // In that case, we ensure that the end of the content view match the end of the table.
2413 // We have a blank area at the bottom of the viewport. In that case we don't have time to
2414 // wait for the viewport to move (after changing endExtent), since that will take an extra
2415 // update cycle, which will be visible as a blink. Instead, unless the blank spot is just
2416 // us overshooting, we brute force the loaded table inside the already existing viewport.
2417 const qreal h = qMin(viewportRect.bottom(), q->contentHeight() + endExtent.height());
2418 if (loadedTableOuterRect.bottom() < h) {
2419 const qreal diff = loadedTableOuterRect.bottom() - h;
2422 tableMovedVertically = true;
2423 }
2424 }
2425 endExtent.rheight() = loadedTableOuterRect.bottom() - q->contentHeight();
2427 } else if (loadedTableOuterRect.bottom() >= q->contentHeight() + endExtent.height() - cellSpacing.height()) {
2428 // The bottom-most row is outside the end of the content view, and we
2429 // still have more visible rows in the model. This can happen if the application
2430 // has set a fixed content height.
2431 const int rowsRemaining = tableSize.height() - nextBottomRow;
2432 const qreal remainingRowHeigts = rowsRemaining * averageEdgeSize.height();
2433 const qreal remainingSpacing = rowsRemaining * cellSpacing.height();
2434 const qreal estimatedRemainingHeight = remainingRowHeigts + remainingSpacing;
2435 const qreal pixelsOutsideContentHeight = loadedTableOuterRect.bottom() - q->contentHeight();
2436 endExtent.rheight() = pixelsOutsideContentHeight + estimatedRemainingHeight;
2438 }
2439
2440 if (tableMovedHorizontally || tableMovedVertically) {
2441 qCDebug(lcTableViewDelegateLifecycle) << "move table to" << loadedTableOuterRect;
2442
2443 // relayoutTableItems() will take care of moving the existing
2444 // delegate items into the new loadedTableOuterRect.
2446
2447 // Inform the sync children that they need to rebuild to stay in sync
2448 for (auto syncChild : std::as_const(syncChildren)) {
2449 auto syncChild_d = syncChild->d_func();
2450 syncChild_d->scheduledRebuildOptions |= RebuildOption::ViewportOnly;
2451 if (tableMovedHorizontally)
2452 syncChild_d->scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftColumn;
2453 if (tableMovedVertically)
2454 syncChild_d->scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftRow;
2455 }
2456 }
2457
2459 qCDebug(lcTableViewDelegateLifecycle) << "move origin and endExtent to:" << origin << endExtent;
2460 // updateBeginningEnd() will let the new extents take effect. This will also change the
2461 // visualArea of the flickable, which again will cause any attached scrollbars to adjust
2462 // the position of the handle. Note the latter will cause the viewport to move once more.
2464 }
2465}
2466
2468{
2470 const qreal accColumnSpacing = (tableSize.width() - 1) * cellSpacing.width();
2472 } else {
2473 const qreal accColumnSpacing = (loadedColumns.count() - 1) * cellSpacing.width();
2475 }
2476}
2477
2479{
2481 const qreal accRowSpacing = (tableSize.height() - 1) * cellSpacing.height();
2483 } else {
2484 const qreal accRowSpacing = (loadedRows.count() - 1) * cellSpacing.height();
2486 }
2487}
2488
2490{
2491 const QPoint topLeft = QPoint(leftColumn(), topRow());
2492 const QPoint bottomRight = QPoint(rightColumn(), bottomRow());
2493 QRectF topLeftRect = loadedTableItem(topLeft)->geometry();
2494 QRectF bottomRightRect = loadedTableItem(bottomRight)->geometry();
2495 loadedTableOuterRect = QRectF(topLeftRect.topLeft(), bottomRightRect.bottomRight());
2496 loadedTableInnerRect = QRectF(topLeftRect.bottomRight(), bottomRightRect.topLeft());
2497}
2498
2500{
2501 // Move the tracked table rects to the new position. For this to
2502 // take visual effect (move the delegate items to be inside the table
2503 // rect), it needs to be followed by a relayoutTableItems().
2504 // Also note that the position of the viewport needs to be adjusted
2505 // separately for it to overlap the loaded table.
2507 loadedTableOuterRect.moveTopLeft(newPosition);
2508 loadedTableInnerRect.moveTopLeft(newPosition + innerDiff);
2509}
2510
2511QQuickTableViewPrivate::RebuildOptions QQuickTableViewPrivate::checkForVisibilityChanges()
2512{
2513 // This function will check if there are any visibility changes among
2514 // the _already loaded_ rows and columns. Note that there can be rows
2515 // and columns to the bottom or right that was not loaded, but should
2516 // now become visible (in case there is free space around the table).
2517 if (loadedItems.isEmpty()) {
2518 // Report no changes
2519 return RebuildOption::None;
2520 }
2521
2522 RebuildOptions rebuildOptions = RebuildOption::None;
2523
2524 if (loadedTableOuterRect.x() == origin.x() && leftColumn() != 0) {
2525 // Since the left column is at the origin of the viewport, but still not the first
2526 // column in the model, we need to calculate a new left column since there might be
2527 // columns in front of it that used to be hidden, but should now be visible (QTBUG-93264).
2530 } else {
2531 // Go through all loaded columns from first to last, find the columns that used
2532 // to be hidden and not loaded, and check if they should become visible
2533 // (and vice versa). If there is a change, we need to rebuild.
2534 for (int column = leftColumn(); column <= rightColumn(); ++column) {
2535 const bool wasVisibleFromBefore = loadedColumns.contains(column);
2536 const bool isVisibleNow = !qFuzzyIsNull(getColumnWidth(column));
2537 if (wasVisibleFromBefore == isVisibleNow)
2538 continue;
2539
2540 // A column changed visibility. This means that it should
2541 // either be loaded or unloaded. So we need a rebuild.
2542 qCDebug(lcTableViewDelegateLifecycle) << "Column" << column << "changed visibility to" << isVisibleNow;
2544 if (column == leftColumn()) {
2545 // The first loaded column should now be hidden. This means that we
2546 // need to calculate which column should now be first instead.
2548 }
2549 break;
2550 }
2551 }
2552
2553 if (loadedTableOuterRect.y() == origin.y() && topRow() != 0) {
2554 // Since the top row is at the origin of the viewport, but still not the first
2555 // row in the model, we need to calculate a new top row since there might be
2556 // rows in front of it that used to be hidden, but should now be visible (QTBUG-93264).
2559 } else {
2560 // Go through all loaded rows from first to last, find the rows that used
2561 // to be hidden and not loaded, and check if they should become visible
2562 // (and vice versa). If there is a change, we need to rebuild.
2563 for (int row = topRow(); row <= bottomRow(); ++row) {
2564 const bool wasVisibleFromBefore = loadedRows.contains(row);
2565 const bool isVisibleNow = !qFuzzyIsNull(getRowHeight(row));
2566 if (wasVisibleFromBefore == isVisibleNow)
2567 continue;
2568
2569 // A row changed visibility. This means that it should
2570 // either be loaded or unloaded. So we need a rebuild.
2571 qCDebug(lcTableViewDelegateLifecycle) << "Row" << row << "changed visibility to" << isVisibleNow;
2573 if (row == topRow())
2575 break;
2576 }
2577 }
2578
2579 return rebuildOptions;
2580}
2581
2583{
2585 RebuildOptions rebuildOptions = RebuildOption::None;
2586
2587 const QSize actualTableSize = calculateTableSize();
2588 if (tableSize != actualTableSize) {
2589 // The table size will have changed if forceLayout is called after
2590 // the row count in the model has changed, but before we received
2591 // a rowsInsertedCallback about it (and vice versa for columns).
2593 }
2594
2595 // Resizing a column (or row) can result in the table going from being
2596 // e.g completely inside the viewport to go outside. And in the latter
2597 // case, the user needs to be able to scroll the viewport, also if
2598 // flags such as Flickable.StopAtBounds is in use. So we need to
2599 // update contentWidth/Height to support that case.
2604
2606
2607 if (immediate) {
2608 auto rootView = rootSyncView();
2609 const bool updated = rootView->d_func()->updateTableRecursive();
2610 if (!updated) {
2611 qWarning() << "TableView::forceLayout(): Cannot do an immediate re-layout during an ongoing layout!";
2612 rootView->polish();
2613 }
2614 }
2615}
2616
2618{
2619 if (loadRequest.edge() == Qt::Edge(0)) {
2620 // No edge means we're loading the top-left item
2623 return;
2624 }
2625
2626 switch (loadRequest.edge()) {
2627 case Qt::LeftEdge:
2628 case Qt::RightEdge:
2630 break;
2631 case Qt::TopEdge:
2632 case Qt::BottomEdge:
2634 break;
2635 }
2636}
2637
2639{
2640 const int modelIndex = modelIndexAtCell(cell);
2641 Q_TABLEVIEW_ASSERT(loadedItems.contains(modelIndex), modelIndex << cell);
2642 return loadedItems.value(modelIndex);
2643}
2644
2646{
2647 Q_Q(QQuickTableView);
2648
2649 bool ownItem = false;
2650 int modelIndex = modelIndexAtCell(cell);
2651
2652 QObject* object = model->object(modelIndex, incubationMode);
2653 if (!object) {
2654 if (model->incubationStatus(modelIndex) == QQmlIncubator::Loading) {
2655 // Item is incubating. Return nullptr for now, and let the table call this
2656 // function again once we get a callback to itemCreatedCallback().
2657 return nullptr;
2658 }
2659
2660 qWarning() << "TableView: failed loading index:" << modelIndex;
2661 object = new QQuickItem();
2662 ownItem = true;
2663 }
2664
2666 if (!item) {
2667 // The model could not provide an QQuickItem for the
2668 // given index, so we create a placeholder instead.
2669 qWarning() << "TableView: delegate is not an item:" << modelIndex;
2670 model->release(object);
2671 item = new QQuickItem();
2672 ownItem = true;
2673 } else {
2676 qmlWarning(item) << "TableView: detected anchors on delegate with index: " << modelIndex
2677 << ". Use implicitWidth and implicitHeight instead.";
2678 }
2679
2680 if (ownItem) {
2681 // Parent item is normally set early on from initItemCallback (to
2682 // allow bindings to the parent property). But if we created the item
2683 // within this function, we need to set it explicit.
2684 item->setImplicitWidth(kDefaultColumnWidth);
2685 item->setImplicitHeight(kDefaultRowHeight);
2686 item->setParentItem(q->contentItem());
2687 }
2688 Q_TABLEVIEW_ASSERT(item->parentItem() == q->contentItem(), item->parentItem());
2689
2690 FxTableItem *fxTableItem = new FxTableItem(item, q, ownItem);
2691 fxTableItem->setVisible(false);
2692 fxTableItem->cell = cell;
2693 fxTableItem->index = modelIndex;
2694 return fxTableItem;
2695}
2696
2698{
2699#ifdef QT_DEBUG
2700 // Since TableView needs to work flawlessly when e.g incubating inside an async
2701 // loader, being able to override all loading to async while debugging can be helpful.
2702 static const bool forcedAsync = forcedIncubationMode == QLatin1String("async");
2703 if (forcedAsync)
2704 incubationMode = QQmlIncubator::Asynchronous;
2705#endif
2706
2707 // Note that even if incubation mode is asynchronous, the item might
2708 // be ready immediately since the model has a cache of items.
2710 auto item = createFxTableItem(cell, incubationMode);
2711 qCDebug(lcTableViewDelegateLifecycle) << cell << "ready?" << bool(item);
2712 return item;
2713}
2714
2716 // Make a copy and clear the list of items first to avoid destroyed
2717 // items being accessed during the loop (QTBUG-61294)
2718 auto const tmpList = loadedItems;
2720 for (FxTableItem *item : tmpList)
2722}
2723
2725{
2726 Q_Q(QQuickTableView);
2727 // Note that fxTableItem->item might already have been destroyed, in case
2728 // the item is owned by the QML context rather than the model (e.g ObjectModel etc).
2729 auto item = fxTableItem->item;
2730
2731 if (fxTableItem->ownItem) {
2732 Q_TABLEVIEW_ASSERT(item, fxTableItem->index);
2733 delete item;
2734 } else if (item) {
2735 auto releaseFlag = model->release(item, reusableFlag);
2736 if (releaseFlag == QQmlInstanceModel::Pooled) {
2737 fxTableItem->setVisible(false);
2738
2739 // If the item (or a descendant) has focus, remove it, so
2740 // that the item doesn't enter with focus when it's reused.
2741 if (QQuickWindow *window = item->window()) {
2742 const auto focusItem = qobject_cast<QQuickItem *>(window->focusObject());
2743 if (focusItem) {
2744 const bool hasFocus = item == focusItem || item->isAncestorOf(focusItem);
2745 if (hasFocus) {
2746 const auto focusChild = QQuickItemPrivate::get(q)->subFocusItem;
2748 }
2749 }
2750 }
2751 }
2752 }
2753
2754 delete fxTableItem;
2755}
2756
2758{
2759 const int modelIndex = modelIndexAtCell(cell);
2760 Q_TABLEVIEW_ASSERT(loadedItems.contains(modelIndex), modelIndex << cell);
2762}
2763
2764bool QQuickTableViewPrivate::canLoadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const
2765{
2766 switch (tableEdge) {
2767 case Qt::LeftEdge:
2768 return loadedTableOuterRect.left() > fillRect.left() + cellSpacing.width();
2769 case Qt::RightEdge:
2770 return loadedTableOuterRect.right() < fillRect.right() - cellSpacing.width();
2771 case Qt::TopEdge:
2772 return loadedTableOuterRect.top() > fillRect.top() + cellSpacing.height();
2773 case Qt::BottomEdge:
2774 return loadedTableOuterRect.bottom() < fillRect.bottom() - cellSpacing.height();
2775 }
2776
2777 return false;
2778}
2779
2781{
2782 // Note: if there is only one row or column left, we cannot unload, since
2783 // they are needed as anchor point for further layouting. We also skip
2784 // unloading in the direction we're currently scrolling.
2785
2786 switch (tableEdge) {
2787 case Qt::LeftEdge:
2788 if (loadedColumns.count() <= 1)
2789 return false;
2791 const qreal to = positionXAnimation.to().toFloat();
2792 if (to < viewportRect.x())
2793 return false;
2794 }
2795 return loadedTableInnerRect.left() <= fillRect.left();
2796 case Qt::RightEdge:
2797 if (loadedColumns.count() <= 1)
2798 return false;
2800 const qreal to = positionXAnimation.to().toFloat();
2801 if (to > viewportRect.x())
2802 return false;
2803 }
2804 return loadedTableInnerRect.right() >= fillRect.right();
2805 case Qt::TopEdge:
2806 if (loadedRows.count() <= 1)
2807 return false;
2809 const qreal to = positionYAnimation.to().toFloat();
2810 if (to < viewportRect.y())
2811 return false;
2812 }
2813 return loadedTableInnerRect.top() <= fillRect.top();
2814 case Qt::BottomEdge:
2815 if (loadedRows.count() <= 1)
2816 return false;
2818 const qreal to = positionYAnimation.to().toFloat();
2819 if (to > viewportRect.y())
2820 return false;
2821 }
2822 return loadedTableInnerRect.bottom() >= fillRect.bottom();
2823 }
2824 Q_TABLEVIEW_UNREACHABLE(tableEdge);
2825 return false;
2826}
2827
2829{
2830 for (Qt::Edge edge : allTableEdges) {
2831 if (!canLoadTableEdge(edge, rect))
2832 continue;
2833 const int nextIndex = nextVisibleEdgeIndexAroundLoadedTable(edge);
2834 if (nextIndex == kEdgeIndexAtEnd)
2835 continue;
2836 return edge;
2837 }
2838
2839 return Qt::Edge(0);
2840}
2841
2843{
2844 for (Qt::Edge edge : allTableEdges) {
2845 if (canUnloadTableEdge(edge, rect))
2846 return edge;
2847 }
2848 return Qt::Edge(0);
2849}
2850
2852{
2853 // Using an items width directly is not an option, since we change
2854 // it during layout (which would also cause problems when recycling items).
2855 auto const cellItem = loadedTableItem(cell)->item;
2856 return cellItem->implicitWidth();
2857}
2858
2860{
2861 // Using an items height directly is not an option, since we change
2862 // it during layout (which would also cause problems when recycling items).
2863 auto const cellItem = loadedTableItem(cell)->item;
2864 return cellItem->implicitHeight();
2865}
2866
2868{
2869 // Find the widest cell in the column, and return its width
2870 qreal columnWidth = 0;
2871 for (const int row : loadedRows)
2872 columnWidth = qMax(columnWidth, cellWidth(QPoint(column, row)));
2873
2874 return columnWidth;
2875}
2876
2878{
2879 // Find the highest cell in the row, and return its height
2880 qreal rowHeight = 0;
2881 for (const int column : loadedColumns)
2882 rowHeight = qMax(rowHeight, cellHeight(QPoint(column, row)));
2883 return rowHeight;
2884}
2885
2887{
2888 QSize size(0, 0);
2889 if (tableModel)
2891 else if (model)
2892 size = QSize(1, model->count());
2893
2894 return isTransposed ? size.transposed() : size;
2895}
2896
2898{
2899 // Return the column width specified by the application, or go
2900 // through the loaded items and calculate it as a fallback. For
2901 // layouting, the width can never be zero (or negative), as this
2902 // can lead us to be stuck in an infinite loop trying to load and
2903 // fill out the empty viewport space with empty columns.
2904 const qreal explicitColumnWidth = getColumnWidth(column);
2905 if (explicitColumnWidth >= 0)
2906 return explicitColumnWidth;
2907
2908 if (syncHorizontally) {
2909 if (syncView->d_func()->loadedColumns.contains(column))
2910 return syncView->d_func()->getColumnLayoutWidth(column);
2911 }
2912
2913 // Iterate over the currently visible items in the column. The downside
2914 // of doing that, is that the column width will then only be based on the implicit
2915 // width of the currently loaded items (which can be different depending on which
2916 // row you're at when the column is flicked in). The upshot is that you don't have to
2917 // bother setting columnWidthProvider for small tables, or if the implicit width doesn't vary.
2918 qreal columnWidth = sizeHintForColumn(column);
2919
2920 if (qIsNaN(columnWidth) || columnWidth <= 0) {
2921 if (!layoutWarningIssued) {
2922 layoutWarningIssued = true;
2923 qmlWarning(q_func()) << "the delegate's implicitWidth needs to be greater than zero";
2924 }
2925 columnWidth = kDefaultColumnWidth;
2926 }
2927
2928 return columnWidth;
2929}
2930
2932{
2933 // Return y pos of row after layout
2935 return loadedTableItem(QPoint(leftColumn(), row))->geometry().y();
2936}
2937
2939{
2940 // Return row height after layout
2943}
2944
2946{
2947 // Return x pos of column after layout
2949 return loadedTableItem(QPoint(column, topRow()))->geometry().x();
2950}
2951
2953{
2954 // Return column width after layout
2957}
2958
2960{
2961 // Return the row height specified by the application, or go
2962 // through the loaded items and calculate it as a fallback. For
2963 // layouting, the height can never be zero (or negative), as this
2964 // can lead us to be stuck in an infinite loop trying to load and
2965 // fill out the empty viewport space with empty rows.
2966 const qreal explicitRowHeight = getRowHeight(row);
2967 if (explicitRowHeight >= 0)
2968 return explicitRowHeight;
2969
2970 if (syncVertically) {
2971 if (syncView->d_func()->loadedRows.contains(row))
2972 return syncView->d_func()->getRowLayoutHeight(row);
2973 }
2974
2975 // Iterate over the currently visible items in the row. The downside
2976 // of doing that, is that the row height will then only be based on the implicit
2977 // height of the currently loaded items (which can be different depending on which
2978 // column you're at when the row is flicked in). The upshot is that you don't have to
2979 // bother setting rowHeightProvider for small tables, or if the implicit height doesn't vary.
2980 qreal rowHeight = sizeHintForRow(row);
2981
2982 if (qIsNaN(rowHeight) || rowHeight <= 0) {
2983 if (!layoutWarningIssued) {
2984 layoutWarningIssued = true;
2985 qmlWarning(q_func()) << "the delegate's implicitHeight needs to be greater than zero";
2986 }
2987 rowHeight = kDefaultRowHeight;
2988 }
2989
2990 return rowHeight;
2991}
2992
2994{
2995 // Return the width of the given column, if explicitly set. Return 0 if the column
2996 // is hidden, and -1 if the width is not set (which means that the width should
2997 // instead be calculated from the implicit size of the delegate items. This function
2998 // can be overridden by e.g HeaderView to provide the column widths by other means.
2999 Q_Q(const QQuickTableView);
3000
3001 const int noExplicitColumnWidth = -1;
3002
3004 return cachedColumnWidth.size;
3005
3006 if (syncHorizontally)
3007 return syncView->d_func()->getColumnWidth(column);
3008
3010 // We only respect explicit column widths when no columnWidthProvider
3011 // is set. Otherwise it's the responsibility of the provider to e.g
3012 // call explicitColumnWidth() (and implicitColumnWidth()), if needed.
3013 qreal explicitColumnWidth = q->explicitColumnWidth(column);
3014 if (explicitColumnWidth >= 0)
3015 return explicitColumnWidth;
3016 return noExplicitColumnWidth;
3017 }
3018
3019 qreal columnWidth = noExplicitColumnWidth;
3020
3022 auto const columnAsArgument = QJSValueList() << QJSValue(column);
3023 columnWidth = columnWidthProvider.call(columnAsArgument).toNumber();
3024 if (qIsNaN(columnWidth) || columnWidth < 0)
3025 columnWidth = noExplicitColumnWidth;
3026 } else {
3027 if (!layoutWarningIssued) {
3028 layoutWarningIssued = true;
3029 qmlWarning(q_func()) << "columnWidthProvider doesn't contain a function";
3030 }
3031 columnWidth = noExplicitColumnWidth;
3032 }
3033
3035 cachedColumnWidth.size = columnWidth;
3036 return columnWidth;
3037}
3038
3040{
3041 // Return the height of the given row, if explicitly set. Return 0 if the row
3042 // is hidden, and -1 if the height is not set (which means that the height should
3043 // instead be calculated from the implicit size of the delegate items. This function
3044 // can be overridden by e.g HeaderView to provide the row heights by other means.
3045 Q_Q(const QQuickTableView);
3046
3047 const int noExplicitRowHeight = -1;
3048
3050 return cachedRowHeight.size;
3051
3052 if (syncVertically)
3053 return syncView->d_func()->getRowHeight(row);
3054
3056 // We only resepect explicit row heights when no rowHeightProvider
3057 // is set. Otherwise it's the responsibility of the provider to e.g
3058 // call explicitRowHeight() (and implicitRowHeight()), if needed.
3059 qreal explicitRowHeight = q->explicitRowHeight(row);
3060 if (explicitRowHeight >= 0)
3061 return explicitRowHeight;
3062 return noExplicitRowHeight;
3063 }
3064
3065 qreal rowHeight = noExplicitRowHeight;
3066
3068 auto const rowAsArgument = QJSValueList() << QJSValue(row);
3069 rowHeight = rowHeightProvider.call(rowAsArgument).toNumber();
3070 if (qIsNaN(rowHeight) || rowHeight < 0)
3071 rowHeight = noExplicitRowHeight;
3072 } else {
3073 if (!layoutWarningIssued) {
3074 layoutWarningIssued = true;
3075 qmlWarning(q_func()) << "rowHeightProvider doesn't contain a function";
3076 }
3077 rowHeight = noExplicitRowHeight;
3078 }
3079
3081 cachedRowHeight.size = rowHeight;
3082 return rowHeight;
3083}
3084
3086{
3087 Q_Q(QQuickTableView);
3088
3089 qreal contentX = 0;
3090 const int columnX = getEffectiveColumnX(column);
3091
3092 if (subRect.isValid()) {
3094 // Special case: Align to the right as long as the left
3095 // edge of the cell remains visible. Otherwise align to the left.
3096 alignment = subRect.width() > q->width() ? Qt::AlignLeft : Qt::AlignRight;
3097 }
3098
3099 if (alignment & Qt::AlignLeft) {
3100 contentX = columnX + subRect.x() + offset;
3101 } else if (alignment & Qt::AlignRight) {
3102 contentX = columnX + subRect.right() - viewportRect.width() + offset;
3103 } else if (alignment & Qt::AlignHCenter) {
3104 const qreal centerDistance = (viewportRect.width() - subRect.width()) / 2;
3105 contentX = columnX + subRect.x() - centerDistance + offset;
3106 }
3107 } else {
3108 const int columnWidth = getEffectiveColumnWidth(column);
3110 alignment = columnWidth > q->width() ? Qt::AlignLeft : Qt::AlignRight;
3111
3112 if (alignment & Qt::AlignLeft) {
3113 contentX = columnX + offset;
3114 } else if (alignment & Qt::AlignRight) {
3115 contentX = columnX + columnWidth - viewportRect.width() + offset;
3116 } else if (alignment & Qt::AlignHCenter) {
3117 const qreal centerDistance = (viewportRect.width() - columnWidth) / 2;
3118 contentX = columnX - centerDistance + offset;
3119 }
3120 }
3121
3122 // Don't overshoot
3123 contentX = qBound(-q->minXExtent(), contentX, -q->maxXExtent());
3124
3125 return contentX;
3126}
3127
3129{
3130 Q_Q(QQuickTableView);
3131
3132 qreal contentY = 0;
3133 const int rowY = getEffectiveRowY(row);
3134
3135 if (subRect.isValid()) {
3137 // Special case: Align to the bottom as long as the top
3138 // edge of the cell remains visible. Otherwise align to the top.
3139 alignment = subRect.height() > q->height() ? Qt::AlignTop : Qt::AlignBottom;
3140 }
3141
3142 if (alignment & Qt::AlignTop) {
3143 contentY = rowY + subRect.y() + offset;
3144 } else if (alignment & Qt::AlignBottom) {
3145 contentY = rowY + subRect.bottom() - viewportRect.height() + offset;
3146 } else if (alignment & Qt::AlignVCenter) {
3147 const qreal centerDistance = (viewportRect.height() - subRect.height()) / 2;
3148 contentY = rowY + subRect.y() - centerDistance + offset;
3149 }
3150 } else {
3151 const int rowHeight = getEffectiveRowHeight(row);
3153 alignment = rowHeight > q->height() ? Qt::AlignTop : Qt::AlignBottom;
3154
3155 if (alignment & Qt::AlignTop) {
3156 contentY = rowY + offset;
3157 } else if (alignment & Qt::AlignBottom) {
3158 contentY = rowY + rowHeight - viewportRect.height() + offset;
3159 } else if (alignment & Qt::AlignVCenter) {
3160 const qreal centerDistance = (viewportRect.height() - rowHeight) / 2;
3161 contentY = rowY - centerDistance + offset;
3162 }
3163 }
3164
3165 // Don't overshoot
3166 contentY = qBound(-q->minYExtent(), contentY, -q->maxYExtent());
3167
3168 return contentY;
3169}
3170
3172{
3173 // A column is hidden if the width is explicit set to zero (either by
3174 // using a columnWidthProvider, or by overriding getColumnWidth()).
3176}
3177
3179{
3180 // A row is hidden if the height is explicit set to zero (either by
3181 // using a rowHeightProvider, or by overriding getRowHeight()).
3182 return qFuzzyIsNull(getRowHeight(row));
3183}
3184
3186{
3187 qCDebug(lcTableViewDelegateLifecycle);
3188
3189 if (viewportRect.isEmpty()) {
3190 // This can happen if TableView was resized down to have a zero size
3191 qCDebug(lcTableViewDelegateLifecycle()) << "Skipping relayout, viewport has zero size";
3192 return;
3193 }
3194
3195 qreal nextColumnX = loadedTableOuterRect.x();
3196 qreal nextRowY = loadedTableOuterRect.y();
3197
3198 for (const int column : loadedColumns) {
3199 // Adjust the geometry of all cells in the current column
3201
3202 for (const int row : loadedRows) {
3204 QRectF geometry = item->geometry();
3205 geometry.moveLeft(nextColumnX);
3206 geometry.setWidth(width);
3207 item->setGeometry(geometry);
3208 }
3209
3210 if (width > 0)
3211 nextColumnX += width + cellSpacing.width();
3212 }
3213
3214 for (const int row : loadedRows) {
3215 // Adjust the geometry of all cells in the current row
3217
3218 for (const int column : loadedColumns) {
3220 QRectF geometry = item->geometry();
3221 geometry.moveTop(nextRowY);
3222 geometry.setHeight(height);
3223 item->setGeometry(geometry);
3224 }
3225
3226 if (height > 0)
3227 nextRowY += height + cellSpacing.height();
3228 }
3229
3230 if (Q_UNLIKELY(lcTableViewDelegateLifecycle().isDebugEnabled())) {
3231 for (const int column : loadedColumns) {
3232 for (const int row : loadedRows) {
3233 QPoint cell = QPoint(column, row);
3234 qCDebug(lcTableViewDelegateLifecycle()) << "relayout item:" << cell << loadedTableItem(cell)->geometry();
3235 }
3236 }
3237 }
3238}
3239
3241{
3242 int columnThatNeedsLayout;
3243 int neighbourColumn;
3244 qreal columnX;
3245 qreal columnWidth;
3246
3247 if (tableEdge == Qt::LeftEdge) {
3248 columnThatNeedsLayout = leftColumn();
3249 neighbourColumn = loadedColumns.values().at(1);
3250 columnWidth = getColumnLayoutWidth(columnThatNeedsLayout);
3251 const auto neighbourItem = loadedTableItem(QPoint(neighbourColumn, topRow()));
3252 columnX = neighbourItem->geometry().left() - cellSpacing.width() - columnWidth;
3253 } else {
3254 columnThatNeedsLayout = rightColumn();
3255 neighbourColumn = loadedColumns.values().at(loadedColumns.count() - 2);
3256 columnWidth = getColumnLayoutWidth(columnThatNeedsLayout);
3257 const auto neighbourItem = loadedTableItem(QPoint(neighbourColumn, topRow()));
3258 columnX = neighbourItem->geometry().right() + cellSpacing.width();
3259 }
3260
3261 for (const int row : loadedRows) {
3262 auto fxTableItem = loadedTableItem(QPoint(columnThatNeedsLayout, row));
3263 auto const neighbourItem = loadedTableItem(QPoint(neighbourColumn, row));
3264 const qreal rowY = neighbourItem->geometry().y();
3265 const qreal rowHeight = neighbourItem->geometry().height();
3266
3267 fxTableItem->setGeometry(QRectF(columnX, rowY, columnWidth, rowHeight));
3268 fxTableItem->setVisible(true);
3269
3270 qCDebug(lcTableViewDelegateLifecycle()) << "layout item:" << QPoint(columnThatNeedsLayout, row) << fxTableItem->geometry();
3271 }
3272}
3273
3275{
3276 int rowThatNeedsLayout;
3277 int neighbourRow;
3278
3279 if (tableEdge == Qt::TopEdge) {
3280 rowThatNeedsLayout = topRow();
3281 neighbourRow = loadedRows.values().at(1);
3282 } else {
3283 rowThatNeedsLayout = bottomRow();
3284 neighbourRow = loadedRows.values().at(loadedRows.count() - 2);
3285 }
3286
3287 // Set the width first, since text items in QtQuick will calculate
3288 // implicitHeight based on the text items width.
3289 for (const int column : loadedColumns) {
3290 auto fxTableItem = loadedTableItem(QPoint(column, rowThatNeedsLayout));
3291 auto const neighbourItem = loadedTableItem(QPoint(column, neighbourRow));
3292 const qreal columnX = neighbourItem->geometry().x();
3293 const qreal columnWidth = neighbourItem->geometry().width();
3294 fxTableItem->item->setX(columnX);
3295 fxTableItem->item->setWidth(columnWidth);
3296 }
3297
3298 qreal rowY;
3299 qreal rowHeight;
3300 if (tableEdge == Qt::TopEdge) {
3301 rowHeight = getRowLayoutHeight(rowThatNeedsLayout);
3302 const auto neighbourItem = loadedTableItem(QPoint(leftColumn(), neighbourRow));
3303 rowY = neighbourItem->geometry().top() - cellSpacing.height() - rowHeight;
3304 } else {
3305 rowHeight = getRowLayoutHeight(rowThatNeedsLayout);
3306 const auto neighbourItem = loadedTableItem(QPoint(leftColumn(), neighbourRow));
3307 rowY = neighbourItem->geometry().bottom() + cellSpacing.height();
3308 }
3309
3310 for (const int column : loadedColumns) {
3311 auto fxTableItem = loadedTableItem(QPoint(column, rowThatNeedsLayout));
3312 fxTableItem->item->setY(rowY);
3313 fxTableItem->item->setHeight(rowHeight);
3314 fxTableItem->setVisible(true);
3315
3316 qCDebug(lcTableViewDelegateLifecycle()) << "layout item:" << QPoint(column, rowThatNeedsLayout) << fxTableItem->geometry();
3317 }
3318}
3319
3321{
3322 const QPoint cell(loadRequest.column(), loadRequest.row());
3323 auto topLeftItem = loadedTableItem(cell);
3324 auto item = topLeftItem->item;
3325
3326 item->setPosition(loadRequest.startPosition());
3327 item->setSize(QSizeF(getColumnLayoutWidth(cell.x()), getRowLayoutHeight(cell.y())));
3328 topLeftItem->setVisible(true);
3329 qCDebug(lcTableViewDelegateLifecycle) << "geometry:" << topLeftItem->geometry();
3330}
3331
3333{
3334 if (loadRequest.edge() == Qt::Edge(0)) {
3335 // No edge means we're loading the top-left item
3337 return;
3338 }
3339
3340 switch (loadRequest.edge()) {
3341 case Qt::LeftEdge:
3342 case Qt::RightEdge:
3344 break;
3345 case Qt::TopEdge:
3346 case Qt::BottomEdge:
3348 break;
3349 }
3350}
3351
3353{
3354 Q_Q(QQuickTableView);
3356
3357 while (loadRequest.hasCurrentCell()) {
3359 FxTableItem *fxTableItem = loadFxTableItem(cell, loadRequest.incubationMode());
3360
3361 if (!fxTableItem) {
3362 // Requested item is not yet ready. Just leave, and wait for this
3363 // function to be called again when the item is ready.
3364 return;
3365 }
3366
3367 loadedItems.insert(modelIndexAtCell(cell), fxTableItem);
3369 }
3370
3371 qCDebug(lcTableViewDelegateLifecycle()) << "all items loaded!";
3372
3376
3378 // Loading of this edge was not done as a part of a rebuild, but
3379 // instead as an incremental build after e.g a flick.
3380 updateExtents();
3382
3383 switch (loadRequest.edge()) {
3384 case Qt::LeftEdge:
3385 emit q->leftColumnChanged();
3386 break;
3387 case Qt::RightEdge:
3388 emit q->rightColumnChanged();
3389 break;
3390 case Qt::TopEdge:
3391 emit q->topRowChanged();
3392 break;
3393 case Qt::BottomEdge:
3394 emit q->bottomRowChanged();
3395 break;
3396 }
3397
3398 if (editIndex.isValid())
3400
3401 emit q->layoutChanged();
3402 }
3403
3405
3406 qCDebug(lcTableViewDelegateLifecycle()) << "current table:" << tableLayoutToString();
3407 qCDebug(lcTableViewDelegateLifecycle()) << "Load request completed!";
3408 qCDebug(lcTableViewDelegateLifecycle()) << "****************************************";
3409}
3410
3412{
3413 Q_Q(QQuickTableView);
3414
3416 qCDebug(lcTableViewDelegateLifecycle()) << "begin rebuild:" << q << "options:" << rebuildOptions;
3419 : QMargins(q->leftColumn(), q->topRow(), q->rightColumn(), q->bottomRow());
3420 }
3421
3423
3427 return;
3428 }
3429
3431 if (loadedItems.isEmpty()) {
3432 qCDebug(lcTableViewDelegateLifecycle()) << "no items loaded!";
3436 } else if (!moveToNextRebuildState()) {
3437 return;
3438 }
3439 }
3440
3445 return;
3446 }
3447
3452 return;
3453 }
3454
3458 return;
3459 }
3460
3461 const bool preload = (rebuildOptions & RebuildOption::All
3463
3465 if (preload && !atTableEnd(Qt::RightEdge))
3468 return;
3469 }
3470
3472 if (preload && !atTableEnd(Qt::BottomEdge))
3475 return;
3476 }
3477
3479 while (Qt::Edge edge = nextEdgeToUnload(viewportRect))
3480 unloadEdge(edge);
3482 return;
3483 }
3484
3487 emit q->columnsChanged();
3489 emit q->rowsChanged();
3490 if (edgesBeforeRebuild.left() != q->leftColumn())
3491 emit q->leftColumnChanged();
3492 if (edgesBeforeRebuild.right() != q->rightColumn())
3493 emit q->rightColumnChanged();
3494 if (edgesBeforeRebuild.top() != q->topRow())
3495 emit q->topRowChanged();
3496 if (edgesBeforeRebuild.bottom() != q->bottomRow())
3497 emit q->bottomRowChanged();
3498
3499 if (editIndex.isValid())
3502
3503 emit q->layoutChanged();
3504
3505 qCDebug(lcTableViewDelegateLifecycle()) << "current table:" << tableLayoutToString();
3506 qCDebug(lcTableViewDelegateLifecycle()) << "rebuild completed!";
3507 qCDebug(lcTableViewDelegateLifecycle()) << "################################################";
3508 qCDebug(lcTableViewDelegateLifecycle());
3509 }
3510
3512}
3513
3515{
3516 if (loadRequest.isActive()) {
3517 // Items are still loading async, which means
3518 // that the current state is not yet done.
3519 return false;
3520 }
3521
3525 else
3527
3528 qCDebug(lcTableViewDelegateLifecycle()) << rebuildState;
3529 return true;
3530}
3531
3533{
3534 if (tableSize.isEmpty()) {
3535 // There is no cell that can be top left
3536 topLeftCell.rx() = kEdgeIndexAtEnd;
3537 topLeftCell.ry() = kEdgeIndexAtEnd;
3538 return;
3539 }
3540
3542 const auto syncView_d = syncView->d_func();
3543
3544 if (syncView_d->loadedItems.isEmpty()) {
3545 topLeftCell.rx() = 0;
3546 topLeftCell.ry() = 0;
3547 return;
3548 }
3549
3550 // Get sync view top left, and use that as our own top left (if possible)
3551 const QPoint syncViewTopLeftCell(syncView_d->leftColumn(), syncView_d->topRow());
3552 const auto syncViewTopLeftFxItem = syncView_d->loadedTableItem(syncViewTopLeftCell);
3553 const QPointF syncViewTopLeftPos = syncViewTopLeftFxItem->geometry().topLeft();
3554
3555 if (syncHorizontally) {
3556 topLeftCell.rx() = syncViewTopLeftCell.x();
3557 topLeftPos.rx() = syncViewTopLeftPos.x();
3558
3559 if (topLeftCell.x() >= tableSize.width()) {
3560 // Top left is outside our own model.
3561 topLeftCell.rx() = kEdgeIndexAtEnd;
3562 topLeftPos.rx() = kEdgeIndexAtEnd;
3563 }
3564 }
3565
3566 if (syncVertically) {
3567 topLeftCell.ry() = syncViewTopLeftCell.y();
3568 topLeftPos.ry() = syncViewTopLeftPos.y();
3569
3570 if (topLeftCell.y() >= tableSize.height()) {
3571 // Top left is outside our own model.
3572 topLeftCell.ry() = kEdgeIndexAtEnd;
3573 topLeftPos.ry() = kEdgeIndexAtEnd;
3574 }
3575 }
3576
3578 // We have a valid top left, so we're done
3579 return;
3580 }
3581 }
3582
3583 // Since we're not sync-ing both horizontal and vertical, calculate the missing
3584 // dimention(s) ourself. If we rebuild all, we find the first visible top-left
3585 // item starting from cell(0, 0). Otherwise, guesstimate which row or column that
3586 // should be the new top-left given the geometry of the viewport.
3587
3588 if (!syncHorizontally) {
3590 // Find the first visible column from the beginning
3591 topLeftCell.rx() = nextVisibleEdgeIndex(Qt::RightEdge, 0);
3592 if (topLeftCell.x() == kEdgeIndexAtEnd) {
3593 // No visible column found
3594 return;
3595 }
3597 // Guesstimate new top left
3598 const int newColumn = int(viewportRect.x() / (averageEdgeSize.width() + cellSpacing.width()));
3599 topLeftCell.rx() = qBound(0, newColumn, tableSize.width() - 1);
3600 topLeftPos.rx() = topLeftCell.x() * (averageEdgeSize.width() + cellSpacing.width());
3602 topLeftCell.rx() = qBound(0, positionViewAtColumnAfterRebuild, tableSize.width() - 1);
3603 topLeftPos.rx() = qFloor(topLeftCell.x()) * (averageEdgeSize.width() + cellSpacing.width());
3604 } else {
3605 // Keep the current top left, unless it's outside model
3606 topLeftCell.rx() = qBound(0, leftColumn(), tableSize.width() - 1);
3607 // We begin by loading the columns where the viewport is at
3608 // now. But will move the whole table and the viewport
3609 // later, when we do a layoutAfterLoadingInitialTable().
3610 topLeftPos.rx() = loadedTableOuterRect.x();
3611 }
3612 }
3613
3614 if (!syncVertically) {
3616 // Find the first visible row from the beginning
3617 topLeftCell.ry() = nextVisibleEdgeIndex(Qt::BottomEdge, 0);
3618 if (topLeftCell.y() == kEdgeIndexAtEnd) {
3619 // No visible row found
3620 return;
3621 }
3623 // Guesstimate new top left
3624 const int newRow = int(viewportRect.y() / (averageEdgeSize.height() + cellSpacing.height()));
3625 topLeftCell.ry() = qBound(0, newRow, tableSize.height() - 1);
3626 topLeftPos.ry() = topLeftCell.y() * (averageEdgeSize.height() + cellSpacing.height());
3628 topLeftCell.ry() = qBound(0, positionViewAtRowAfterRebuild, tableSize.height() - 1);
3629 topLeftPos.ry() = qFloor(topLeftCell.y()) * (averageEdgeSize.height() + cellSpacing.height());
3630 } else {
3631 topLeftCell.ry() = qBound(0, topRow(), tableSize.height() - 1);
3632 topLeftPos.ry() = loadedTableOuterRect.y();
3633 }
3634 }
3635}
3636
3638{
3640
3645 }
3646
3651 }
3652
3653 QPoint topLeft;
3654 QPointF topLeftPos;
3655 calculateTopLeft(topLeft, topLeftPos);
3656 qCDebug(lcTableViewDelegateLifecycle()) << "initial viewport rect:" << viewportRect;
3657 qCDebug(lcTableViewDelegateLifecycle()) << "initial top left cell:" << topLeft << ", pos:" << topLeftPos;
3658
3659 if (!loadedItems.isEmpty()) {
3664 }
3665
3667 origin = QPointF(0, 0);
3668 endExtent = QSizeF(0, 0);
3672 }
3673
3675 loadedRows.clear();
3679
3680 if (syncHorizontally)
3682
3683 if (syncVertically)
3685
3687 setLocalViewportX(topLeftPos.x());
3688
3690 setLocalViewportY(topLeftPos.y());
3691
3693
3694 if (!model) {
3695 qCDebug(lcTableViewDelegateLifecycle()) << "no model found, leaving table empty";
3696 return;
3697 }
3698
3699 if (model->count() == 0) {
3700 qCDebug(lcTableViewDelegateLifecycle()) << "empty model found, leaving table empty";
3701 return;
3702 }
3703
3704 if (tableModel && !tableModel->delegate()) {
3705 qCDebug(lcTableViewDelegateLifecycle()) << "no delegate found, leaving table empty";
3706 return;
3707 }
3708
3709 if (topLeft.x() == kEdgeIndexAtEnd || topLeft.y() == kEdgeIndexAtEnd) {
3710 qCDebug(lcTableViewDelegateLifecycle()) << "no visible row or column found, leaving table empty";
3711 return;
3712 }
3713
3714 if (topLeft.x() == kEdgeIndexNotSet || topLeft.y() == kEdgeIndexNotSet) {
3715 qCDebug(lcTableViewDelegateLifecycle()) << "could not resolve top-left item, leaving table empty";
3716 return;
3717 }
3718
3719 if (viewportRect.isEmpty()) {
3720 qCDebug(lcTableViewDelegateLifecycle()) << "viewport has zero size, leaving table empty";
3721 return;
3722 }
3723
3724 // Load top-left item. After loaded, loadItemsInsideRect() will take
3725 // care of filling out the rest of the table.
3729}
3730
3732{
3733 const bool allColumnsLoaded = atTableEnd(Qt::LeftEdge) && atTableEnd(Qt::RightEdge);
3734 if (rebuildOptions.testFlag(RebuildOption::CalculateNewContentWidth) || allColumnsLoaded) {
3737 }
3738
3739 const bool allRowsLoaded = atTableEnd(Qt::TopEdge) && atTableEnd(Qt::BottomEdge);
3740 if (rebuildOptions.testFlag(RebuildOption::CalculateNewContentHeight) || allRowsLoaded) {
3743 }
3744
3745 updateExtents();
3746}
3747
3759
3761{
3762 // Check if we are supposed to position the viewport at a certain column
3764 return;
3765 // The requested column might have been hidden or is outside model bounds
3767 return;
3768
3769 const qreal newContentX = getAlignmentContentX(
3774
3775 setLocalViewportX(newContentX);
3777}
3778
3780{
3781 // Check if we are supposed to position the viewport at a certain row
3783 return;
3784 // The requested row might have been hidden or is outside model bounds
3786 return;
3787
3788 const qreal newContentY = getAlignmentContentY(
3793
3794 setLocalViewportY(newContentY);
3796}
3797
3799{
3800 Q_Q(QQuickTableView);
3801
3802 // Note: we only want to cancel overshoot from a rebuild if we're supposed to position
3803 // the view on a specific cell. The app is allowed to overshoot by setting contentX and
3804 // contentY manually. Also, if this view is a sync child, we should always stay in sync
3805 // with the syncView, so then we don't do anything.
3806 const bool positionVertically = rebuildOptions.testFlag(RebuildOption::PositionViewAtRow);
3807 const bool positionHorizontally = rebuildOptions.testFlag(RebuildOption::PositionViewAtColumn);
3808 const bool cancelVertically = positionVertically && !syncVertically;
3809 const bool cancelHorizontally = positionHorizontally && !syncHorizontally;
3810
3811 if (cancelHorizontally && !qFuzzyIsNull(q->horizontalOvershoot())) {
3812 qCDebug(lcTableViewDelegateLifecycle()) << "cancelling overshoot horizontally:" << q->horizontalOvershoot();
3813 setLocalViewportX(q->horizontalOvershoot() < 0 ? -q->minXExtent() : -q->maxXExtent());
3815 }
3816
3817 if (cancelVertically && !qFuzzyIsNull(q->verticalOvershoot())) {
3818 qCDebug(lcTableViewDelegateLifecycle()) << "cancelling overshoot vertically:" << q->verticalOvershoot();
3819 setLocalViewportY(q->verticalOvershoot() < 0 ? -q->minYExtent() : -q->maxYExtent());
3821 }
3822}
3823
3825{
3826 Q_Q(QQuickTableView);
3827 qCDebug(lcTableViewDelegateLifecycle) << edge;
3828
3829 switch (edge) {
3830 case Qt::LeftEdge: {
3831 const int column = leftColumn();
3832 for (int row : loadedRows)
3837 emit q->leftColumnChanged();
3838 break; }
3839 case Qt::RightEdge: {
3840 const int column = rightColumn();
3841 for (int row : loadedRows)
3846 emit q->rightColumnChanged();
3847 break; }
3848 case Qt::TopEdge: {
3849 const int row = topRow();
3850 for (int col : loadedColumns)
3851 unloadItem(QPoint(col, row));
3855 emit q->topRowChanged();
3856 break; }
3857 case Qt::BottomEdge: {
3858 const int row = bottomRow();
3859 for (int col : loadedColumns)
3860 unloadItem(QPoint(col, row));
3864 emit q->bottomRowChanged();
3865 break; }
3866 }
3867
3869 emit q->layoutChanged();
3870
3871 qCDebug(lcTableViewDelegateLifecycle) << tableLayoutToString();
3872}
3873
3875{
3876 const int edgeIndex = nextVisibleEdgeIndexAroundLoadedTable(edge);
3877 qCDebug(lcTableViewDelegateLifecycle) << edge << edgeIndex << q_func();
3878
3879 const auto &visibleCells = edge & (Qt::LeftEdge | Qt::RightEdge)
3881 loadRequest.begin(edge, edgeIndex, visibleCells, incubationMode);
3883}
3884
3886{
3887 // Unload table edges that have been moved outside the visible part of the
3888 // table (including buffer area), and load new edges that has been moved inside.
3889 // Note: an important point is that we always keep the table rectangular
3890 // and without holes to reduce complexity (we never leave the table in
3891 // a half-loaded state, or keep track of multiple patches).
3892 // We load only one edge (row or column) at a time. This is especially
3893 // important when loading into the buffer, since we need to be able to
3894 // cancel the buffering quickly if the user starts to flick, and then
3895 // focus all further loading on the edges that are flicked into view.
3896
3897 if (loadRequest.isActive()) {
3898 // Don't start loading more edges while we're
3899 // already waiting for another one to load.
3900 return;
3901 }
3902
3903 if (loadedItems.isEmpty()) {
3904 // We need at least the top-left item to be loaded before we can
3905 // start loading edges around it. Not having a top-left item at
3906 // this point means that the model is empty (or no delegate).
3907 return;
3908 }
3909
3910 bool tableModified;
3911
3912 do {
3913 tableModified = false;
3914
3916 tableModified = true;
3917 unloadEdge(edge);
3918 }
3919
3920 if (Qt::Edge edge = nextEdgeToLoad(viewportRect)) {
3921 tableModified = true;
3922 loadEdge(edge, incubationMode);
3923 if (loadRequest.isActive())
3924 return;
3925 }
3926 } while (tableModified);
3927
3928}
3929
3931{
3932 Q_Q(QQuickTableView);
3933
3935 return;
3936
3937 if (!qFuzzyIsNull(q->verticalOvershoot()) || !qFuzzyIsNull(q->horizontalOvershoot())) {
3938 // Don't drain while we're overshooting, since this will fill up the
3939 // pool, but we expect to reuse them all once the content item moves back.
3940 return;
3941 }
3942
3943 // When loading edges, we don't want to drain the reuse pool too aggressively. Normally,
3944 // all the items in the pool are reused rapidly as the content view is flicked around
3945 // anyway. Even if the table is temporarily flicked to a section that contains fewer
3946 // cells than what used to be (e.g if the flicked-in rows are taller than average), it
3947 // still makes sense to keep all the items in circulation; Chances are, that soon enough,
3948 // thinner rows are flicked back in again (meaning that we can fit more items into the
3949 // view). But at the same time, if a delegate chooser is in use, the pool might contain
3950 // items created from different delegates. And some of those delegates might be used only
3951 // occasionally. So to avoid situations where an item ends up in the pool for too long, we
3952 // call drain after each load request, but with a sufficiently large pool time. (If an item
3953 // in the pool has a large pool time, it means that it hasn't been reused for an equal
3954 // amount of load cycles, and should be released).
3955 //
3956 // We calculate an appropriate pool time by figuring out what the minimum time must be to
3957 // not disturb frequently reused items. Since the number of items in a row might be higher
3958 // than in a column (or vice versa), the minimum pool time should take into account that
3959 // you might be flicking out a single row (filling up the pool), before you continue
3960 // flicking in several new columns (taking them out again, but now in smaller chunks). This
3961 // will increase the number of load cycles items are kept in the pool (poolTime), but still,
3962 // we shouldn't release them, as they are still being reused frequently.
3963 // To get a flexible maxValue (that e.g tolerates rows and columns being flicked
3964 // in with varying sizes, causing some items not to be resued immediately), we multiply the
3965 // value by 2. Note that we also add an extra +1 to the column count, because the number of
3966 // visible columns will fluctuate between +1/-1 while flicking.
3967 const int w = loadedColumns.count();
3968 const int h = loadedRows.count();
3969 const int minTime = int(std::ceil(w > h ? qreal(w + 1) / h : qreal(h + 1) / w));
3970 const int maxTime = minTime * 2;
3972}
3973
3975 if (!q_func()->isComponentComplete()) {
3976 // We'll rebuild the table once complete anyway
3977 return;
3978 }
3979
3980 scheduledRebuildOptions |= options;
3981 q_func()->polish();
3982}
3983
3985{
3986 QQuickTableView *root = const_cast<QQuickTableView *>(q_func());
3987 while (QQuickTableView *view = root->d_func()->syncView)
3988 root = view;
3989 return root;
3990}
3991
3993{
3994 // We always start updating from the top of the syncView tree, since
3995 // the layout of a syncView child will depend on the layout of the syncView.
3996 // E.g when a new column is flicked in, the syncView should load and layout
3997 // the column first, before any syncChildren gets a chance to do the same.
3998 Q_TABLEVIEW_ASSERT(!polishing, "recursive updatePolish() calls are not allowed!");
3999 rootSyncView()->d_func()->updateTableRecursive();
4000}
4001
4003{
4004 if (polishing) {
4005 // We're already updating the Table in this view, so
4006 // we cannot continue. Signal this back by returning false.
4007 // The caller can then choose to call "polish()" instead, to
4008 // do the update later.
4009 return false;
4010 }
4011
4012 const bool updateComplete = updateTable();
4013 if (!updateComplete)
4014 return false;
4015
4016 const auto children = syncChildren;
4017 for (auto syncChild : children) {
4018 auto syncChild_d = syncChild->d_func();
4019 const int mask =
4024 syncChild_d->scheduledRebuildOptions |= rebuildOptions & ~mask;
4025
4026 const bool descendantUpdateComplete = syncChild_d->updateTableRecursive();
4027 if (!descendantUpdateComplete)
4028 return false;
4029 }
4030
4032
4033 return true;
4034}
4035
4037{
4038 // Whenever something changes, e.g viewport moves, spacing is set to a
4039 // new value, model changes etc, this function will end up being called. Here
4040 // we check what needs to be done, and load/unload cells accordingly.
4041 // If we cannot complete the update (because we need to wait for an item
4042 // to load async), we return false.
4043
4044 Q_TABLEVIEW_ASSERT(!polishing, "recursive updatePolish() calls are not allowed!");
4045 QBoolBlocker polishGuard(polishing, true);
4046
4047 if (loadRequest.isActive()) {
4048 // We're currently loading items async to build a new edge in the table. We see the loading
4049 // as an atomic operation, which means that we don't continue doing anything else until all
4050 // items have been received and laid out. Note that updatePolish is then called once more
4051 // after the loadRequest has completed to handle anything that might have occurred in-between.
4052 return false;
4053 }
4054
4058 }
4059
4061
4065 }
4066
4067 if (loadedItems.isEmpty())
4068 return !loadRequest.isActive();
4069
4072
4073 return !loadRequest.isActive();
4074}
4075
4077{
4078 if (inUpdateContentSize) {
4079 // We update the content size dynamically as we load and unload edges.
4080 // Unfortunately, this also triggers a call to this function. The base
4081 // implementation will do things like start a momentum animation or move
4082 // the content view somewhere else, which causes glitches. This can
4083 // especially happen if flicking on one of the syncView children, which triggers
4084 // an update to our content size. In that case, the base implementation don't know
4085 // that the view is being indirectly dragged, and will therefore do strange things as
4086 // it tries to 'fixup' the geometry. So we use a guard to prevent this from happening.
4087 return;
4088 }
4089
4090 QQuickFlickablePrivate::fixup(data, minExtent, maxExtent);
4091}
4092
4094{
4095 const auto data = QQmlData::get(q_func());
4096 if (!data || !data->propertyCache)
4097 return QTypeRevision::zero();
4098
4099 const auto cppMetaObject = data->propertyCache->firstCppMetaObject();
4100 const auto qmlTypeView = QQmlMetaType::qmlType(cppMetaObject);
4101
4102 // TODO: did we rather want qmlTypeView.revision() here?
4103 return qmlTypeView.metaObjectRevision();
4104}
4105
4107{
4108 Q_Q(QQuickTableView);
4109 // When the assigned model is not an instance model, we create a wrapper
4110 // model (QQmlTableInstanceModel) that keeps a pointer to both the
4111 // assigned model and the assigned delegate. This model will give us a
4112 // common interface to any kind of model (js arrays, QAIM, number etc), and
4113 // help us create delegate instances.
4116 model = tableModel;
4117}
4118
4120{
4121 if (!selectionModel)
4122 return false;
4123
4125 if (!model)
4126 return false;
4127
4128 return selectionModel->isSelected(q_func()->modelIndex(cell));
4129}
4130
4132{
4133 if (!selectionModel)
4134 return false;
4135
4137 if (!model)
4138 return false;
4139
4140 return selectionModel->currentIndex() == q_func()->modelIndex(cell);
4141}
4142
4144{
4146 // The selection model was manipulated outside of TableView
4147 // and SelectionRectangle. In that case we cancel any ongoing
4148 // selection tracking.
4150 }
4151
4152 const auto &selectedIndexes = selected.indexes();
4153 const auto &deselectedIndexes = deselected.indexes();
4154 for (int i = 0; i < selectedIndexes.size(); ++i)
4155 setSelectedOnDelegateItem(selectedIndexes.at(i), true);
4156 for (int i = 0; i < deselectedIndexes.size(); ++i)
4157 setSelectedOnDelegateItem(deselectedIndexes.at(i), false);
4158}
4159
4161{
4162 if (modelIndex.isValid() && modelIndex.model() != selectionSourceModel()) {
4163 qmlWarning(q_func())
4164 << "Cannot select cells: TableView.selectionModel.model is not "
4165 << "compatible with the model displayed in the view";
4166 return;
4167 }
4168
4169 const int cellIndex = modelIndexToCellIndex(modelIndex);
4170 if (!loadedItems.contains(cellIndex))
4171 return;
4172 const QPoint cell = cellAtModelIndex(cellIndex);
4175}
4176
4178{
4179 // TableView.selectionModel.model should always be the same as TableView.model.
4180 // After all, when the user selects an index in the view, the same index should
4181 // be selected in the selection model. We therefore set the model in
4182 // selectionModel.model automatically.
4183 // But it's not always the case that the model shown in the view is the same
4184 // as TableView.model. Subclasses with a proxy model will instead show the
4185 // proxy model (e.g TreeView and HeaderView). And then it's no longer clear if
4186 // we should use the proxy model or the TableView.model as source model in
4187 // TableView.selectionModel. It's up to the subclass. But in short, if the proxy
4188 // model shares the same model items as TableView.model (just with e.g a filter
4189 // applied, or sorted etc), then TableView.model should be used. If the proxy
4190 // model is a completely different model that shares no model items with
4191 // TableView.model, then the proxy model should be used (e.g HeaderView).
4192 return qaim(modelImpl());
4193}
4194
4196{
4197 // If modelAsVariant wraps a qaim, return it
4198 if (modelAsVariant.userType() == qMetaTypeId<QJSValue>())
4199 modelAsVariant = modelAsVariant.value<QJSValue>().toVariant();
4200 return qvariant_cast<QAbstractItemModel *>(modelAsVariant);
4201}
4202
4204{
4206
4207 for (auto it = loadedItems.keyBegin(), end = loadedItems.keyEnd(); it != end; ++it) {
4208 const int cellIndex = *it;
4209 const QPoint cell = cellAtModelIndex(cellIndex);
4210 const bool selected = selectedInSelectionModel(cell);
4211 const bool current = currentInSelectionModel(cell);
4213 const bool editing = editIndex == q_func()->modelIndex(cell);
4217 }
4218}
4219
4221{
4222 if (current.isValid() && current.model() != selectionSourceModel()) {
4223 qmlWarning(q_func())
4224 << "Cannot change current index: TableView.selectionModel.model is not "
4225 << "compatible with the model displayed in the view";
4226 return;
4227 }
4228
4230 setCurrentOnDelegateItem(previous, false);
4231 setCurrentOnDelegateItem(current, true);
4232}
4233
4235{
4236 Q_Q(QQuickTableView);
4237
4239 const QPoint currentCell = q->cellAtIndex(currentIndex);
4240 if (currentCell.x() != currentColumn) {
4241 currentColumn = currentCell.x();
4242 emit q->currentColumnChanged();
4243 }
4244
4245 if (currentCell.y() != currentRow) {
4246 currentRow = currentCell.y();
4247 emit q->currentRowChanged();
4248 }
4249}
4250
4252{
4253 const int cellIndex = modelIndexToCellIndex(index);
4254 if (!loadedItems.contains(cellIndex))
4255 return;
4256
4257 const QPoint cell = cellAtModelIndex(cellIndex);
4260}
4261
4263{
4265 return;
4266
4267 qCDebug(lcTableViewDelegateLifecycle) << "item done loading:"
4268 << cellAtModelIndex(modelIndex);
4269
4270 // Since the item we waited for has finished incubating, we can
4271 // continue with the load request. processLoadRequest will
4272 // ask the model for the requested item once more, which will be
4273 // quick since the model has cached it.
4276 updatePolish();
4277}
4278
4280{
4281 Q_Q(QQuickTableView);
4282
4283 auto item = qobject_cast<QQuickItem*>(object);
4284 if (!item)
4285 return;
4286
4287 item->setParentItem(q->contentItem());
4288 item->setZ(1);
4289
4290 const QPoint cell = cellAtModelIndex(modelIndex);
4291 const bool current = currentInSelectionModel(cell);
4292 const bool selected = selectedInSelectionModel(cell);
4293 setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(current), modelIndex, object, true);
4294 setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(selected), modelIndex, object, true);
4296
4297 if (auto attached = getAttachedObject(object))
4298 attached->setView(q);
4299}
4300
4302{
4303 Q_UNUSED(modelIndex);
4304
4305 if (auto attached = getAttachedObject(object))
4306 emit attached->pooled();
4307}
4308
4310{
4311 const QPoint cell = cellAtModelIndex(modelIndex);
4312 const bool current = currentInSelectionModel(cell);
4313 const bool selected = selectedInSelectionModel(cell);
4314 setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(current), modelIndex, object, false);
4315 setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(selected), modelIndex, object, false);
4316 // Note: the edit item will never be reused, so no reason to set kRequiredProperty_editing
4317
4318 if (auto item = qobject_cast<QQuickItem*>(object))
4319 QQuickItemPrivate::get(item)->setCulled(false);
4320
4321 if (auto attached = getAttachedObject(object))
4322 emit attached->reused();
4323}
4324
4326{
4327 // The application can change properties like the model or the delegate while
4328 // we're e.g in the middle of e.g loading a new row. Since this will lead to
4329 // unpredicted behavior, and possibly a crash, we need to postpone taking
4330 // such assignments into effect until we're in a state that allows it.
4331
4333 syncModel();
4334 syncDelegate();
4335 syncSyncView();
4337
4339}
4340
4369
4371{
4372 if (!tableModel) {
4373 // Only the tableModel uses the delegate assigned to a
4374 // TableView. DelegateModel has it's own delegate, and
4375 // ObjectModel etc. doesn't use one.
4376 return;
4377 }
4378
4381}
4382
4387
4389{
4390 assignedModel = newModel;
4392 emit q_func()->modelChanged();
4393}
4394
4396{
4398 return;
4399
4400 if (model) {
4403 }
4404
4406 QVariant effectiveModelVariant = modelVariant;
4407 if (effectiveModelVariant.userType() == qMetaTypeId<QJSValue>())
4408 effectiveModelVariant = effectiveModelVariant.value<QJSValue>().toVariant();
4409
4410 const auto instanceModel = qobject_cast<QQmlInstanceModel *>(qvariant_cast<QObject*>(effectiveModelVariant));
4411
4412 if (instanceModel) {
4413 if (tableModel) {
4414 delete tableModel;
4415 tableModel = nullptr;
4416 }
4417 model = instanceModel;
4418 } else {
4419 if (!tableModel)
4421 tableModel->setModel(effectiveModelVariant);
4422 }
4423
4425}
4426
4428{
4429 Q_Q(QQuickTableView);
4430
4431 if (assignedSyncView != syncView) {
4432 if (syncView)
4433 syncView->d_func()->syncChildren.removeOne(q);
4434
4435 if (assignedSyncView) {
4437
4438 while (view) {
4439 if (view == q) {
4440 if (!layoutWarningIssued) {
4441 layoutWarningIssued = true;
4442 qmlWarning(q) << "TableView: recursive syncView connection detected!";
4443 }
4444 syncView = nullptr;
4445 return;
4446 }
4447 view = view->d_func()->syncView;
4448 }
4449
4450 assignedSyncView->d_func()->syncChildren.append(q);
4452 }
4453
4455 }
4456
4459
4460 if (syncHorizontally) {
4461 QBoolBlocker fixupGuard(inUpdateContentSize, true);
4462 q->setColumnSpacing(syncView->columnSpacing());
4463 q->setLeftMargin(syncView->leftMargin());
4464 q->setRightMargin(syncView->rightMargin());
4466
4467 if (syncView->leftColumn() != q->leftColumn()) {
4468 // The left column is no longer the same as the left
4469 // column in syncView. This requires a rebuild.
4472 }
4473 }
4474
4475 if (syncVertically) {
4476 QBoolBlocker fixupGuard(inUpdateContentSize, true);
4477 q->setRowSpacing(syncView->rowSpacing());
4478 q->setTopMargin(syncView->topMargin());
4479 q->setBottomMargin(syncView->bottomMargin());
4481
4482 if (syncView->topRow() != q->topRow()) {
4483 // The top row is no longer the same as the top
4484 // row in syncView. This requires a rebuild.
4487 }
4488 }
4489
4490 if (syncView && loadedItems.isEmpty() && !tableSize.isEmpty()) {
4491 // When we have a syncView, we can sometimes temporarily end up with no loaded items.
4492 // This can happen if the syncView has a model with more rows or columns than us, in
4493 // which case the viewport can end up in a place where we have no rows or columns to
4494 // show. In that case, check now if the viewport has been flicked back again, and
4495 // that we can rebuild the table with a visible top-left cell.
4496 const auto syncView_d = syncView->d_func();
4497 if (!syncView_d->loadedItems.isEmpty()) {
4498 if (syncHorizontally && syncView_d->leftColumn() <= tableSize.width() - 1)
4500 else if (syncVertically && syncView_d->topRow() <= tableSize.height() - 1)
4502 }
4503 }
4504}
4505
4507{
4508 // Only positionViewAtRowAfterRebuild/positionViewAtColumnAfterRebuild are critical
4509 // to sync before a rebuild to avoid them being overwritten
4510 // by the setters while building. The other position properties
4511 // can change without it causing trouble.
4514}
4515
4517{
4518 Q_Q(QQuickTableView);
4520
4523 QObjectPrivate::connect(model, &QQmlTableInstanceModel::itemPooled, this, &QQuickTableViewPrivate::itemPooledCallback);
4524 QObjectPrivate::connect(model, &QQmlTableInstanceModel::itemReused, this, &QQuickTableViewPrivate::itemReusedCallback);
4525
4526 // Connect atYEndChanged to a function that fetches data if more is available
4528
4529 if (auto const aim = model->abstractItemModel()) {
4530 // When the model exposes a QAIM, we connect to it directly. This means that if the current model is
4531 // a QQmlDelegateModel, we just ignore all the change sets it emits. In most cases, the model will instead
4532 // be our own QQmlTableInstanceModel, which doesn't bother creating change sets at all. For models that are
4533 // not based on QAIM (like QQmlObjectModel, QQmlListModel, javascript arrays etc), there is currently no way
4534 // to modify the model at runtime without also re-setting the model on the view.
4543 } else {
4545 }
4546}
4547
4549{
4550 Q_Q(QQuickTableView);
4552
4555 QObjectPrivate::disconnect(model, &QQmlTableInstanceModel::itemPooled, this, &QQuickTableViewPrivate::itemPooledCallback);
4556 QObjectPrivate::disconnect(model, &QQmlTableInstanceModel::itemReused, this, &QQuickTableViewPrivate::itemReusedCallback);
4557
4559
4560 if (auto const aim = model->abstractItemModel()) {
4569 } else {
4571 }
4572}
4573
4584
4585void QQuickTableViewPrivate::rowsMovedCallback(const QModelIndex &parent, int, int, const QModelIndex &, int )
4586{
4587 if (parent != QModelIndex())
4588 return;
4589
4591}
4592
4594{
4595 if (parent != QModelIndex())
4596 return;
4597
4599}
4600
4608
4610{
4611 Q_Q(QQuickTableView);
4612
4613 if (parent != QModelIndex())
4614 return;
4615
4616 // If editIndex was a part of the removed rows, it will now be invalid.
4617 if (!editIndex.isValid() && editItem)
4618 q->closeEditor();
4619
4621}
4622
4624{
4625 if (parent != QModelIndex())
4626 return;
4627
4628 // Adding a column (or row) can result in the table going from being
4629 // e.g completely inside the viewport to go outside. And in the latter
4630 // case, the user needs to be able to scroll the viewport, also if
4631 // flags such as Flickable.StopAtBounds is in use. So we need to
4632 // update contentWidth to support that case.
4634}
4635
4637{
4638 Q_Q(QQuickTableView);
4639
4640 if (parent != QModelIndex())
4641 return;
4642
4643 // If editIndex was a part of the removed columns, it will now be invalid.
4644 if (!editIndex.isValid() && editItem)
4645 q->closeEditor();
4646
4648}
4649
4651{
4652 Q_UNUSED(parents);
4653 Q_UNUSED(hint);
4654
4656}
4657
4665
4672
4673bool QQuickTableViewPrivate::compareModel(const QVariant& model1, const QVariant& model2) const
4674{
4675 return (model1 == model2 ||
4676 (model1.userType() == qMetaTypeId<QJSValue>() && model2.userType() == qMetaTypeId<QJSValue>() &&
4677 model1.value<QJSValue>().strictlyEquals(model2.value<QJSValue>())));
4678}
4679
4681{
4682 Qt::Alignment verticalAlignment = alignment & (Qt::AlignTop | Qt::AlignVCenter | Qt::AlignBottom);
4683 Q_TABLEVIEW_ASSERT(verticalAlignment, alignment);
4684
4685 if (syncHorizontally) {
4686 syncView->d_func()->positionViewAtRow(row, verticalAlignment, offset, subRect);
4687 } else {
4688 if (!scrollToRow(row, verticalAlignment, offset, subRect)) {
4689 // Could not scroll, so rebuild instead
4691 positionViewAtRowAlignment = verticalAlignment;
4693 positionViewAtRowSubRect = subRect;
4696 }
4697 }
4698}
4699
4701{
4702 Qt::Alignment horizontalAlignment = alignment & (Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight);
4703 Q_TABLEVIEW_ASSERT(horizontalAlignment, alignment);
4704
4705 if (syncVertically) {
4706 syncView->d_func()->positionViewAtColumn(column, horizontalAlignment, offset, subRect);
4707 } else {
4708 if (!scrollToColumn(column, horizontalAlignment, offset, subRect)) {
4709 // Could not scroll, so rebuild instead
4711 positionViewAtColumnAlignment = horizontalAlignment;
4716 }
4717 }
4718}
4719
4720bool QQuickTableViewPrivate::scrollToRow(int row, Qt::Alignment alignment, qreal offset, const QRectF subRect)
4721{
4722 Q_Q(QQuickTableView);
4723
4724 // This function will only scroll to rows that are loaded (since we
4725 // don't know the location of unloaded rows). But as an exception, to
4726 // allow moving currentIndex out of the viewport, we support scrolling
4727 // to a row that is adjacent to the loaded table. So start by checking
4728 // if we should load en extra row.
4729 if (row < topRow()) {
4731 return false;
4733 } else if (row > bottomRow()) {
4735 return false;
4737 } else if (row < topRow() || row > bottomRow()) {
4738 return false;
4739 }
4740
4741 if (!loadedRows.contains(row))
4742 return false;
4743
4744 const qreal newContentY = getAlignmentContentY(row, alignment, offset, subRect);
4745 if (qFuzzyCompare(newContentY, q->contentY()))
4746 return true;
4747
4748 if (animate) {
4749 const qreal diffY = qAbs(newContentY - q->contentY());
4750 const qreal duration = qBound(700., diffY * 5, 1500.);
4751 positionYAnimation.setTo(newContentY);
4754 } else {
4756 q->setContentY(newContentY);
4757 }
4758
4759 return true;
4760}
4761
4763{
4764 Q_Q(QQuickTableView);
4765
4766 // This function will only scroll to columns that are loaded (since we
4767 // don't know the location of unloaded columns). But as an exception, to
4768 // allow moving currentIndex out of the viewport, we support scrolling
4769 // to a column that is adjacent to the loaded table. So start by checking
4770 // if we should load en extra column.
4771 if (column < leftColumn()) {
4773 return false;
4775 } else if (column > rightColumn()) {
4777 return false;
4779 } else if (column < leftColumn() || column > rightColumn()) {
4780 return false;
4781 }
4782
4784 return false;
4785
4786 const qreal newContentX = getAlignmentContentX(column, alignment, offset, subRect);
4787 if (qFuzzyCompare(newContentX, q->contentX()))
4788 return true;
4789
4790 if (animate) {
4791 const qreal diffX = qAbs(newContentX - q->contentX());
4792 const qreal duration = qBound(700., diffX * 5, 1500.);
4793 positionXAnimation.setTo(newContentX);
4796 } else {
4798 q->setContentX(newContentX);
4799 }
4800
4801 return true;
4802}
4803
4805{
4806 Q_Q(QQuickTableView);
4807 // If the viewport has moved more than one page vertically or horizontally, we switch
4808 // strategy from refilling edges around the current table to instead rebuild the table
4809 // from scratch inside the new viewport. This will greatly improve performance when flicking
4810 // a long distance in one go, which can easily happen when dragging on scrollbars.
4811 // Note that we don't want to update the content size in this case, since first of all, the
4812 // content size should logically not change as a result of flicking. But more importantly, updating
4813 // the content size in combination with fast-flicking has a tendency to cause flicker in the viewport.
4814
4815 // Check the viewport moved more than one page vertically
4816 if (!viewportRect.intersects(QRectF(viewportRect.x(), q->contentY(), 1, q->height()))) {
4819 }
4820
4821 // Check the viewport moved more than one page horizontally
4822 if (!viewportRect.intersects(QRectF(q->contentX(), viewportRect.y(), q->width(), 1))) {
4825 }
4826}
4827
4829{
4830 // Set the new viewport position if changed, but don't trigger any
4831 // rebuilds or updates. We use this function internally to distinguish
4832 // external flicking from internal sync-ing of the content view.
4833 Q_Q(QQuickTableView);
4834 QBoolBlocker blocker(inSetLocalViewportPos, true);
4835
4836 if (qFuzzyCompare(contentX, q->contentX()))
4837 return;
4838
4839 q->setContentX(contentX);
4840}
4841
4843{
4844 // Set the new viewport position if changed, but don't trigger any
4845 // rebuilds or updates. We use this function internally to distinguish
4846 // external flicking from internal sync-ing of the content view.
4847 Q_Q(QQuickTableView);
4848 QBoolBlocker blocker(inSetLocalViewportPos, true);
4849
4850 if (qFuzzyCompare(contentY, q->contentY()))
4851 return;
4852
4853 q->setContentY(contentY);
4854}
4855
4857{
4858 // Sync viewportRect so that it contains the actual geometry of the viewport.
4859 // Since the column (and row) size of a sync child is decided by the column size
4860 // of its sync view, the viewport width of a sync view needs to be the maximum of
4861 // the sync views width, and its sync childrens width. This to ensure that no sync
4862 // child loads a column which is not yet loaded by the sync view, since then the
4863 // implicit column size cannot be resolved.
4864 Q_Q(QQuickTableView);
4865
4866 qreal w = q->width();
4867 qreal h = q->height();
4868
4869 for (auto syncChild : std::as_const(syncChildren)) {
4870 auto syncChild_d = syncChild->d_func();
4871 if (syncChild_d->syncHorizontally)
4872 w = qMax(w, syncChild->width());
4873 if (syncChild_d->syncHorizontally)
4874 h = qMax(h, syncChild->height());
4875 }
4876
4877 viewportRect = QRectF(q->contentX(), q->contentY(), w, h);
4878}
4879
4881{
4882 Q_Q(QQuickTableView);
4883
4885 q->setActiveFocusOnTab(true);
4886
4890
4894
4895 auto tapHandler = new QQuickTableViewTapHandler(q);
4896
4901
4902 // To allow for a more snappy UX, we try to change the current index already upon
4903 // receiving a pointer press. But we should only do that if the view is not interactive
4904 // (so that it doesn't interfere with flicking), and if the resizeHandler is not
4905 // being hovered/dragged. For those cases, we fall back to setting the current index
4906 // on tap instead. A double tap on a resize area should also revert the section size
4907 // back to its implicit size.
4908 QObject::connect(tapHandler, &QQuickTapHandler::pressedChanged, [this, q, tapHandler] {
4909 if (!tapHandler->isPressed())
4910 return;
4911
4914
4915 if (!q->isInteractive())
4916 handleTap(tapHandler->point());
4917 });
4918
4919 QObject::connect(tapHandler, &QQuickTapHandler::singleTapped, [this, q, tapHandler] {
4920 if (q->isInteractive())
4921 handleTap(tapHandler->point());
4922 });
4923
4924 QObject::connect(tapHandler, &QQuickTapHandler::doubleTapped, [this, q, tapHandler] {
4925 const bool resizeRow = resizableRows && hoverHandler->m_row != -1;
4926 const bool resizeColumn = resizableColumns && hoverHandler->m_column != -1;
4927
4928 if (resizeRow || resizeColumn) {
4929 if (resizeRow)
4930 q->setRowHeight(hoverHandler->m_row, -1);
4931 if (resizeColumn)
4932 q->setColumnWidth(hoverHandler->m_column, -1);
4934 const QPointF pos = tapHandler->point().pressPosition();
4935 const QPoint cell = q->cellAtPosition(pos);
4936 const QModelIndex index = q->modelIndex(cell);
4937 if (canEdit(index, false))
4938 q->edit(index);
4939 }
4940 });
4941}
4942
4944{
4945 Q_Q(QQuickTableView);
4946
4948 q->forceActiveFocus(Qt::MouseFocusReason);
4949
4950 if (point.modifiers() != Qt::NoModifier)
4951 return;
4952 if (resizableRows && hoverHandler->m_row != -1)
4953 return;
4955 return;
4957 return;
4958
4959 const QModelIndex tappedIndex = q->modelIndex(q->cellAtPosition(point.position()));
4960 bool tappedCellIsSelected = false;
4961
4962 if (selectionModel)
4963 tappedCellIsSelected = selectionModel->isSelected(tappedIndex);
4964
4965 if (canEdit(tappedIndex, false)) {
4969 q->edit(tappedIndex);
4970 return;
4971 } else if (editTriggers & QQuickTableView::SelectedTapped && tappedCellIsSelected) {
4972 q->edit(tappedIndex);
4973 return;
4974 }
4975 }
4976
4977 // Since the tap didn't result in selecting or editing cells, we clear
4978 // the current selection and move the current index instead.
4980 q->closeEditor();
4984 }
4986 }
4987}
4988
4989bool QQuickTableViewPrivate::canEdit(const QModelIndex tappedIndex, bool warn)
4990{
4991 // Check that a call to edit(tappedIndex) would not
4992 // result in warnings being printed.
4993 Q_Q(QQuickTableView);
4994
4995 if (!tappedIndex.isValid()) {
4996 if (warn)
4997 qmlWarning(q) << "cannot edit: index is not valid!";
4998 return false;
4999 }
5000
5001 if (auto const qaim = model->abstractItemModel()) {
5002 if (!(qaim->flags(tappedIndex) & Qt::ItemIsEditable)) {
5003 if (warn)
5004 qmlWarning(q) << "cannot edit: QAbstractItemModel::flags(index) doesn't contain Qt::ItemIsEditable";
5005 return false;
5006 }
5007 }
5008
5009 const QPoint cell = q->cellAtIndex(tappedIndex);
5010 const QQuickItem *cellItem = q->itemAtCell(cell);
5011 if (!cellItem) {
5012 if (warn)
5013 qmlWarning(q) << "cannot edit: the cell to edit is not inside the viewport!";
5014 return false;
5015 }
5016
5017 auto attached = getAttachedObject(cellItem);
5018 if (!attached || !attached->editDelegate()) {
5019 if (warn)
5020 qmlWarning(q) << "cannot edit: no TableView.editDelegate set!";
5021 return false;
5022 }
5023
5024 return true;
5025}
5026
5028{
5029 Q_Q(QQuickTableView);
5030 QBoolBlocker recursionGuard(inSyncViewportPosRecursive, true);
5031
5032 if (syncView) {
5033 auto syncView_d = syncView->d_func();
5034 if (!syncView_d->inSyncViewportPosRecursive) {
5035 if (syncHorizontally)
5036 syncView_d->setLocalViewportX(q->contentX());
5037 if (syncVertically)
5038 syncView_d->setLocalViewportY(q->contentY());
5039 syncView_d->syncViewportPosRecursive();
5040 }
5041 }
5042
5043 for (auto syncChild : std::as_const(syncChildren)) {
5044 auto syncChild_d = syncChild->d_func();
5045 if (!syncChild_d->inSyncViewportPosRecursive) {
5046 if (syncChild_d->syncHorizontally)
5047 syncChild_d->setLocalViewportX(q->contentX());
5048 if (syncChild_d->syncVertically)
5049 syncChild_d->setLocalViewportY(q->contentY());
5050 syncChild_d->syncViewportPosRecursive();
5051 }
5052 }
5053}
5054
5056{
5057 Q_Q(QQuickTableView);
5058
5059 const QPoint cell = q->cellAtPosition(pos);
5060 if (!cellIsValid(cell))
5061 return;
5062
5063 setCurrentIndex(cell);
5064}
5065
5067{
5068 if (!selectionModel)
5069 return;
5070
5071 const auto index = q_func()->modelIndex(cell);
5073}
5074
5076{
5077 Q_Q(QQuickTableView);
5078
5080 return false;
5081
5082 const QModelIndex currentIndex = selectionModel->currentIndex();
5083 const QPoint currentCell = q->cellAtIndex(currentIndex);
5084
5085 if (!q->activeFocusOnTab()) {
5086 switch (e->key()) {
5087 case Qt::Key_Tab:
5088 case Qt::Key_Backtab:
5089 return false;
5090 }
5091 }
5092
5093 if (!cellIsValid(currentCell)) {
5094 switch (e->key()) {
5095 case Qt::Key_Up:
5096 case Qt::Key_Down:
5097 case Qt::Key_Left:
5098 case Qt::Key_Right:
5099 case Qt::Key_PageUp:
5100 case Qt::Key_PageDown:
5101 case Qt::Key_Home:
5102 case Qt::Key_End:
5103 case Qt::Key_Tab:
5104 case Qt::Key_Backtab:
5105 // Special case: the current index doesn't map to a cell in the view (perhaps
5106 // because it isn't set yet). In that case, we set it to be the top-left cell.
5107 const QModelIndex topLeftIndex = q->index(topRow(), leftColumn());
5109 return true;
5110 }
5111 return false;
5112 }
5113
5114 auto beginMoveCurrentIndex = [&](){
5115 const bool shouldSelect = (e->modifiers() & Qt::ShiftModifier) && (e->key() != Qt::Key_Backtab);
5116 const bool startNewSelection = selectionRectangle().isEmpty();
5117 if (!shouldSelect) {
5120 } else if (startNewSelection) {
5121 // Try to start a new selection if no selection exists from before.
5122 // The startSelection() call is theoretically allowed to refuse, although this
5123 // is less likely when starting a selection using the keyboard.
5124 const int serializedStartIndex = modelIndexToCellIndex(selectionModel->currentIndex());
5125 if (loadedItems.contains(serializedStartIndex)) {
5126 const QRectF startGeometry = loadedItems.value(serializedStartIndex)->geometry();
5127 if (startSelection(startGeometry.center(), Qt::ShiftModifier)) {
5128 setSelectionStartPos(startGeometry.center());
5131 }
5132 }
5133 }
5134 };
5135
5136 auto endMoveCurrentIndex = [&](const QPoint &cell){
5137 const bool isSelecting = selectionFlag != QItemSelectionModel::NoUpdate;
5138 if (isSelecting) {
5139 if (polishScheduled)
5140 forceLayout(true);
5141 const int serializedEndIndex = modelIndexAtCell(cell);
5142 if (loadedItems.contains(serializedEndIndex)) {
5143 const QRectF endGeometry = loadedItems.value(serializedEndIndex)->geometry();
5144 setSelectionEndPos(endGeometry.center());
5147 }
5148 }
5150 };
5151
5152 switch (e->key()) {
5153 case Qt::Key_Up: {
5154 beginMoveCurrentIndex();
5155 const int nextRow = nextVisibleEdgeIndex(Qt::TopEdge, currentCell.y() - 1);
5156 if (nextRow == kEdgeIndexAtEnd)
5157 break;
5158 const qreal marginY = atTableEnd(Qt::TopEdge, nextRow - 1) ? -q->topMargin() : 0;
5159 q->positionViewAtRow(nextRow, QQuickTableView::Contain, marginY);
5160 endMoveCurrentIndex({currentCell.x(), nextRow});
5161 break; }
5162 case Qt::Key_Down: {
5163 beginMoveCurrentIndex();
5164 const int nextRow = nextVisibleEdgeIndex(Qt::BottomEdge, currentCell.y() + 1);
5165 if (nextRow == kEdgeIndexAtEnd)
5166 break;
5167 const qreal marginY = atTableEnd(Qt::BottomEdge, nextRow + 1) ? q->bottomMargin() : 0;
5168 q->positionViewAtRow(nextRow, QQuickTableView::Contain, marginY);
5169 endMoveCurrentIndex({currentCell.x(), nextRow});
5170 break; }
5171 case Qt::Key_Left: {
5172 beginMoveCurrentIndex();
5173 const int nextColumn = nextVisibleEdgeIndex(Qt::LeftEdge, currentCell.x() - 1);
5174 if (nextColumn == kEdgeIndexAtEnd)
5175 break;
5176 const qreal marginX = atTableEnd(Qt::LeftEdge, nextColumn - 1) ? -q->leftMargin() : 0;
5177 q->positionViewAtColumn(nextColumn, QQuickTableView::Contain, marginX);
5178 endMoveCurrentIndex({nextColumn, currentCell.y()});
5179 break; }
5180 case Qt::Key_Right: {
5181 beginMoveCurrentIndex();
5182 const int nextColumn = nextVisibleEdgeIndex(Qt::RightEdge, currentCell.x() + 1);
5183 if (nextColumn == kEdgeIndexAtEnd)
5184 break;
5185 const qreal marginX = atTableEnd(Qt::RightEdge, nextColumn + 1) ? q->rightMargin() : 0;
5186 q->positionViewAtColumn(nextColumn, QQuickTableView::Contain, marginX);
5187 endMoveCurrentIndex({nextColumn, currentCell.y()});
5188 break; }
5189 case Qt::Key_PageDown: {
5190 int newBottomRow = -1;
5191 beginMoveCurrentIndex();
5192 if (currentCell.y() < bottomRow()) {
5193 // The first PageDown should just move currentIndex to the bottom
5194 newBottomRow = bottomRow();
5195 q->positionViewAtRow(newBottomRow, QQuickTableView::AlignBottom, 0);
5196 } else {
5197 q->positionViewAtRow(bottomRow(), QQuickTableView::AlignTop, 0);
5199 newBottomRow = topRow() != bottomRow() ? bottomRow() : bottomRow() + 1;
5200 const qreal marginY = atTableEnd(Qt::BottomEdge, newBottomRow + 1) ? q->bottomMargin() : 0;
5201 q->positionViewAtRow(newBottomRow, QQuickTableView::AlignTop | QQuickTableView::AlignBottom, marginY);
5203 }
5204 endMoveCurrentIndex(QPoint(currentCell.x(), newBottomRow));
5205 break; }
5206 case Qt::Key_PageUp: {
5207 int newTopRow = -1;
5208 beginMoveCurrentIndex();
5209 if (currentCell.y() > topRow()) {
5210 // The first PageUp should just move currentIndex to the top
5211 newTopRow = topRow();
5212 q->positionViewAtRow(newTopRow, QQuickTableView::AlignTop, 0);
5213 } else {
5214 q->positionViewAtRow(topRow(), QQuickTableView::AlignBottom, 0);
5216 newTopRow = topRow() != bottomRow() ? topRow() : topRow() - 1;
5217 const qreal marginY = atTableEnd(Qt::TopEdge, newTopRow - 1) ? -q->topMargin() : 0;
5218 q->positionViewAtRow(newTopRow, QQuickTableView::AlignTop, marginY);
5220 }
5221 endMoveCurrentIndex(QPoint(currentCell.x(), newTopRow));
5222 break; }
5223 case Qt::Key_Home: {
5224 beginMoveCurrentIndex();
5225 const int firstColumn = nextVisibleEdgeIndex(Qt::RightEdge, 0);
5226 q->positionViewAtColumn(firstColumn, QQuickTableView::AlignLeft, -q->leftMargin());
5227 endMoveCurrentIndex(QPoint(firstColumn, currentCell.y()));
5228 break; }
5229 case Qt::Key_End: {
5230 beginMoveCurrentIndex();
5231 const int lastColumn = nextVisibleEdgeIndex(Qt::LeftEdge, tableSize.width() - 1);
5232 q->positionViewAtColumn(lastColumn, QQuickTableView::AlignRight, q->rightMargin());
5233 endMoveCurrentIndex(QPoint(lastColumn, currentCell.y()));
5234 break; }
5235 case Qt::Key_Tab: {
5236 beginMoveCurrentIndex();
5237 int nextRow = currentCell.y();
5238 int nextColumn = nextVisibleEdgeIndex(Qt::RightEdge, currentCell.x() + 1);
5239 if (nextColumn == kEdgeIndexAtEnd) {
5240 nextRow = nextVisibleEdgeIndex(Qt::BottomEdge, currentCell.y() + 1);
5241 if (nextRow == kEdgeIndexAtEnd)
5243 nextColumn = nextVisibleEdgeIndex(Qt::RightEdge, 0);
5244 const qreal marginY = atTableEnd(Qt::BottomEdge, nextRow + 1) ? q->bottomMargin() : 0;
5245 q->positionViewAtRow(nextRow, QQuickTableView::Contain, marginY);
5246 }
5247
5248 qreal marginX = 0;
5249 if (atTableEnd(Qt::RightEdge, nextColumn + 1))
5250 marginX = q->leftMargin();
5251 else if (atTableEnd(Qt::LeftEdge, nextColumn - 1))
5252 marginX = -q->leftMargin();
5253
5254 q->positionViewAtColumn(nextColumn, QQuickTableView::Contain, marginX);
5255 endMoveCurrentIndex({nextColumn, nextRow});
5256 break; }
5257 case Qt::Key_Backtab: {
5258 beginMoveCurrentIndex();
5259 int nextRow = currentCell.y();
5260 int nextColumn = nextVisibleEdgeIndex(Qt::LeftEdge, currentCell.x() - 1);
5261 if (nextColumn == kEdgeIndexAtEnd) {
5262 nextRow = nextVisibleEdgeIndex(Qt::TopEdge, currentCell.y() - 1);
5263 if (nextRow == kEdgeIndexAtEnd)
5265 nextColumn = nextVisibleEdgeIndex(Qt::LeftEdge, tableSize.width() - 1);
5266 const qreal marginY = atTableEnd(Qt::TopEdge, nextRow - 1) ? -q->topMargin() : 0;
5267 q->positionViewAtRow(nextRow, QQuickTableView::Contain, marginY);
5268 }
5269
5270 qreal marginX = 0;
5271 if (atTableEnd(Qt::RightEdge, nextColumn + 1))
5272 marginX = q->leftMargin();
5273 else if (atTableEnd(Qt::LeftEdge, nextColumn - 1))
5274 marginX = -q->leftMargin();
5275
5276 q->positionViewAtColumn(nextColumn, QQuickTableView::Contain, marginX);
5277 endMoveCurrentIndex({nextColumn, nextRow});
5278 break; }
5279 default:
5280 return false;
5281 }
5282
5283 return true;
5284}
5285
5287{
5288 Q_Q(QQuickTableView);
5289
5291 return false;
5293 return false;
5294
5296 const QPoint cell = q->cellAtIndex(index);
5297 const QQuickItem *cellItem = q->itemAtCell(cell);
5298 if (!cellItem)
5299 return false;
5300
5301 auto attached = getAttachedObject(cellItem);
5302 if (!attached || !attached->editDelegate())
5303 return false;
5304
5305 bool anyKeyPressed = false;
5306 bool editKeyPressed = false;
5307
5308 switch (e->key()) {
5309 case Qt::Key_Return:
5310 case Qt::Key_Enter:
5311#ifndef Q_OS_MACOS
5312 case Qt::Key_F2:
5313#endif
5314 anyKeyPressed = true;
5315 editKeyPressed = true;
5316 break;
5317 case Qt::Key_Shift:
5318 case Qt::Key_Alt:
5319 case Qt::Key_Control:
5320 case Qt::Key_Meta:
5321 case Qt::Key_Tab:
5322 case Qt::Key_Backtab:
5323 break;
5324 default:
5325 anyKeyPressed = true;
5326 }
5327
5328 const bool anyKeyAccepted = anyKeyPressed && (editTriggers & QQuickTableView::AnyKeyPressed);
5329 const bool editKeyAccepted = editKeyPressed && (editTriggers & QQuickTableView::EditKeyPressed);
5330
5331 if (!(editKeyAccepted || anyKeyAccepted))
5332 return false;
5333
5334 if (!canEdit(index, false)) {
5335 // If canEdit() returns false at this point (e.g because currentIndex is not
5336 // editable), we still want to eat the key event, to keep a consistent behavior
5337 // when some cells are editable, but others not.
5338 return true;
5339 }
5340
5341 q->edit(index);
5342
5343 if (editIndex.isValid() && anyKeyAccepted && !editKeyPressed) {
5344 // Replay the key event to the focus object (which should at this point
5345 // be the edit item, or an item inside the edit item).
5347 }
5348
5349 return true;
5350}
5351
5352#if QT_CONFIG(cursor)
5353void QQuickTableViewPrivate::updateCursor()
5354{
5355 int row = resizableRows ? hoverHandler->m_row : -1;
5357
5358 const auto resizeState = resizeHandler->state();
5360 || resizeState == QQuickTableViewResizeHandler::Dragging) {
5361 // Don't change the cursor while resizing, even if
5362 // the pointer is not actually hovering the grid.
5365 }
5366
5367 if (row != -1 || column != -1) {
5368 Qt::CursorShape shape;
5369 if (row != -1 && column != -1)
5370 shape = Qt::SizeFDiagCursor;
5371 else if (row != -1)
5372 shape = Qt::SplitVCursor;
5373 else
5374 shape = Qt::SplitHCursor;
5375
5376 if (m_cursorSet)
5377 qApp->changeOverrideCursor(shape);
5378 else
5379 qApp->setOverrideCursor(shape);
5380
5381 m_cursorSet = true;
5382 } else if (m_cursorSet) {
5383 qApp->restoreOverrideCursor();
5384 m_cursorSet = false;
5385 }
5386}
5387#endif
5388
5390{
5391 Q_Q(QQuickTableView);
5392
5393 if (!editItem)
5394 return;
5395
5396 const QPoint cell = q->cellAtIndex(editIndex);
5397 auto cellItem = q->itemAtCell(cell);
5398 if (!cellItem) {
5399 // The delegate item that is being edited has left the viewport. But since we
5400 // added an extra reference to it when editing began, the delegate item has
5401 // not been unloaded! It's therefore still on the content item (outside the
5402 // viewport), but its position will no longer be updated until the row and column
5403 // it's a part of enters the viewport again. To avoid glitches related to the
5404 // item showing up on wrong places (e.g after resizing a column in front of it),
5405 // we move it far out of the viewport. This way it will be "hidden", but continue
5406 // to have edit focus. When the row and column that it's a part of are eventually
5407 // flicked back in again, a relayout will move it back to the correct place.
5408 editItem->parentItem()->setX(-editItem->width() - 10000);
5409 }
5410}
5411
5413 : QQuickFlickable(*(new QQuickTableViewPrivate), parent)
5414{
5415 d_func()->init();
5416}
5417
5419 : QQuickFlickable(dd, parent)
5420{
5421 d_func()->init();
5422}
5423
5425{
5426 Q_D(QQuickTableView);
5427
5428 if (d->syncView) {
5429 // Remove this TableView as a sync child from the syncView
5430 auto syncView_d = d->syncView->d_func();
5431 syncView_d->syncChildren.removeOne(this);
5432 syncView_d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly);
5433 }
5434}
5435
5437{
5438 // componentComplete() is called on us after all static values have been assigned, but
5439 // before bindings to any anchestors has been evaluated. Especially this means that
5440 // if our size is bound to the parents size, it will still be empty at that point.
5441 // And we cannot build the table without knowing our own size. We could wait until we
5442 // got the first updatePolish() callback, but at that time, any asynchronous loaders that we
5443 // might be inside have already finished loading, which means that we would load all
5444 // the delegate items synchronously instead of asynchronously. We therefore use componentFinalized
5445 // which gets called after all the bindings we rely on has been evaluated.
5446 // When receiving this call, we load the delegate items (and build the table).
5447
5448 // Now that all bindings are evaluated, and we know
5449 // our final geometery, we can build the table.
5450 Q_D(QQuickTableView);
5451 qCDebug(lcTableViewDelegateLifecycle);
5452 d->updatePolish();
5453}
5454
5456{
5457 return QQuickFlickable::minXExtent() - d_func()->origin.x();
5458}
5459
5461{
5462 return QQuickFlickable::maxXExtent() - d_func()->endExtent.width();
5463}
5464
5466{
5467 return QQuickFlickable::minYExtent() - d_func()->origin.y();
5468}
5469
5471{
5472 return QQuickFlickable::maxYExtent() - d_func()->endExtent.height();
5473}
5474
5476{
5477 return d_func()->tableSize.height();
5478}
5479
5481{
5482 return d_func()->tableSize.width();
5483}
5484
5486{
5487 return d_func()->cellSpacing.height();
5488}
5489
5491{
5492 Q_D(QQuickTableView);
5494 return;
5495 if (qFuzzyCompare(d->cellSpacing.height(), spacing))
5496 return;
5497
5498 d->cellSpacing.setHeight(spacing);
5502}
5503
5505{
5506 return d_func()->cellSpacing.width();
5507}
5508
5510{
5511 Q_D(QQuickTableView);
5513 return;
5514 if (qFuzzyCompare(d->cellSpacing.width(), spacing))
5515 return;
5516
5517 d->cellSpacing.setWidth(spacing);
5521}
5522
5524{
5525 return d_func()->rowHeightProvider;
5526}
5527
5529{
5530 Q_D(QQuickTableView);
5531 if (provider.strictlyEquals(d->rowHeightProvider))
5532 return;
5533
5534 d->rowHeightProvider = provider;
5538}
5539
5541{
5542 return d_func()->columnWidthProvider;
5543}
5544
5546{
5547 Q_D(QQuickTableView);
5548 if (provider.strictlyEquals(d->columnWidthProvider))
5549 return;
5550
5551 d->columnWidthProvider = provider;
5555}
5556
5558{
5559 return d_func()->modelImpl();
5560}
5561
5563{
5564 Q_D(QQuickTableView);
5565 if (d->compareModel(newModel, d->assignedModel))
5566 return;
5567
5568 closeEditor();
5569 d->setModelImpl(newModel);
5570 if (d->selectionModel)
5571 d->selectionModel->setModel(d->selectionSourceModel());
5572}
5573
5575{
5576 return d_func()->assignedDelegate;
5577}
5578
5580{
5581 Q_D(QQuickTableView);
5582 if (newDelegate == d->assignedDelegate)
5583 return;
5584
5585 d->assignedDelegate = newDelegate;
5586 d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::All);
5587
5589}
5590
5591QQuickTableView::EditTriggers QQuickTableView::editTriggers() const
5592{
5593 return d_func()->editTriggers;
5594}
5595
5596void QQuickTableView::setEditTriggers(QQuickTableView::EditTriggers editTriggers)
5597{
5598 Q_D(QQuickTableView);
5599 if (editTriggers == d->editTriggers)
5600 return;
5601
5602 d->editTriggers = editTriggers;
5603
5604 emit editTriggersChanged();
5605}
5606
5608{
5609 return bool(d_func()->reusableFlag == QQmlTableInstanceModel::Reusable);
5610}
5611
5613{
5614 Q_D(QQuickTableView);
5615 if (reuseItems() == reuse)
5616 return;
5617
5619
5620 if (!reuse && d->tableModel) {
5621 // When we're told to not reuse items, we
5622 // immediately, as documented, drain the pool.
5623 d->tableModel->drainReusableItemsPool(0);
5624 }
5625
5627}
5628
5630{
5631 Q_D(QQuickTableView);
5632 d->explicitContentWidth = width;
5634}
5635
5637{
5638 Q_D(QQuickTableView);
5639 d->explicitContentHeight = height;
5641}
5642
5661{
5662 return d_func()->assignedSyncView;
5663}
5664
5666{
5667 Q_D(QQuickTableView);
5668 if (d->assignedSyncView == view)
5669 return;
5670
5671 d->assignedSyncView = view;
5673
5674 emit syncViewChanged();
5675}
5676
5695Qt::Orientations QQuickTableView::syncDirection() const
5696{
5697 return d_func()->assignedSyncDirection;
5698}
5699
5701{
5702 Q_D(QQuickTableView);
5703 if (d->assignedSyncDirection == direction)
5704 return;
5705
5706 d->assignedSyncDirection = direction;
5707 if (d->assignedSyncView)
5709
5710 emit syncDirectionChanged();
5711}
5712
5714{
5715 return d_func()->selectionModel;
5716}
5717
5719{
5720 Q_D(QQuickTableView);
5721 if (d->selectionModel == selectionModel)
5722 return;
5723
5724 // Note: There is no need to rebuild the table when the selection model
5725 // changes, since selections only affect the internals of the delegate
5726 // items, and not the layout of the TableView.
5727
5728 if (d->selectionModel) {
5733 }
5734
5735 d->selectionModel = selectionModel;
5736
5737 if (d->selectionModel) {
5738 d->selectionModel->setModel(d->selectionSourceModel());
5743 }
5744
5745 d->updateSelectedOnAllDelegateItems();
5746
5747 emit selectionModelChanged();
5748}
5749
5751{
5752 return d_func()->animate;
5753}
5754
5756{
5757 Q_D(QQuickTableView);
5758 if (d->animate == animate)
5759 return;
5760
5761 d->animate = animate;
5762 if (!animate) {
5763 d->positionXAnimation.stop();
5764 d->positionYAnimation.stop();
5765 }
5766
5767 emit animateChanged();
5768}
5769
5771{
5772 return d_func()->keyNavigationEnabled;
5773}
5774
5776{
5777 Q_D(QQuickTableView);
5778 if (d->keyNavigationEnabled == enabled)
5779 return;
5780
5781 d->keyNavigationEnabled = enabled;
5782
5783 emit keyNavigationEnabledChanged();
5784}
5785
5787{
5788 return d_func()->pointerNavigationEnabled;
5789}
5790
5792{
5793 Q_D(QQuickTableView);
5794 if (d->pointerNavigationEnabled == enabled)
5795 return;
5796
5797 d->pointerNavigationEnabled = enabled;
5798
5799 emit pointerNavigationEnabledChanged();
5800}
5801
5803{
5804 Q_D(const QQuickTableView);
5805 return d->loadedItems.isEmpty() ? -1 : d_func()->leftColumn();
5806}
5807
5809{
5810 Q_D(const QQuickTableView);
5811 return d->loadedItems.isEmpty() ? -1 : d_func()->rightColumn();
5812}
5813
5815{
5816 Q_D(const QQuickTableView);
5817 return d->loadedItems.isEmpty() ? -1 : d_func()->topRow();
5818}
5819
5821{
5822 Q_D(const QQuickTableView);
5823 return d->loadedItems.isEmpty() ? -1 : d_func()->bottomRow();
5824}
5825
5827{
5828 return d_func()->currentRow;
5829}
5830
5832{
5833 return d_func()->currentColumn;
5834}
5835
5836void QQuickTableView::positionViewAtRow(int row, PositionMode mode, qreal offset, const QRectF &subRect)
5837{
5838 Q_D(QQuickTableView);
5839 if (row < 0 || row >= rows() || d->loadedRows.isEmpty())
5840 return;
5841
5842 // Note: PositionMode::Contain is from here on translated to (Qt::AlignTop | Qt::AlignBottom).
5843 // This is an internal (unsupported) combination which means "align bottom if the whole cell
5844 // fits inside the viewport, otherwise align top".
5845
5846 if (mode & (AlignTop | AlignBottom | AlignVCenter)) {
5848 d->positionViewAtRow(row, Qt::Alignment(int(mode)), offset, subRect);
5849 } else if (mode == Contain) {
5850 if (row < topRow()) {
5851 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
5852 } else if (row > bottomRow()) {
5853 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5854 } else if (row == topRow()) {
5855 if (!subRect.isValid()) {
5856 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
5857 } else {
5858 const qreal subRectTop = d->loadedTableOuterRect.top() + subRect.top();
5859 const qreal subRectBottom = d->loadedTableOuterRect.top() + subRect.bottom();
5860 if (subRectTop < d->viewportRect.y())
5861 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
5862 else if (subRectBottom > d->viewportRect.bottom())
5863 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5864 }
5865 } else if (row == bottomRow()) {
5866 if (!subRect.isValid()) {
5867 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5868 } else {
5869 // Note: entering here means that topRow() != bottomRow(). So at least two rows are
5870 // visible in the viewport, which means that the top side of the subRect is visible.
5871 const qreal subRectBottom = d->loadedTableInnerRect.bottom() + subRect.bottom();
5872 if (subRectBottom > d->viewportRect.bottom())
5873 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5874 }
5875 }
5876 } else if (mode == Visible) {
5877 if (row < topRow()) {
5878 d->positionViewAtRow(row, Qt::AlignTop, -offset, subRect);
5879 } else if (row > bottomRow()) {
5880 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5881 } else if (subRect.isValid()) {
5882 if (row == topRow()) {
5883 const qreal subRectTop = d->loadedTableOuterRect.top() + subRect.top();
5884 const qreal subRectBottom = d->loadedTableOuterRect.top() + subRect.bottom();
5885 if (subRectBottom < d->viewportRect.top())
5886 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
5887 else if (subRectTop > d->viewportRect.bottom())
5888 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5889 } else if (row == bottomRow()) {
5890 // Note: entering here means that topRow() != bottomRow(). So at least two rows are
5891 // visible in the viewport, which means that the top side of the subRect is visible.
5892 const qreal subRectTop = d->loadedTableInnerRect.bottom() + subRect.top();
5893 if (subRectTop > d->viewportRect.bottom())
5894 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5895 }
5896 }
5897 } else {
5898 qmlWarning(this) << "Unsupported mode:" << int(mode);
5899 }
5900}
5901
5902void QQuickTableView::positionViewAtColumn(int column, PositionMode mode, qreal offset, const QRectF &subRect)
5903{
5904 Q_D(QQuickTableView);
5905 if (column < 0 || column >= columns() || d->loadedColumns.isEmpty())
5906 return;
5907
5908 // Note: PositionMode::Contain is from here on translated to (Qt::AlignLeft | Qt::AlignRight).
5909 // This is an internal (unsupported) combination which means "align right if the whole cell
5910 // fits inside the viewport, otherwise align left".
5911
5912 if (mode & (AlignLeft | AlignRight | AlignHCenter)) {
5914 d->positionViewAtColumn(column, Qt::Alignment(int(mode)), offset, subRect);
5915 } else if (mode == Contain) {
5916 if (column < leftColumn()) {
5917 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
5918 } else if (column > rightColumn()) {
5919 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
5920 } else if (column == leftColumn()) {
5921 if (!subRect.isValid()) {
5922 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
5923 } else {
5924 const qreal subRectLeft = d->loadedTableOuterRect.left() + subRect.left();
5925 const qreal subRectRight = d->loadedTableOuterRect.left() + subRect.right();
5926 if (subRectLeft < d->viewportRect.left())
5927 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
5928 else if (subRectRight > d->viewportRect.right())
5929 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
5930 }
5931 } else if (column == rightColumn()) {
5932 if (!subRect.isValid()) {
5933 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
5934 } else {
5935 // Note: entering here means that leftColumn() != rightColumn(). So at least two columns
5936 // are visible in the viewport, which means that the left side of the subRect is visible.
5937 const qreal subRectRight = d->loadedTableInnerRect.right() + subRect.right();
5938 if (subRectRight > d->viewportRect.right())
5939 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
5940 }
5941 }
5942 } else if (mode == Visible) {
5943 if (column < leftColumn()) {
5944 d->positionViewAtColumn(column, Qt::AlignLeft, -offset, subRect);
5945 } else if (column > rightColumn()) {
5946 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
5947 } else if (subRect.isValid()) {
5948 if (column == leftColumn()) {
5949 const qreal subRectLeft = d->loadedTableOuterRect.left() + subRect.left();
5950 const qreal subRectRight = d->loadedTableOuterRect.left() + subRect.right();
5951 if (subRectRight < d->viewportRect.left())
5952 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
5953 else if (subRectLeft > d->viewportRect.right())
5954 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
5955 } else if (column == rightColumn()) {
5956 // Note: entering here means that leftColumn() != rightColumn(). So at least two columns
5957 // are visible in the viewport, which means that the left side of the subRect is visible.
5958 const qreal subRectLeft = d->loadedTableInnerRect.right() + subRect.left();
5959 if (subRectLeft > d->viewportRect.right())
5960 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
5961 }
5962 }
5963 } else {
5964 qmlWarning(this) << "Unsupported mode:" << int(mode);
5965 }
5966}
5967
5968void QQuickTableView::positionViewAtCell(const QPoint &cell, PositionMode mode, const QPointF &offset, const QRectF &subRect)
5969{
5970 PositionMode horizontalMode = mode & ~(AlignTop | AlignBottom | AlignVCenter);
5971 PositionMode verticalMode = mode & ~(AlignLeft | AlignRight | AlignHCenter);
5972 if (!horizontalMode && !verticalMode) {
5973 qmlWarning(this) << "Unsupported mode:" << int(mode);
5974 return;
5975 }
5976
5977 if (horizontalMode)
5978 positionViewAtColumn(cell.x(), horizontalMode, offset.x(), subRect);
5979 if (verticalMode)
5980 positionViewAtRow(cell.y(), verticalMode, offset.y(), subRect);
5981}
5982
5983void QQuickTableView::positionViewAtIndex(const QModelIndex &index, PositionMode mode, const QPointF &offset, const QRectF &subRect)
5984{
5985 PositionMode horizontalMode = mode & ~(AlignTop | AlignBottom | AlignVCenter);
5986 PositionMode verticalMode = mode & ~(AlignLeft | AlignRight | AlignHCenter);
5987 if (!horizontalMode && !verticalMode) {
5988 qmlWarning(this) << "Unsupported mode:" << int(mode);
5989 return;
5990 }
5991
5992 if (horizontalMode)
5993 positionViewAtColumn(columnAtIndex(index), horizontalMode, offset.x(), subRect);
5994 if (verticalMode)
5995 positionViewAtRow(rowAtIndex(index), verticalMode, offset.y(), subRect);
5996}
5997
5998#if QT_DEPRECATED_SINCE(6, 5)
5999void QQuickTableView::positionViewAtCell(int column, int row, PositionMode mode, const QPointF &offset, const QRectF &subRect)
6000{
6001 PositionMode horizontalMode = mode & ~(AlignTop | AlignBottom | AlignVCenter);
6002 PositionMode verticalMode = mode & ~(AlignLeft | AlignRight | AlignHCenter);
6003 if (!horizontalMode && !verticalMode) {
6004 qmlWarning(this) << "Unsupported mode:" << int(mode);
6005 return;
6006 }
6007
6008 if (horizontalMode)
6009 positionViewAtColumn(column, horizontalMode, offset.x(), subRect);
6010 if (verticalMode)
6011 positionViewAtRow(row, verticalMode, offset.y(), subRect);
6012}
6013#endif
6014
6016{
6017 Q_D(const QQuickTableView);
6018 const int modelIndex = d->modelIndexAtCell(cell);
6019 if (!d->loadedItems.contains(modelIndex))
6020 return nullptr;
6021 return d->loadedItems.value(modelIndex)->item;
6022}
6023
6024#if QT_DEPRECATED_SINCE(6, 5)
6026{
6027 return itemAtCell(QPoint(column, row));
6028}
6029#endif
6030
6031QQuickItem *QQuickTableView::itemAtIndex(const QModelIndex &index) const
6032{
6033 Q_D(const QQuickTableView);
6034 const int serializedIndex = d->modelIndexToCellIndex(index);
6035 if (!d->loadedItems.contains(serializedIndex))
6036 return nullptr;
6037 return d->loadedItems.value(serializedIndex)->item;
6038}
6039
6040#if QT_DEPRECATED_SINCE(6, 4)
6041QPoint QQuickTableView::cellAtPos(qreal x, qreal y, bool includeSpacing) const
6042{
6043 return cellAtPosition(mapToItem(contentItem(), {x, y}), includeSpacing);
6044}
6045
6046QPoint QQuickTableView::cellAtPos(const QPointF &position, bool includeSpacing) const
6047{
6048 return cellAtPosition(mapToItem(contentItem(), position), includeSpacing);
6049}
6050#endif
6051
6052QPoint QQuickTableView::cellAtPosition(qreal x, qreal y, bool includeSpacing) const
6053{
6054 return cellAtPosition(QPoint(x, y), includeSpacing);
6055}
6056
6057QPoint QQuickTableView::cellAtPosition(const QPointF &position, bool includeSpacing) const
6058{
6059 Q_D(const QQuickTableView);
6060
6061 if (!d->loadedTableOuterRect.contains(position))
6062 return QPoint(-1, -1);
6063
6064 const qreal hSpace = d->cellSpacing.width();
6065 const qreal vSpace = d->cellSpacing.height();
6066 qreal currentColumnEnd = d->loadedTableOuterRect.x();
6067 qreal currentRowEnd = d->loadedTableOuterRect.y();
6068
6069 int foundColumn = -1;
6070 int foundRow = -1;
6071
6072 for (const int column : d->loadedColumns) {
6073 currentColumnEnd += d->getEffectiveColumnWidth(column);
6074 if (position.x() < currentColumnEnd) {
6075 foundColumn = column;
6076 break;
6077 }
6078 currentColumnEnd += hSpace;
6079 if (!includeSpacing && position.x() < currentColumnEnd) {
6080 // Hit spacing
6081 return QPoint(-1, -1);
6082 } else if (includeSpacing && position.x() < currentColumnEnd - (hSpace / 2)) {
6083 foundColumn = column;
6084 break;
6085 }
6086 }
6087
6088 for (const int row : d->loadedRows) {
6089 currentRowEnd += d->getEffectiveRowHeight(row);
6090 if (position.y() < currentRowEnd) {
6091 foundRow = row;
6092 break;
6093 }
6094 currentRowEnd += vSpace;
6095 if (!includeSpacing && position.y() < currentRowEnd) {
6096 // Hit spacing
6097 return QPoint(-1, -1);
6098 }
6099 if (includeSpacing && position.y() < currentRowEnd - (vSpace / 2)) {
6100 foundRow = row;
6101 break;
6102 }
6103 }
6104
6105 return QPoint(foundColumn, foundRow);
6106}
6107
6108bool QQuickTableView::isColumnLoaded(int column) const
6109{
6110 Q_D(const QQuickTableView);
6111 if (!d->loadedColumns.contains(column))
6112 return false;
6113
6114 if (d->rebuildState != QQuickTableViewPrivate::RebuildState::Done) {
6115 // TableView is rebuilding, and none of the rows and columns
6116 // are completely loaded until we reach the layout phase.
6118 return false;
6119 }
6120
6121 return true;
6122}
6123
6124bool QQuickTableView::isRowLoaded(int row) const
6125{
6126 Q_D(const QQuickTableView);
6127 if (!d->loadedRows.contains(row))
6128 return false;
6129
6130 if (d->rebuildState != QQuickTableViewPrivate::RebuildState::Done) {
6131 // TableView is rebuilding, and none of the rows and columns
6132 // are completely loaded until we reach the layout phase.
6134 return false;
6135 }
6136
6137 return true;
6138}
6139
6140qreal QQuickTableView::columnWidth(int column) const
6141{
6142 Q_D(const QQuickTableView);
6143 if (!isColumnLoaded(column))
6144 return -1;
6145
6146 return d->getEffectiveColumnWidth(column);
6147}
6148
6149qreal QQuickTableView::rowHeight(int row) const
6150{
6151 Q_D(const QQuickTableView);
6152 if (!isRowLoaded(row))
6153 return -1;
6154
6155 return d->getEffectiveRowHeight(row);
6156}
6157
6158qreal QQuickTableView::implicitColumnWidth(int column) const
6159{
6160 Q_D(const QQuickTableView);
6161 if (!isColumnLoaded(column))
6162 return -1;
6163
6164 return d->sizeHintForColumn(column);
6165}
6166
6167qreal QQuickTableView::implicitRowHeight(int row) const
6168{
6169 Q_D(const QQuickTableView);
6170 if (!isRowLoaded(row))
6171 return -1;
6172
6173 return d->sizeHintForRow(row);
6174}
6175
6176void QQuickTableView::setColumnWidth(int column, qreal size)
6177{
6178 Q_D(QQuickTableView);
6179 if (column < 0) {
6180 qmlWarning(this) << "column must be greather than, or equal to, zero";
6181 return;
6182 }
6183
6184 if (d->syncHorizontally) {
6185 d->syncView->setColumnWidth(column, size);
6186 return;
6187 }
6188
6189 if (qFuzzyCompare(explicitColumnWidth(column), size))
6190 return;
6191
6192 if (size < 0)
6193 d->explicitColumnWidths.remove(column);
6194 else
6195 d->explicitColumnWidths.insert(column, size);
6196
6197 if (d->loadedItems.isEmpty())
6198 return;
6199
6200 const bool allColumnsLoaded = d->atTableEnd(Qt::LeftEdge) && d->atTableEnd(Qt::RightEdge);
6201 if (column >= leftColumn() || column <= rightColumn() || allColumnsLoaded)
6202 d->forceLayout(false);
6203}
6204
6205void QQuickTableView::clearColumnWidths()
6206{
6207 Q_D(QQuickTableView);
6208
6209 if (d->syncHorizontally) {
6210 d->syncView->clearColumnWidths();
6211 return;
6212 }
6213
6214 if (d->explicitColumnWidths.isEmpty())
6215 return;
6216
6217 d->explicitColumnWidths.clear();
6218 d->forceLayout(false);
6219}
6220
6221qreal QQuickTableView::explicitColumnWidth(int column) const
6222{
6223 Q_D(const QQuickTableView);
6224
6225 if (d->syncHorizontally)
6226 return d->syncView->explicitColumnWidth(column);
6227
6228 const auto it = d->explicitColumnWidths.constFind(column);
6229 if (it != d->explicitColumnWidths.constEnd())
6230 return *it;
6231 return -1;
6232}
6233
6234void QQuickTableView::setRowHeight(int row, qreal size)
6235{
6236 Q_D(QQuickTableView);
6237 if (row < 0) {
6238 qmlWarning(this) << "row must be greather than, or equal to, zero";
6239 return;
6240 }
6241
6242 if (d->syncVertically) {
6243 d->syncView->setRowHeight(row, size);
6244 return;
6245 }
6246
6247 if (qFuzzyCompare(explicitRowHeight(row), size))
6248 return;
6249
6250 if (size < 0)
6251 d->explicitRowHeights.remove(row);
6252 else
6253 d->explicitRowHeights.insert(row, size);
6254
6255 if (d->loadedItems.isEmpty())
6256 return;
6257
6258 const bool allRowsLoaded = d->atTableEnd(Qt::TopEdge) && d->atTableEnd(Qt::BottomEdge);
6259 if (row >= topRow() || row <= bottomRow() || allRowsLoaded)
6260 d->forceLayout(false);
6261}
6262
6263void QQuickTableView::clearRowHeights()
6264{
6265 Q_D(QQuickTableView);
6266
6267 if (d->syncVertically) {
6268 d->syncView->clearRowHeights();
6269 return;
6270 }
6271
6272 if (d->explicitRowHeights.isEmpty())
6273 return;
6274
6275 d->explicitRowHeights.clear();
6276 d->forceLayout(false);
6277}
6278
6279qreal QQuickTableView::explicitRowHeight(int row) const
6280{
6281 Q_D(const QQuickTableView);
6282
6283 if (d->syncVertically)
6284 return d->syncView->explicitRowHeight(row);
6285
6286 const auto it = d->explicitRowHeights.constFind(row);
6287 if (it != d->explicitRowHeights.constEnd())
6288 return *it;
6289 return -1;
6290}
6291
6292QModelIndex QQuickTableView::modelIndex(const QPoint &cell) const
6293{
6294 Q_D(const QQuickTableView);
6295 if (cell.x() < 0 || cell.x() >= columns() || cell.y() < 0 || cell.y() >= rows())
6296 return {};
6297
6298 auto const qaim = d->model->abstractItemModel();
6299 if (!qaim)
6300 return {};
6301
6302 return qaim->index(cell.y(), cell.x());
6303}
6304
6305QPoint QQuickTableView::cellAtIndex(const QModelIndex &index) const
6306{
6307 if (!index.isValid() || index.parent().isValid())
6308 return {-1, -1};
6309 return {index.column(), index.row()};
6310}
6311
6312#if QT_DEPRECATED_SINCE(6, 4)
6313QModelIndex QQuickTableView::modelIndex(int row, int column) const
6314{
6315 static bool compat6_4 = qEnvironmentVariable("QT_QUICK_TABLEVIEW_COMPAT_VERSION") == QStringLiteral("6.4");
6316 if (compat6_4) {
6317 // In Qt 6.4.0 and 6.4.1, a source incompatible change led to row and column
6318 // being documented to be specified in the opposite order.
6319 // QT_QUICK_TABLEVIEW_COMPAT_VERSION can therefore be set to force tableview
6320 // to continue accepting calls to modelIndex(column, row).
6321 return modelIndex({row, column});
6322 } else {
6323 qmlWarning(this) << "modelIndex(row, column) is deprecated. "
6324 "Use index(row, column) instead. For more information, see "
6325 "https://doc.qt.io/qt-6/qml-qtquick-tableview-obsolete.html";
6326 return modelIndex({column, row});
6327 }
6328}
6329#endif
6330
6331QModelIndex QQuickTableView::index(int row, int column) const
6332{
6333 return modelIndex({column, row});
6334}
6335
6336int QQuickTableView::rowAtIndex(const QModelIndex &index) const
6337{
6338 return cellAtIndex(index).y();
6339}
6340
6341int QQuickTableView::columnAtIndex(const QModelIndex &index) const
6342{
6343 return cellAtIndex(index).x();
6344}
6345
6347{
6348 d_func()->forceLayout(true);
6349}
6350
6351void QQuickTableView::edit(const QModelIndex &index)
6352{
6353 Q_D(QQuickTableView);
6354
6355 if (!d->canEdit(index, true))
6356 return;
6357
6358 if (d->editIndex == index)
6359 return;
6360
6361 if (!d->tableModel)
6362 return;
6363
6364 if (!d->editModel) {
6365 d->editModel = new QQmlTableInstanceModel(qmlContext(this));
6366 d->editModel->useImportVersion(d->resolveImportVersion());
6368 [this, d] (int serializedModelIndex, QObject *object) {
6369 // initItemCallback will call setRequiredProperty for each required property in the
6370 // delegate, both for this class, but also also for any subclasses. setRequiredProperty
6371 // is currently dependent of the QQmlTableInstanceModel that was used to create the object
6372 // in order to initialize required properties, so we need to set the editItem variable
6373 // early on, so that we can use it in setRequiredProperty.
6374 d->editIndex = modelIndex(d->cellAtModelIndex(serializedModelIndex));
6375 d->editItem = qmlobject_cast<QQuickItem*>(object);
6376 if (!d->editItem)
6377 return;
6378 // Initialize required properties
6379 d->initItemCallback(serializedModelIndex, object);
6380 const auto cellItem = itemAtCell(cellAtIndex(d->editIndex));
6381 Q_ASSERT(cellItem);
6382 d->editItem->setParentItem(cellItem);
6383 // Move the cell item to the top of the other items, to ensure
6384 // that e.g a focus frame ends up on top of all the cells
6385 cellItem->setZ(2);
6386 });
6387 }
6388
6389 if (d->selectionModel)
6390 d->selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
6391
6392 if (d->editIndex.isValid())
6393 closeEditor();
6394
6395 const auto cellItem = itemAtCell(cellAtIndex(index));
6396 Q_ASSERT(cellItem);
6397 const auto attached = d->getAttachedObject(cellItem);
6398 Q_ASSERT(attached);
6399
6400 d->editModel->setModel(d->tableModel->model());
6401 d->editModel->setDelegate(attached->editDelegate());
6402
6403 const int cellIndex = d->modelIndexToCellIndex(index);
6404 QObject* object = d->editModel->object(cellIndex, QQmlIncubator::Synchronous);
6405 if (!object) {
6406 d->editIndex = QModelIndex();
6407 d->editItem = nullptr;
6408 qmlWarning(this) << "cannot edit: TableView.editDelegate could not be instantiated!";
6409 return;
6410 }
6411
6412 // Note: at this point, editIndex and editItem has been set from initItem!
6413
6414 if (!d->editItem) {
6415 qmlWarning(this) << "cannot edit: TableView.editDelegate is not an Item!";
6416 d->editItem = nullptr;
6417 d->editIndex = QModelIndex();
6418 d->editModel->release(object, QQmlInstanceModel::NotReusable);
6419 return;
6420 }
6421
6422 // Reference the cell item once more, so that it doesn't
6423 // get reused or deleted if it leaves the viewport.
6424 d->model->object(cellIndex, QQmlIncubator::Synchronous);
6425
6426 // Inform the delegate, and the edit delegate, that they're being edited
6427 d->setRequiredProperty(kRequiredProperty_editing, QVariant::fromValue(true), cellIndex, cellItem, false);
6428
6429 // Transfer focus to the edit item
6430 d->editItem->forceActiveFocus(Qt::MouseFocusReason);
6431
6432 // Install an event filter on the focus object to handle Enter and Tab.
6433 // Note that the focusObject doesn't need to be the editItem itself, in
6434 // case the editItem is a FocusScope.
6435 if (QObject *focusObject = d->editItem->window()->focusObject()) {
6436 QQuickItem *focusItem = qobject_cast<QQuickItem *>(focusObject);
6437 if (focusItem == d->editItem || d->editItem->isAncestorOf(focusItem))
6438 focusItem->installEventFilter(this);
6439 }
6440}
6441
6442void QQuickTableView::closeEditor()
6443{
6444 Q_D(QQuickTableView);
6445
6446 if (!d->editItem)
6447 return;
6448
6449 QQuickItem *cellItem = d->editItem->parentItem();
6450 d->editModel->release(d->editItem, QQmlInstanceModel::NotReusable);
6451 d->editItem = nullptr;
6452
6453 cellItem->setZ(1);
6454 const int cellIndex = d->modelIndexToCellIndex(d->editIndex);
6455 d->setRequiredProperty(kRequiredProperty_editing, QVariant::fromValue(false), cellIndex, cellItem, false);
6456 // Remove the extra reference we sat on the cell item from edit()
6457 d->model->release(cellItem, QQmlInstanceModel::NotReusable);
6458
6459 if (d->editIndex.isValid()) {
6460 // Note: we can have an invalid editIndex, even when we
6461 // have an editItem, if the model has changed (e.g been reset)!
6462 d->editIndex = QModelIndex();
6463 }
6464}
6465
6470
6471void QQuickTableView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
6472{
6473 Q_D(QQuickTableView);
6474 QQuickFlickable::geometryChange(newGeometry, oldGeometry);
6475
6476 if (d->tableModel) {
6477 // When the view changes size, we force the pool to
6478 // shrink by releasing all pooled items.
6479 d->tableModel->drainReusableItemsPool(0);
6480 }
6481
6482 d->forceLayout(false);
6483}
6484
6485void QQuickTableView::viewportMoved(Qt::Orientations orientation)
6486{
6487 Q_D(QQuickTableView);
6488
6489 // If the new viewport position was set from the setLocalViewportXY()
6490 // functions, we just update the position silently and return. Otherwise, if
6491 // the viewport was flicked by the user, or some other control, we
6492 // recursively sync all the views in the hierarchy to the same position.
6493 QQuickFlickable::viewportMoved(orientation);
6494 if (d->inSetLocalViewportPos)
6495 return;
6496
6497 // Move all views in the syncView hierarchy to the same contentX/Y.
6498 // We need to start from this view (and not the root syncView) to
6499 // ensure that we respect all the individual syncDirection flags
6500 // between the individual views in the hierarchy.
6501 d->syncViewportPosRecursive();
6502
6503 auto rootView = d->rootSyncView();
6504 auto rootView_d = rootView->d_func();
6505
6506 rootView_d->scheduleRebuildIfFastFlick();
6507
6508 if (!rootView_d->polishScheduled) {
6509 if (rootView_d->scheduledRebuildOptions) {
6510 // When we need to rebuild, collecting several viewport
6511 // moves and do a single polish gives a quicker UI.
6512 rootView->polish();
6513 } else {
6514 // Updating the table right away when flicking
6515 // slowly gives a smoother experience.
6516 const bool updated = rootView->d_func()->updateTableRecursive();
6517 if (!updated) {
6518 // One, or more, of the views are already in an
6519 // update, so we need to wait a cycle.
6520 rootView->polish();
6521 }
6522 }
6523 }
6524}
6525
6527{
6528 Q_D(QQuickTableView);
6529
6530 if (!d->keyNavigationEnabled) {
6532 return;
6533 }
6534
6535 if (d->tableSize.isEmpty())
6536 return;
6537
6538 if (d->editIndex.isValid()) {
6539 // While editing, we limit the keys that we
6540 // handle to not interfere with editing.
6541 return;
6542 }
6543
6544 if (d->setCurrentIndexFromKeyEvent(e))
6545 return;
6546
6547 if (d->editFromKeyEvent(e))
6548 return;
6549
6551}
6552
6554{
6555 Q_D(QQuickTableView);
6556
6557 if (event->type() == QEvent::KeyPress) {
6558 Q_ASSERT(d->editItem);
6559 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
6560 switch (keyEvent->key()) {
6561 case Qt::Key_Enter:
6562 case Qt::Key_Return:
6563 if (auto attached = d->getAttachedObject(d->editItem))
6564 emit attached->commit();
6565 closeEditor();
6566 return true;
6567 case Qt::Key_Tab:
6568 case Qt::Key_Backtab:
6569 if (activeFocusOnTab()) {
6570 if (auto attached = d->getAttachedObject(d->editItem))
6571 emit attached->commit();
6572 closeEditor();
6573 if (d->setCurrentIndexFromKeyEvent(keyEvent)) {
6574 const QModelIndex currentIndex = d->selectionModel->currentIndex();
6575 if (d->canEdit(currentIndex, false))
6576 edit(currentIndex);
6577 }
6578 return true;
6579 }
6580 break;
6581 case Qt::Key_Escape:
6582 closeEditor();
6583 return true;
6584 }
6585 }
6586
6588}
6589
6591{
6592 return d_func()->alternatingRows;
6593}
6594
6595void QQuickTableView::setAlternatingRows(bool alternatingRows)
6596{
6597 Q_D(QQuickTableView);
6598 if (d->alternatingRows == alternatingRows)
6599 return;
6600
6601 d->alternatingRows = alternatingRows;
6602 emit alternatingRowsChanged();
6603}
6604
6606{
6607 return d_func()->selectionBehavior;
6608}
6609
6611{
6612 Q_D(QQuickTableView);
6613 if (d->selectionBehavior == selectionBehavior)
6614 return;
6615
6616 d->selectionBehavior = selectionBehavior;
6617 emit selectionBehaviorChanged();
6618}
6619
6621{
6622 return d_func()->selectionMode;
6623}
6624
6626{
6627 Q_D(QQuickTableView);
6628 if (d->selectionMode == selectionMode)
6629 return;
6630
6631 d->selectionMode = selectionMode;
6632 emit selectionModeChanged();
6633}
6634
6636{
6637 return d_func()->resizableColumns;
6638}
6639
6641{
6642 Q_D(QQuickTableView);
6643 if (d->resizableColumns == enabled)
6644 return;
6645
6646 d->resizableColumns = enabled;
6647 d->resizeHandler->setEnabled(d->resizableRows || d->resizableColumns);
6648 d->hoverHandler->setEnabled(d->resizableRows || d->resizableColumns);
6649
6650 emit resizableColumnsChanged();
6651}
6652
6654{
6655 return d_func()->resizableRows;
6656}
6657
6659{
6660 Q_D(QQuickTableView);
6661 if (d->resizableRows == enabled)
6662 return;
6663
6664 d->resizableRows = enabled;
6665 d->resizeHandler->setEnabled(d->resizableRows || d->resizableColumns);
6666 d->hoverHandler->setEnabled(d->resizableRows || d->resizableColumns);
6667
6668 emit resizableRowsChanged();
6669}
6670
6671// ----------------------------------------------
6672
6674 : QQuickHoverHandler(view->contentItem())
6675{
6676 setMargin(5);
6677
6679 if (!isHoveringGrid())
6680 return;
6681 m_row = -1;
6682 m_column = -1;
6683#if QT_CONFIG(cursor)
6684 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
6685 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
6686 tableViewPrivate->updateCursor();
6687#endif
6688 });
6689}
6690
6692{
6694
6695 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
6696#if QT_CONFIG(cursor)
6697 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
6698#endif
6699
6700 const QPoint cell = tableView->cellAtPosition(point.position(), true);
6701 const auto item = tableView->itemAtCell(cell);
6702 if (!item) {
6703 m_row = -1;
6704 m_column = -1;
6705#if QT_CONFIG(cursor)
6706 tableViewPrivate->updateCursor();
6707#endif
6708 return;
6709 }
6710
6711 const QPointF itemPos = item->mapFromItem(tableView->contentItem(), point.position());
6712 const bool hoveringRow = (itemPos.y() < margin() || itemPos.y() > item->height() - margin());
6713 const bool hoveringColumn = (itemPos.x() < margin() || itemPos.x() > item->width() - margin());
6714 m_row = hoveringRow ? itemPos.y() < margin() ? cell.y() - 1 : cell.y() : -1;
6715 m_column = hoveringColumn ? itemPos.x() < margin() ? cell.x() - 1 : cell.x() : -1;
6716#if QT_CONFIG(cursor)
6717 tableViewPrivate->updateCursor();
6718#endif
6719}
6720
6721// ----------------------------------------------
6722
6724 : QQuickSinglePointHandler(view->contentItem())
6725{
6726 setMargin(5);
6727 // Set a grab permission that stops the flickable, as well as
6728 // any drag handler inside the delegate, from stealing the drag.
6730 setObjectName("tableViewResizeHandler");
6731}
6732
6756
6758{
6760 return false;
6761
6762 // If we have a mouse wheel event then we do not want to do anything related to resizing.
6763 if (event->type() == QEvent::Type::Wheel)
6764 return false;
6765
6766 // When the user is flicking, we disable resizing, so that
6767 // he doesn't start to resize by accident.
6768 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
6769 return !tableView->isMoving();
6770}
6771
6773{
6774 // Resolve which state we're in first...
6776 // ...and act on it next
6778}
6779
6781{
6782 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
6783 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
6784
6787
6788 if (point.state() == QEventPoint::Pressed) {
6789 m_row = tableViewPrivate->resizableRows ? tableViewPrivate->hoverHandler->m_row : -1;
6790 m_column = tableViewPrivate->resizableColumns ? tableViewPrivate->hoverHandler->m_column : -1;
6791 if (m_row != -1 || m_column != -1)
6792 m_state = Tracking;
6793 } else if (point.state() == QEventPoint::Released) {
6796 else
6798 } else if (point.state() == QEventPoint::Updated) {
6799 switch (m_state) {
6800 case Listening:
6801 break;
6802 case Tracking: {
6803 const qreal distX = m_column != -1 ? point.position().x() - point.pressPosition().x() : 0;
6804 const qreal distY = m_row != -1 ? point.position().y() - point.pressPosition().y() : 0;
6805 const qreal dragDist = qSqrt(distX * distX + distY * distY);
6806 if (dragDist > qApp->styleHints()->startDragDistance())
6808 break;}
6809 case DraggingStarted:
6810 m_state = Dragging;
6811 break;
6812 case Dragging:
6813 break;
6814 case DraggingFinished:
6815 // Handled at the top of the function
6816 Q_UNREACHABLE();
6817 break;
6818 }
6819 }
6820}
6821
6823{
6824 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
6825#if QT_CONFIG(cursor)
6826 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
6827#endif
6828
6829 switch (m_state) {
6830 case Listening:
6831 break;
6832 case Tracking:
6833 setPassiveGrab(event, point, true);
6834 // Disable flicking while dragging. TableView uses filtering instead of
6835 // pointer handlers to do flicking, so setting an exclusive grab (together
6836 // with grab permissions) doens't work ATM.
6837 tableView->setFiltersChildMouseEvents(false);
6838 break;
6839 case DraggingStarted:
6842 m_columnStartWidth = tableView->columnWidth(m_column);
6844 m_rowStartHeight = tableView->rowHeight(m_row);
6845#if QT_CONFIG(cursor)
6846 tableViewPrivate->updateCursor();
6847#endif
6848 Q_FALLTHROUGH();
6849 case Dragging: {
6850 const qreal distX = point.position().x() - m_columnStartX;
6851 const qreal distY = point.position().y() - m_rowStartY;
6852 if (m_column != -1)
6853 tableView->setColumnWidth(m_column, qMax(0.001, m_columnStartWidth + distX));
6854 if (m_row != -1)
6855 tableView->setRowHeight(m_row, qMax(0.001, m_rowStartHeight + distY));
6856 break; }
6857 case DraggingFinished: {
6858 tableView->setFiltersChildMouseEvents(true);
6859#if QT_CONFIG(cursor)
6860 tableViewPrivate->updateCursor();
6861#endif
6862 break; }
6863 }
6864}
6865
6866// ----------------------------------------------
6867
6873
6875{
6876 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
6877 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
6878 return tableViewPrivate->pointerNavigationEnabled && QQuickTapHandler::wantsEventPoint(event, point);
6879}
6880
6882
6883#include "moc_qquicktableview_p.cpp"
6884#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.
\inmodule QtCore
static QDir current()
Returns the application's current directory.
Definition qdir.h:219
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:1221
qsizetype size() const noexcept
Returns the number of items in the hash.
Definition qhash.h:927
QList< T > values() const
Returns a list containing all the values in the hash, in an arbitrary order.
Definition qhash.h:1098
T take(const Key &key)
Removes the item with the key from the hash and returns the value associated with it.
Definition qhash.h:985
bool contains(const Key &key) const noexcept
Returns true if the hash contains an item with the key; otherwise returns false.
Definition qhash.h:1007
key_iterator keyBegin() const noexcept
Definition qhash.h:1220
T value(const Key &key) const noexcept
Definition qhash.h:1054
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:951
bool isEmpty() const noexcept
Returns true if the hash contains no items; otherwise returns false.
Definition qhash.h:928
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1303
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.
Q_CORE_EXPORT void merge(const QItemSelection &other, QItemSelectionModel::SelectionFlags command)
Merges the other selection with this QItemSelection using the command given.
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:450
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:705
double toNumber() const
Returns the number value of this QJSValue, as defined in \l{ECMA-262} section 9.3,...
Definition qjsvalue.cpp:526
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:351
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:424
Qt::KeyboardModifiers modifiers() const
Returns the keyboard modifier flags that existed immediately after the event occurred.
Definition qevent.cpp:1468
int key() const
Returns the code of the key that was pressed or released.
Definition qevent.h:434
const_reference at(qsizetype i) const noexcept
Definition qlist.h:446
void clear()
Definition qlist.h:434
\inmodule QtCore
Definition qmargins.h:24
constexpr int bottom() const noexcept
Returns the bottom margin.
Definition qmargins.h:115
constexpr int left() const noexcept
Returns the left margin.
Definition qmargins.h:106
constexpr int right() const noexcept
Returns the right margin.
Definition qmargins.h:112
constexpr int top() const noexcept
Returns the top margin.
Definition qmargins.h:109
std::pair< iterator, bool > insert(value_type &&v)
const Container & values() const &
bool contains(const value_type &v) const
void remove(const value_type &v)
\inmodule QtCore
constexpr const QAbstractItemModel * model() const noexcept
Returns a pointer to the model containing the item that this index refers to.
constexpr bool isValid() const noexcept
Returns {true} if this model index is valid; otherwise returns {false}.
QDynamicMetaObjectData * metaObject
Definition qobject.h:90
QObject * parent
Definition qobject.h:73
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:299
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:328
\inmodule QtCore
Definition qobject.h:103
void installEventFilter(QObject *filterObj)
Installs an event filter filterObj on this object.
Definition qobject.cpp:2339
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
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:1555
QVariant property(const char *name) const
Returns the value of the object's name property.
Definition qobject.cpp:4323
Q_WEAK_OVERLOAD void setObjectName(const QString &name)
Sets the object's name to name.
Definition qobject.h:127
bool isValid() const
Returns {true} if this persistent model index is valid; otherwise returns {false}.
\inmodule QtCore\reentrant
Definition qpoint.h:217
constexpr qreal & ry() noexcept
Returns a reference to the y coordinate of this point.
Definition qpoint.h:368
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:343
constexpr qreal y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:348
constexpr qreal & rx() noexcept
Returns a reference to the x coordinate of this point.
Definition qpoint.h:363
\inmodule QtCore\reentrant
Definition qpoint.h:25
constexpr int & ry() noexcept
Returns a reference to the y coordinate of this point.
Definition qpoint.h:160
constexpr int & rx() noexcept
Returns a reference to the x coordinate of this point.
Definition qpoint.h:155
constexpr int x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:130
constexpr int y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:135
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() const
\qmlpropertygroup QtQuick::Item::anchors \qmlproperty AnchorLine QtQuick::Item::anchors....
QQuickWindow * window
QQuickDeliveryAgentPrivate * deliveryAgentPrivate()
QQmlListProperty< QQuickItem > children()
static QQuickItemPrivate * get(QQuickItem *item)
QPointer< QQuickItem > item
The QQuickItem class provides the most basic of all visual items in \l {Qt Quick}.
Definition qquickitem.h:63
qreal implicitWidth
Definition qquickitem.h:114
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:72
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:75
QQuickItem * parentItem() const
QQuickItem * parent
\qmlproperty Item QtQuick::Item::parent This property holds the visual parent of the item.
Definition qquickitem.h:67
qreal implicitHeight
Definition qquickitem.h:115
QPointF position() const
qreal height
This property holds the height of this item.
Definition qquickitem.h:76
void setX(qreal)
bool enabled
\qmlproperty bool QtQuick::Item::enabled
Definition qquickitem.h:79
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
std::function< void(CallBackFlag)> selectableCallbackFunction
QQuickTableView::SelectionBehavior selectionBehavior
bool editFromKeyEvent(QKeyEvent *e)
bool startSelection(const QPointF &pos, Qt::KeyboardModifiers modifiers) override
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
void setCallback(std::function< void(CallBackFlag)> func) override
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()
bool compareModel(const QVariant &model1, const QVariant &model2) const
qreal getRowHeight(int row) const
void normalizeSelection() override
QItemSelectionModel::SelectionFlag selectionFlag
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 cellIsValid(const QPoint &cell) const
virtual QAbstractItemModel * selectionSourceModel()
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)
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 *)
Qt::Orientations syncDirection
void setAlternatingRows(bool alternatingRows)
~QQuickTableView() override
QItemSelectionModel * selectionModel
void setSelectionMode(SelectionMode selectionMode)
qreal minYExtent() const override
void setEditTriggers(EditTriggers editTriggers)
void setResizableRows(bool enabled)
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()
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:484
constexpr bool isEmpty() const noexcept
Returns true if the rectangle is empty, otherwise returns false.
Definition qrect.h:661
constexpr qreal bottom() const noexcept
Returns the y-coordinate of the rectangle's bottom edge.
Definition qrect.h:500
constexpr qreal y() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:672
constexpr qreal height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:732
constexpr qreal width() const noexcept
Returns the width of the rectangle.
Definition qrect.h:729
constexpr qreal x() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:669
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:708
constexpr void moveTopLeft(const QPointF &p) noexcept
Moves the rectangle, leaving the top-left corner at the given position.
Definition qrect.h:714
constexpr qreal left() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:497
bool intersects(const QRectF &r) const noexcept
Returns true if this rectangle intersects with the given rectangle (i.e.
Definition qrect.cpp:2271
constexpr void setWidth(qreal w) noexcept
Sets the width of the rectangle to the given finite width.
Definition qrect.h:818
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:711
constexpr QPointF topLeft() const noexcept
Returns the position of the rectangle's top-left corner.
Definition qrect.h:511
constexpr QPointF center() const noexcept
Returns the center point of the rectangle.
Definition qrect.h:699
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:702
constexpr qreal top() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:498
constexpr void setHeight(qreal h) noexcept
Sets the height of the rectangle to the given finite height.
Definition qrect.h:821
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:705
constexpr qreal right() const noexcept
Returns the x-coordinate of the rectangle's right edge.
Definition qrect.h:499
\inmodule QtCore\reentrant
Definition qrect.h:30
QRect normalized() const noexcept
Returns a normalized rectangle; i.e., a rectangle that has a non-negative width and height.
Definition qrect.cpp:277
\inmodule QtCore
Definition qsize.h:208
constexpr qreal & rwidth() noexcept
Returns a reference to the width.
Definition qsize.h:356
constexpr void setHeight(qreal h) noexcept
Sets the height to the given finite height.
Definition qsize.h:341
constexpr qreal & rheight() noexcept
Returns a reference to the height.
Definition qsize.h:359
constexpr void setWidth(qreal w) noexcept
Sets the width to the given finite width.
Definition qsize.h:338
constexpr qreal width() const noexcept
Returns the width.
Definition qsize.h:332
constexpr qreal height() const noexcept
Returns the height.
Definition qsize.h:335
\inmodule QtCore
Definition qsize.h:25
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:133
constexpr int width() const noexcept
Returns the width.
Definition qsize.h:130
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:124
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
QString arg(qlonglong a, int fieldwidth=0, int base=10, QChar fillChar=u' ') const
Definition qstring.cpp:8870
\inmodule QtCore
static constexpr QTypeRevision zero()
Produces a QTypeRevision with major and minor version {0}.
\inmodule QtCore
Definition qvariant.h:65
T value() const &
Definition qvariant.h:516
qreal toReal(bool *ok=nullptr) const
Returns the variant as a qreal if the variant has userType() \l QMetaType::Double,...
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:536
QStringList toStringList() const
Returns the variant as a QStringList if the variant has userType() \l QMetaType::QStringList,...
EGLImageKHR int int EGLuint64KHR * modifiers
qreal spacing
QSet< QString >::iterator it
object setObjectName("A new object name")
rect
[4]
uint alignment
direction
else opt state
[0]
Combined button and popup list for selecting options.
@ AlignRight
Definition qnamespace.h:146
@ AlignBottom
Definition qnamespace.h:154
@ AlignVCenter
Definition qnamespace.h:155
@ AlignTop
Definition qnamespace.h:153
@ AlignHCenter
Definition qnamespace.h:148
@ AlignLeft
Definition qnamespace.h:144
@ Horizontal
Definition qnamespace.h:99
@ Vertical
Definition qnamespace.h:100
CursorShape
@ SizeFDiagCursor
@ SplitVCursor
@ SplitHCursor
@ Key_Escape
Definition qnamespace.h:663
@ Key_Tab
Definition qnamespace.h:664
@ Key_Shift
Definition qnamespace.h:683
@ Key_Return
Definition qnamespace.h:667
@ Key_Right
Definition qnamespace.h:679
@ Key_Enter
Definition qnamespace.h:668
@ Key_PageUp
Definition qnamespace.h:681
@ Key_Backtab
Definition qnamespace.h:665
@ Key_Left
Definition qnamespace.h:677
@ Key_Control
Definition qnamespace.h:684
@ Key_Alt
Definition qnamespace.h:686
@ Key_Up
Definition qnamespace.h:678
@ Key_Down
Definition qnamespace.h:680
@ Key_F2
Definition qnamespace.h:691
@ Key_Meta
Definition qnamespace.h:685
@ Key_PageDown
Definition qnamespace.h:682
@ Key_Home
Definition qnamespace.h:675
@ Key_End
Definition qnamespace.h:676
@ ShiftModifier
@ ControlModifier
@ NoModifier
@ RightEdge
@ TopEdge
@ BottomEdge
@ LeftEdge
@ ItemIsEditable
@ MouseFocusReason
@ OtherFocusReason
#define Q_FALLTHROUGH()
#define Q_UNLIKELY(x)
#define qApp
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:349
bool qIsNaN(qfloat16 f) noexcept
Definition qfloat16.h:284
qfloat16 qSqrt(qfloat16 f)
Definition qfloat16.h:289
#define forever
Definition qforeach.h:78
QList< QJSValue > QJSValueList
Definition qjsvalue.h:22
#define qWarning
Definition qlogging.h:166
#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:112
static Q_DECL_CONST_FUNCTION bool qt_is_finite(double d)
Definition qnumeric_p.h:117
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
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLenum GLsizei const GLuint GLboolean enabled
GLint GLsizei width
GLint left
GLint GLint bottom
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]
GLdouble s
[6]
Definition qopenglext.h:235
GLboolean reset
GLenum func
Definition qopenglext.h:663
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLsizei const GLchar *const * path
GLenum GLenum GLsizei void * row
static int log2(uint i)
QQmlContext * qmlContext(const QObject *obj)
Definition qqml.cpp:75
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:492
static const Qt::Edge allTableEdges[]
#define TV_REBUILDOPTION(OPTION)
static const char * kRequiredProperties
#define Q_TABLEVIEW_ASSERT(cond, output)
#define Q_TABLEVIEW_UNREACHABLE(output)
\qmltype TableView \inqmlmodule QtQuick
#define TV_REBUILDSTATE(STATE)
static const char * kRequiredProperty_selected
QDebug operator<<(QDebug dbg, QQuickTableViewPrivate::RebuildState state)
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:187
static QVariant toVariant(const QV4::Value &value, QMetaType typeHint, JSToQVariantConversionBehavior conversionBehavior, 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]