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
1510
1511Q_LOGGING_CATEGORY(lcTableViewDelegateLifecycle, "qt.quick.tableview.lifecycle")
1512
1513#define Q_TABLEVIEW_UNREACHABLE(output) { dumpTable(); qWarning() << "output:" << output; Q_UNREACHABLE(); }
1514#define Q_TABLEVIEW_ASSERT(cond, output) Q_ASSERT((cond) || [&](){ dumpTable(); qWarning() << "output:" << output; return false;}())
1515
1517
1518static const char* kRequiredProperties = "_qt_tableview_requiredpropertymask";
1519static const char* kRequiredProperty_selected = "selected";
1520static const char* kRequiredProperty_current = "current";
1521static const char* kRequiredProperty_editing = "editing";
1522
1524{
1525#define TV_REBUILDSTATE(STATE) \
1526 case QQuickTableViewPrivate::RebuildState::STATE: \
1527 dbg << QStringLiteral(#STATE); break;
1528
1529 switch (state) {
1530 TV_REBUILDSTATE(Begin);
1531 TV_REBUILDSTATE(LoadInitalTable);
1532 TV_REBUILDSTATE(VerifyTable);
1533 TV_REBUILDSTATE(LayoutTable);
1534 TV_REBUILDSTATE(CancelOvershoot);
1535 TV_REBUILDSTATE(UpdateContentSize);
1536 TV_REBUILDSTATE(PreloadColumns);
1537 TV_REBUILDSTATE(PreloadRows);
1538 TV_REBUILDSTATE(MovePreloadedItemsToPool);
1539 TV_REBUILDSTATE(Done);
1540 }
1541
1542 return dbg;
1543}
1544
1545QDebug operator<<(QDebug dbg, QQuickTableViewPrivate::RebuildOptions options)
1546{
1547#define TV_REBUILDOPTION(OPTION) \
1548 if (options & QQuickTableViewPrivate::RebuildOption::OPTION) \
1549 dbg << QStringLiteral(#OPTION)
1550
1552 dbg << QStringLiteral("None");
1553 } else {
1555 TV_REBUILDOPTION(LayoutOnly);
1556 TV_REBUILDOPTION(ViewportOnly);
1557 TV_REBUILDOPTION(CalculateNewTopLeftRow);
1558 TV_REBUILDOPTION(CalculateNewTopLeftColumn);
1559 TV_REBUILDOPTION(CalculateNewContentWidth);
1560 TV_REBUILDOPTION(CalculateNewContentHeight);
1561 TV_REBUILDOPTION(PositionViewAtRow);
1562 TV_REBUILDOPTION(PositionViewAtColumn);
1563 }
1564
1565 return dbg;
1566}
1567
1569 : startIndex(kEdgeIndexNotSet)
1570 , endIndex(kEdgeIndexNotSet)
1571 , size(0)
1572{}
1573
1575{
1576 if (startIndex == kEdgeIndexNotSet)
1577 return false;
1578
1579 if (endIndex == kEdgeIndexAtEnd) {
1580 switch (edge) {
1581 case Qt::LeftEdge:
1582 case Qt::TopEdge:
1583 return index <= startIndex;
1584 case Qt::RightEdge:
1585 case Qt::BottomEdge:
1586 return index >= startIndex;
1587 }
1588 }
1589
1590 const int s = std::min(startIndex, endIndex);
1591 const int e = std::max(startIndex, endIndex);
1592 return index >= s && index <= e;
1593}
1594
1599
1601{
1602 if (editItem) {
1603 QQuickItem *cellItem = editItem->parentItem();
1604 Q_ASSERT(cellItem);
1607 }
1608
1609 if (editModel)
1610 delete editModel;
1611
1612 for (auto *fxTableItem : loadedItems) {
1613 if (auto item = fxTableItem->item) {
1614 if (fxTableItem->ownItem)
1615 delete item;
1616 else if (tableModel)
1618 }
1619 delete fxTableItem;
1620 }
1621
1622 if (tableModel)
1623 delete tableModel;
1624}
1625
1627{
1628 if (loadedItems.isEmpty())
1629 return QLatin1String("table is empty!");
1630 return QString(QLatin1String("table cells: (%1,%2) -> (%3,%4), item count: %5, table rect: %6,%7 x %8,%9"))
1631 .arg(leftColumn()).arg(topRow())
1633 .arg(loadedItems.size())
1638}
1639
1641{
1642 auto listCopy = loadedItems.values();
1643 std::stable_sort(listCopy.begin(), listCopy.end(),
1644 [](const FxTableItem *lhs, const FxTableItem *rhs)
1645 { return lhs->index < rhs->index; });
1646
1647 qWarning() << QStringLiteral("******* TABLE DUMP *******");
1648 for (int i = 0; i < listCopy.size(); ++i)
1649 qWarning() << static_cast<FxTableItem *>(listCopy.at(i))->cell;
1651
1652 const QString filename = QStringLiteral("QQuickTableView_dumptable_capture.png");
1653 const QString path = QDir::current().absoluteFilePath(filename);
1654 if (q_func()->window() && q_func()->window()->grabWindow().save(path))
1655 qWarning() << "Window capture saved to:" << path;
1656}
1657
1659 const QVariant &value, int serializedModelIndex, QObject *object, bool init)
1660{
1661 Q_Q(QQuickTableView);
1662
1663 QQmlTableInstanceModel *tableInstanceModel = qobject_cast<QQmlTableInstanceModel *>(model);
1664 if (!tableInstanceModel) {
1665 // TableView only supports using required properties when backed by
1666 // a QQmlTableInstanceModel. This is almost always the case, except
1667 // if you assign it an ObjectModel or a DelegateModel (which are really
1668 // not supported by TableView, it expects a QAIM).
1669 return;
1670 }
1671
1672 // Attaching a property list to the delegate item is just a
1673 // work-around until QMetaProperty::isRequired() works (QTBUG-98846).
1674 const QString propertyName = QString::fromUtf8(property);
1675
1676 if (init) {
1677 bool wasRequired = false;
1678 if (object == editItem) {
1679 // Special case: the item that we should write to belongs to the edit
1680 // model rather than 'model' (which is used for normal delegate items).
1681 wasRequired = editModel->setRequiredProperty(serializedModelIndex, propertyName, value);
1682 } else {
1683 wasRequired = tableInstanceModel->setRequiredProperty(serializedModelIndex, propertyName, value);
1684 }
1685 if (wasRequired) {
1686 QStringList propertyList = object->property(kRequiredProperties).toStringList();
1687 object->setProperty(kRequiredProperties, propertyList << propertyName);
1688 }
1689 } else {
1690 {
1691 const QStringList propertyList = object->property(kRequiredProperties).toStringList();
1692 if (propertyList.contains(propertyName)) {
1693 const auto metaObject = object->metaObject();
1694 const int propertyIndex = metaObject->indexOfProperty(property);
1695 const auto metaProperty = metaObject->property(propertyIndex);
1696 metaProperty.write(object, value);
1697 }
1698 }
1699
1700 if (editItem) {
1701 // Whenever we're told to update a required property for a table item that has the
1702 // same model index as the edit item, we also mirror that update to the edit item.
1703 // As such, this function is never called for the edit item directly (except the
1704 // first time when it needs to be initialized).
1705 Q_TABLEVIEW_ASSERT(object != editItem, "");
1706 const QModelIndex modelIndex = q->modelIndex(cellAtModelIndex(serializedModelIndex));
1707 if (modelIndex == editIndex) {
1709 if (propertyList.contains(propertyName)) {
1710 const auto metaObject = editItem->metaObject();
1711 const int propertyIndex = metaObject->indexOfProperty(property);
1712 const auto metaProperty = metaObject->property(propertyIndex);
1713 metaProperty.write(editItem, value);
1714 }
1715 }
1716 }
1717
1718 }
1719}
1720
1722{
1723 return const_cast<QQuickTableView *>(q_func())->contentItem();
1724}
1725
1727{
1728 Q_Q(QQuickTableView);
1729 if (!selectionModel) {
1731 qmlWarning(q_func()) << "Cannot start selection: no SelectionModel assigned!";
1732 warnNoSelectionModel = false;
1733 return false;
1734 }
1735
1737 qmlWarning(q) << "Cannot start selection: TableView.selectionBehavior == TableView.SelectionDisabled";
1738 return false;
1739 }
1740
1741 // Only allow a selection if it doesn't conflict with resizing
1743 return false;
1744
1745 // For SingleSelection and ContiguousSelection, we should only allow one selection at a time
1749 else if (selectionModel)
1751
1752 // If pos is on top of an unselected cell, we start a session where the user selects which
1753 // cells to become selected. Otherwise, if pos is on top of an already selected cell and
1754 // ctrl is being held, we start a session where the user selects which selected cells to
1755 // become unselected.
1758 QPoint startCell = clampedCellAtPos(pos);
1759 const QModelIndex startIndex = q->index(startCell.y(), startCell.x());
1760 if (selectionModel->isSelected(startIndex))
1762 }
1763
1764 selectionStartCell = QPoint(-1, -1);
1765 selectionEndCell = QPoint(-1, -1);
1766 q->closeEditor();
1767 return true;
1768}
1769
1771{
1772 Q_Q(QQuickTableView);
1774 if (loadedItems.isEmpty())
1775 return;
1776 if (!selectionModel) {
1778 qmlWarning(q_func()) << "Cannot set selection: no SelectionModel assigned!";
1779 warnNoSelectionModel = false;
1780 return;
1781 }
1783 if (!qaim)
1784 return;
1785
1788 return;
1789 }
1790
1791 const QRect prevSelection = selection();
1792
1793 QPoint clampedCell;
1794 if (pos.x() == -1) {
1795 // Special case: use current cell as start cell
1796 clampedCell = q->cellAtIndex(selectionModel->currentIndex());
1797 } else {
1798 clampedCell = clampedCellAtPos(pos);
1799 if (cellIsValid(clampedCell))
1800 setCurrentIndex(clampedCell);
1801 }
1802
1803 if (!cellIsValid(clampedCell))
1804 return;
1805
1806 switch (selectionBehavior) {
1808 selectionStartCell = clampedCell;
1809 break;
1811 selectionStartCell = QPoint(0, clampedCell.y());
1812 break;
1814 selectionStartCell = QPoint(clampedCell.x(), 0);
1815 break;
1817 return;
1818 }
1819
1821 return;
1822
1823 // Update selection model
1824 QScopedValueRollback callbackGuard(inSelectionModelUpdate, true);
1825 updateSelection(prevSelection, selection());
1826}
1827
1829{
1831 if (loadedItems.isEmpty())
1832 return;
1833 if (!selectionModel) {
1835 qmlWarning(q_func()) << "Cannot set selection: no SelectionModel assigned!";
1836 warnNoSelectionModel = false;
1837 return;
1838 }
1840 if (!qaim)
1841 return;
1842
1843 const QRect prevSelection = selection();
1844
1845 QPoint clampedCell;
1847 clampedCell = selectionStartCell;
1848 } else {
1849 clampedCell = clampedCellAtPos(pos);
1850 if (!cellIsValid(clampedCell))
1851 return;
1852 }
1853
1854 setCurrentIndex(clampedCell);
1855
1856 switch (selectionBehavior) {
1858 selectionEndCell = clampedCell;
1859 break;
1861 selectionEndCell = QPoint(tableSize.width() - 1, clampedCell.y());
1862 break;
1864 selectionEndCell = QPoint(clampedCell.x(), tableSize.height() - 1);
1865 break;
1867 return;
1868 }
1869
1871 return;
1872
1873 // Update selection model
1874 QScopedValueRollback callbackGuard(inSelectionModelUpdate, true);
1875 updateSelection(prevSelection, selection());
1876}
1877
1879{
1880 Q_Q(const QQuickTableView);
1881
1882 // Note: pos should be relative to selectionPointerHandlerTarget()
1883 QPoint cell = q->cellAtPosition(pos, true);
1884 if (cellIsValid(cell))
1885 return cell;
1886
1887 // Clamp the cell to the loaded table and the viewport, whichever is the smallest
1888 QPointF clampedPos(
1891 QPointF clampedPosInView = q->mapFromItem(selectionPointerHandlerTarget(), clampedPos);
1892 clampedPosInView.rx() = qBound(0., clampedPosInView.x(), viewportRect.width());
1893 clampedPosInView.ry() = qBound(0., clampedPosInView.y(), viewportRect.height());
1894 clampedPos = q->mapToItem(selectionPointerHandlerTarget(), clampedPosInView);
1895
1896 return q->cellAtPosition(clampedPos, true);
1897}
1898
1899void QQuickTableViewPrivate::updateSelection(const QRect &oldSelection, const QRect &newSelection)
1900{
1902 const QRect oldRect = oldSelection.normalized();
1903 const QRect newRect = newSelection.normalized();
1904
1906 QItemSelection deselect;
1907
1908 // Select cells inside the new selection rect
1909 {
1910 const QModelIndex startIndex = qaim->index(newRect.y(), newRect.x());
1911 const QModelIndex endIndex = qaim->index(newRect.y() + newRect.height(), newRect.x() + newRect.width());
1912 for (const auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) {
1913 const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column()));
1914 select.append(QItemSelection(logicalModelIndex, logicalModelIndex));
1915 }
1916 }
1917
1918 // Unselect cells in the new minus old rects
1919 if (oldRect.x() < newRect.x()) {
1920 const QModelIndex startIndex = qaim->index(oldRect.y(), oldRect.x());
1921 const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), newRect.x() - 1);
1922 for (const auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) {
1923 const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column()));
1924 deselect.merge(QItemSelection(logicalModelIndex, logicalModelIndex), QItemSelectionModel::Select);
1925 }
1926 } else if (oldRect.x() + oldRect.width() > newRect.x() + newRect.width()) {
1927 const QModelIndex startIndex = qaim->index(oldRect.y(), newRect.x() + newRect.width() + 1);
1928 const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), oldRect.x() + oldRect.width());
1929 for (auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) {
1930 const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column()));
1931 deselect.merge(QItemSelection(logicalModelIndex, logicalModelIndex), QItemSelectionModel::Select);
1932 }
1933 }
1934
1935 if (oldRect.y() < newRect.y()) {
1936 const QModelIndex startIndex = qaim->index(oldRect.y(), oldRect.x());
1937 const QModelIndex endIndex = qaim->index(newRect.y() - 1, oldRect.x() + oldRect.width());
1938 for (const auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) {
1939 const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column()));
1940 deselect.merge(QItemSelection(logicalModelIndex, logicalModelIndex), QItemSelectionModel::Select);
1941 }
1942 } else if (oldRect.y() + oldRect.height() > newRect.y() + newRect.height()) {
1943 const QModelIndex startIndex = qaim->index(newRect.y() + newRect.height() + 1, oldRect.x());
1944 const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), oldRect.x() + oldRect.width());
1945 for (const auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) {
1946 const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column()));
1947 deselect.merge(QItemSelection(logicalModelIndex, logicalModelIndex), QItemSelectionModel::Select);
1948 }
1949 }
1950
1952 // Don't clear the selection that existed before the user started a new selection block
1957 QItemSelection oldSelection = existingSelection;
1961 } else {
1962 Q_UNREACHABLE();
1963 }
1964}
1965
1976
1978{
1979 if (!selectionModel)
1980 return;
1981 QScopedValueRollback callbackGuard(inSelectionModelUpdate, true);
1983}
1984
1986{
1987 // Normalize the selection if necessary, so that the start cell is to the left
1988 // and above the end cell. This is typically done after a selection drag has
1989 // finished so that the start and end positions up in sync with the handles.
1990 // This will not cause any changes to the selection itself.
1992 std::swap(selectionStartCell.rx(), selectionEndCell.rx());
1994 std::swap(selectionStartCell.ry(), selectionEndCell.ry());
1995}
1996
1998{
1999 Q_Q(const QQuickTableView);
2000
2001 QPoint topLeftCell = selectionStartCell;
2002 QPoint bottomRightCell = selectionEndCell;
2003 if (bottomRightCell.x() < topLeftCell.x())
2004 std::swap(topLeftCell.rx(), bottomRightCell.rx());
2005 if (selectionEndCell.y() < topLeftCell.y())
2006 std::swap(topLeftCell.ry(), bottomRightCell.ry());
2007
2008 const QPoint leftCell(topLeftCell.x(), topRow());
2009 const QPoint topCell(leftColumn(), topLeftCell.y());
2010 const QPoint rightCell(bottomRightCell.x(), topRow());
2011 const QPoint bottomCell(leftColumn(), bottomRightCell.y());
2012
2013 // If the corner cells of the selection are loaded, we can position the
2014 // selection rectangle at its exact location. Otherwise we extend it out
2015 // to the edges of the content item. This is not ideal, but the best we
2016 // can do while the location of the corner cells are unknown.
2017 // This will at least move the selection handles (and other overlay) out
2018 // of the viewport until the affected cells are eventually loaded.
2019 int left = 0;
2020 int top = 0;
2021 int right = 0;
2022 int bottom = 0;
2023
2024 if (loadedItems.contains(modelIndexAtCell(leftCell)))
2025 left = loadedTableItem(leftCell)->geometry().left();
2026 else if (leftCell.x() > rightColumn())
2027 left = q->contentWidth();
2028
2030 top = loadedTableItem(topCell)->geometry().top();
2031 else if (topCell.y() > bottomRow())
2032 top = q->contentHeight();
2033
2034 if (loadedItems.contains(modelIndexAtCell(rightCell)))
2035 right = loadedTableItem(rightCell)->geometry().right();
2036 else if (rightCell.x() > rightColumn())
2037 right = q->contentWidth();
2038
2039 if (loadedItems.contains(modelIndexAtCell(bottomCell)))
2040 bottom = loadedTableItem(bottomCell)->geometry().bottom();
2041 else if (bottomCell.y() > bottomRow())
2042 bottom = q->contentHeight();
2043
2044 return QRectF(left, top, right - left, bottom - top);
2045}
2046
2053
2055{
2056 Q_Q(QQuickTableView);
2057
2058 if (loadedItems.isEmpty())
2059 return QSizeF();
2060
2061 // Scroll the content item towards pos.
2062 // Return the distance in pixels from the edge of the viewport to pos.
2063 // The caller will typically use this information to throttle the scrolling speed.
2064 // If pos is already inside the viewport, or the viewport is scrolled all the way
2065 // to the end, we return 0.
2066 QSizeF dist(0, 0);
2067
2068 const bool outsideLeft = pos.x() < viewportRect.x();
2069 const bool outsideRight = pos.x() >= viewportRect.right() - 1;
2070 const bool outsideTop = pos.y() < viewportRect.y();
2071 const bool outsideBottom = pos.y() >= viewportRect.bottom() - 1;
2072
2073 if (outsideLeft) {
2074 const bool firstColumnLoaded = atTableEnd(Qt::LeftEdge);
2075 const qreal remainingDist = viewportRect.left() - loadedTableOuterRect.left();
2076 if (remainingDist > 0 || !firstColumnLoaded) {
2077 qreal stepX = step.width();
2078 if (firstColumnLoaded)
2079 stepX = qMin(stepX, remainingDist);
2080 q->setContentX(q->contentX() - stepX);
2081 dist.setWidth(pos.x() - viewportRect.left() - 1);
2082 }
2083 } else if (outsideRight) {
2084 const bool lastColumnLoaded = atTableEnd(Qt::RightEdge);
2085 const qreal remainingDist = loadedTableOuterRect.right() - viewportRect.right();
2086 if (remainingDist > 0 || !lastColumnLoaded) {
2087 qreal stepX = step.width();
2088 if (lastColumnLoaded)
2089 stepX = qMin(stepX, remainingDist);
2090 q->setContentX(q->contentX() + stepX);
2091 dist.setWidth(pos.x() - viewportRect.right() - 1);
2092 }
2093 }
2094
2095 if (outsideTop) {
2096 const bool firstRowLoaded = atTableEnd(Qt::TopEdge);
2097 const qreal remainingDist = viewportRect.top() - loadedTableOuterRect.top();
2098 if (remainingDist > 0 || !firstRowLoaded) {
2099 qreal stepY = step.height();
2100 if (firstRowLoaded)
2101 stepY = qMin(stepY, remainingDist);
2102 q->setContentY(q->contentY() - stepY);
2103 dist.setHeight(pos.y() - viewportRect.top() - 1);
2104 }
2105 } else if (outsideBottom) {
2106 const bool lastRowLoaded = atTableEnd(Qt::BottomEdge);
2107 const qreal remainingDist = loadedTableOuterRect.bottom() - viewportRect.bottom();
2108 if (remainingDist > 0 || !lastRowLoaded) {
2109 qreal stepY = step.height();
2110 if (lastRowLoaded)
2111 stepY = qMin(stepY, remainingDist);
2112 q->setContentY(q->contentY() + stepY);
2113 dist.setHeight(pos.y() - viewportRect.bottom() - 1);
2114 }
2115 }
2116
2117 return dist;
2118}
2119
2124
2126{
2127 QObject *attachedObject = qmlAttachedPropertiesObject<QQuickTableView>(object);
2128 return static_cast<QQuickTableViewAttached *>(attachedObject);
2129}
2130
2132{
2133 // QQmlTableInstanceModel expects index to be in column-major
2134 // order. This means that if the view is transposed (with a flipped
2135 // width and height), we need to calculate it in row-major instead.
2136 if (isTransposed) {
2137 int availableColumns = tableSize.width();
2138 return (cell.y() * availableColumns) + cell.x();
2139 } else {
2140 int availableRows = tableSize.height();
2141 return (cell.x() * availableRows) + cell.y();
2142 }
2143}
2144
2146{
2147 // QQmlTableInstanceModel expects index to be in column-major
2148 // order. This means that if the view is transposed (with a flipped
2149 // width and height), we need to calculate it in row-major instead.
2150 if (isTransposed) {
2151 int availableColumns = tableSize.width();
2152 int row = int(modelIndex / availableColumns);
2153 int column = modelIndex % availableColumns;
2154 return QPoint(column, row);
2155 } else {
2156 int availableRows = tableSize.height();
2157 int column = int(modelIndex / availableRows);
2158 int row = modelIndex % availableRows;
2159 return QPoint(column, row);
2160 }
2161}
2162
2163int QQuickTableViewPrivate::modelIndexToCellIndex(const QModelIndex &modelIndex, bool visualIndex) const
2164{
2165 // Convert QModelIndex to cell index. A cell index is just an
2166 // integer representation of a cell instead of using a QPoint.
2167 const QPoint cell = q_func()->cellAtIndex(modelIndex);
2168 if (!cellIsValid(cell))
2169 return -1;
2170 return modelIndexAtCell(visualIndex ? cell : QPoint(modelIndex.column(), modelIndex.row()));
2171}
2172
2174{
2175 return int(log2(float(edge)));
2176}
2177
2186
2188{
2189 // Find the next column (or row) around the loaded table that is
2190 // visible, and should be loaded next if the content item moves.
2191 int startIndex = -1;
2192 switch (edge) {
2193 case Qt::LeftEdge: startIndex = leftColumn() - 1; break;
2194 case Qt::RightEdge: startIndex = rightColumn() + 1; break;
2195 case Qt::TopEdge: startIndex = topRow() - 1; break;
2196 case Qt::BottomEdge: startIndex = bottomRow() + 1; break;
2197 }
2198
2199 return nextVisibleEdgeIndex(edge, startIndex);
2200}
2201
2203{
2204 // First check if we have already searched for the first visible index
2205 // after the given startIndex recently, and if so, return the cached result.
2206 // The cached result is valid if startIndex is inside the range between the
2207 // startIndex and the first visible index found after it.
2208 auto &cachedResult = cachedNextVisibleEdgeIndex[edgeToArrayIndex(edge)];
2209 if (cachedResult.containsIndex(edge, startIndex))
2210 return cachedResult.endIndex;
2211
2212 // Search for the first column (or row) in the direction of edge that is
2213 // visible, starting from the given column (startIndex).
2214 int foundIndex = kEdgeIndexNotSet;
2215 int testIndex = startIndex;
2216
2217 switch (edge) {
2218 case Qt::LeftEdge: {
2219 forever {
2220 if (testIndex < 0) {
2221 foundIndex = kEdgeIndexAtEnd;
2222 break;
2223 }
2224
2225 if (!isColumnHidden(testIndex)) {
2226 foundIndex = testIndex;
2227 break;
2228 }
2229
2230 --testIndex;
2231 }
2232 break; }
2233 case Qt::RightEdge: {
2234 forever {
2235 if (testIndex > tableSize.width() - 1) {
2236 foundIndex = kEdgeIndexAtEnd;
2237 break;
2238 }
2239
2240 if (!isColumnHidden(testIndex)) {
2241 foundIndex = testIndex;
2242 break;
2243 }
2244
2245 ++testIndex;
2246 }
2247 break; }
2248 case Qt::TopEdge: {
2249 forever {
2250 if (testIndex < 0) {
2251 foundIndex = kEdgeIndexAtEnd;
2252 break;
2253 }
2254
2255 if (!isRowHidden(testIndex)) {
2256 foundIndex = testIndex;
2257 break;
2258 }
2259
2260 --testIndex;
2261 }
2262 break; }
2263 case Qt::BottomEdge: {
2264 forever {
2265 if (testIndex > tableSize.height() - 1) {
2266 foundIndex = kEdgeIndexAtEnd;
2267 break;
2268 }
2269
2270 if (!isRowHidden(testIndex)) {
2271 foundIndex = testIndex;
2272 break;
2273 }
2274
2275 ++testIndex;
2276 }
2277 break; }
2278 }
2279
2280 cachedResult.startIndex = startIndex;
2281 cachedResult.endIndex = foundIndex;
2282 return foundIndex;
2283}
2284
2286{
2287 // Note that we actually never really know what the content size / size of the full table will
2288 // be. Even if e.g spacing changes, and we normally would assume that the size of the table
2289 // would increase accordingly, the model might also at some point have removed/hidden/resized
2290 // rows/columns outside the viewport. This would also affect the size, but since we don't load
2291 // rows or columns outside the viewport, this information is ignored. And even if we did, we
2292 // might also have been fast-flicked to a new location at some point, and started a new rebuild
2293 // there based on a new guesstimated top-left cell. So the calculated content size should always
2294 // be understood as a guesstimate, which sometimes can be really off (as a tradeoff for performance).
2295 // When this is not acceptable, the user can always set a custom content size explicitly.
2296 Q_Q(QQuickTableView);
2297
2298 if (syncHorizontally) {
2299 QBoolBlocker fixupGuard(inUpdateContentSize, true);
2300 q->QQuickFlickable::setContentWidth(syncView->contentWidth());
2301 return;
2302 }
2303
2305 // Don't calculate contentWidth when it
2306 // was set explicitly by the application.
2307 return;
2308 }
2309
2310 if (loadedItems.isEmpty()) {
2311 QBoolBlocker fixupGuard(inUpdateContentSize, true);
2312 if (model && model->count() > 0 && tableModel && tableModel->delegate())
2313 q->QQuickFlickable::setContentWidth(kDefaultColumnWidth);
2314 else
2315 q->QQuickFlickable::setContentWidth(0);
2316 return;
2317 }
2318
2320 const int columnsRemaining = nextColumn == kEdgeIndexAtEnd ? 0 : tableSize.width() - nextColumn;
2321 const qreal remainingColumnWidths = columnsRemaining * averageEdgeSize.width();
2322 const qreal remainingSpacing = columnsRemaining * cellSpacing.width();
2323 const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing;
2324 const qreal estimatedWidth = loadedTableOuterRect.right() + estimatedRemainingWidth;
2325
2326 QBoolBlocker fixupGuard(inUpdateContentSize, true);
2327 q->QQuickFlickable::setContentWidth(estimatedWidth);
2328}
2329
2331{
2332 Q_Q(QQuickTableView);
2333
2334 if (syncVertically) {
2335 QBoolBlocker fixupGuard(inUpdateContentSize, true);
2336 q->QQuickFlickable::setContentHeight(syncView->contentHeight());
2337 return;
2338 }
2339
2341 // Don't calculate contentHeight when it
2342 // was set explicitly by the application.
2343 return;
2344 }
2345
2346 if (loadedItems.isEmpty()) {
2347 QBoolBlocker fixupGuard(inUpdateContentSize, true);
2348 if (model && model->count() > 0 && tableModel && tableModel->delegate())
2349 q->QQuickFlickable::setContentHeight(kDefaultRowHeight);
2350 else
2351 q->QQuickFlickable::setContentHeight(0);
2352 return;
2353 }
2354
2356 const int rowsRemaining = nextRow == kEdgeIndexAtEnd ? 0 : tableSize.height() - nextRow;
2357 const qreal remainingRowHeights = rowsRemaining * averageEdgeSize.height();
2358 const qreal remainingSpacing = rowsRemaining * cellSpacing.height();
2359 const qreal estimatedRemainingHeight = remainingRowHeights + remainingSpacing;
2360 const qreal estimatedHeight = loadedTableOuterRect.bottom() + estimatedRemainingHeight;
2361
2362 QBoolBlocker fixupGuard(inUpdateContentSize, true);
2363 q->QQuickFlickable::setContentHeight(estimatedHeight);
2364}
2365
2367{
2368 // When rows or columns outside the viewport are removed or added, or a rebuild
2369 // forces us to guesstimate a new top-left, the edges of the table might end up
2370 // out of sync with the edges of the content view. We detect this situation here, and
2371 // move the origin to ensure that there will never be gaps at the end of the table.
2372 // Normally we detect that the size of the whole table is not going to be equal to the
2373 // size of the content view already when we load the last row/column, and especially
2374 // before it's flicked completely inside the viewport. For those cases we simply adjust
2375 // the origin/endExtent, to give a smooth flicking experience.
2376 // But if flicking fast (e.g with a scrollbar), it can happen that the viewport ends up
2377 // outside the end of the table in just one viewport update. To avoid a "blink" in the
2378 // viewport when that happens, we "move" the loaded table into the viewport to cover it.
2379 Q_Q(QQuickTableView);
2380
2381 bool tableMovedHorizontally = false;
2382 bool tableMovedVertically = false;
2383
2384 const int nextLeftColumn = nextVisibleEdgeIndexAroundLoadedTable(Qt::LeftEdge);
2385 const int nextRightColumn = nextVisibleEdgeIndexAroundLoadedTable(Qt::RightEdge);
2386 const int nextTopRow = nextVisibleEdgeIndexAroundLoadedTable(Qt::TopEdge);
2387 const int nextBottomRow = nextVisibleEdgeIndexAroundLoadedTable(Qt::BottomEdge);
2388
2389 if (syncHorizontally) {
2390 const auto syncView_d = syncView->d_func();
2391 origin.rx() = syncView_d->origin.x();
2392 endExtent.rwidth() = syncView_d->endExtent.width();
2394 } else if (nextLeftColumn == kEdgeIndexAtEnd) {
2395 // There are no more columns to load on the left side of the table.
2396 // In that case, we ensure that the origin match the beginning of the table.
2398 // We have a blank area at the left end of the viewport. In that case we don't have time to
2399 // wait for the viewport to move (after changing origin), since that will take an extra
2400 // update cycle, which will be visible as a blink. Instead, unless the blank spot is just
2401 // us overshooting, we brute force the loaded table inside the already existing viewport.
2402 if (loadedTableOuterRect.left() > origin.x()) {
2403 const qreal diff = loadedTableOuterRect.left() - origin.x();
2406 tableMovedHorizontally = true;
2407 }
2408 }
2411 } else if (loadedTableOuterRect.left() <= origin.x() + cellSpacing.width()) {
2412 // The table rect is at the origin, or outside, but we still have more
2413 // visible columns to the left. So we try to guesstimate how much space
2414 // the rest of the columns will occupy, and move the origin accordingly.
2415 const int columnsRemaining = nextLeftColumn + 1;
2416 const qreal remainingColumnWidths = columnsRemaining * averageEdgeSize.width();
2417 const qreal remainingSpacing = columnsRemaining * cellSpacing.width();
2418 const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing;
2419 origin.rx() = loadedTableOuterRect.left() - estimatedRemainingWidth;
2421 } else if (nextRightColumn == kEdgeIndexAtEnd) {
2422 // There are no more columns to load on the right side of the table.
2423 // In that case, we ensure that the end of the content view match the end of the table.
2425 // We have a blank area at the right end of the viewport. In that case we don't have time to
2426 // wait for the viewport to move (after changing endExtent), since that will take an extra
2427 // update cycle, which will be visible as a blink. Instead, unless the blank spot is just
2428 // us overshooting, we brute force the loaded table inside the already existing viewport.
2429 const qreal w = qMin(viewportRect.right(), q->contentWidth() + endExtent.width());
2430 if (loadedTableOuterRect.right() < w) {
2431 const qreal diff = loadedTableOuterRect.right() - w;
2434 tableMovedHorizontally = true;
2435 }
2436 }
2437 endExtent.rwidth() = loadedTableOuterRect.right() - q->contentWidth();
2439 } else if (loadedTableOuterRect.right() >= q->contentWidth() + endExtent.width() - cellSpacing.width()) {
2440 // The right-most column is outside the end of the content view, and we
2441 // still have more visible columns in the model. This can happen if the application
2442 // has set a fixed content width.
2443 const int columnsRemaining = tableSize.width() - nextRightColumn;
2444 const qreal remainingColumnWidths = columnsRemaining * averageEdgeSize.width();
2445 const qreal remainingSpacing = columnsRemaining * cellSpacing.width();
2446 const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing;
2447 const qreal pixelsOutsideContentWidth = loadedTableOuterRect.right() - q->contentWidth();
2448 endExtent.rwidth() = pixelsOutsideContentWidth + estimatedRemainingWidth;
2450 }
2451
2452 if (syncVertically) {
2453 const auto syncView_d = syncView->d_func();
2454 origin.ry() = syncView_d->origin.y();
2455 endExtent.rheight() = syncView_d->endExtent.height();
2457 } else if (nextTopRow == kEdgeIndexAtEnd) {
2458 // There are no more rows to load on the top side of the table.
2459 // In that case, we ensure that the origin match the beginning of the table.
2461 // We have a blank area at the top of the viewport. In that case we don't have time to
2462 // wait for the viewport to move (after changing origin), since that will take an extra
2463 // update cycle, which will be visible as a blink. Instead, unless the blank spot is just
2464 // us overshooting, we brute force the loaded table inside the already existing viewport.
2465 if (loadedTableOuterRect.top() > origin.y()) {
2466 const qreal diff = loadedTableOuterRect.top() - origin.y();
2469 tableMovedVertically = true;
2470 }
2471 }
2474 } else if (loadedTableOuterRect.top() <= origin.y() + cellSpacing.height()) {
2475 // The table rect is at the origin, or outside, but we still have more
2476 // visible rows at the top. So we try to guesstimate how much space
2477 // the rest of the rows will occupy, and move the origin accordingly.
2478 const int rowsRemaining = nextTopRow + 1;
2479 const qreal remainingRowHeights = rowsRemaining * averageEdgeSize.height();
2480 const qreal remainingSpacing = rowsRemaining * cellSpacing.height();
2481 const qreal estimatedRemainingHeight = remainingRowHeights + remainingSpacing;
2482 origin.ry() = loadedTableOuterRect.top() - estimatedRemainingHeight;
2484 } else if (nextBottomRow == kEdgeIndexAtEnd) {
2485 // There are no more rows to load on the bottom side of the table.
2486 // In that case, we ensure that the end of the content view match the end of the table.
2488 // We have a blank area at the bottom of the viewport. In that case we don't have time to
2489 // wait for the viewport to move (after changing endExtent), since that will take an extra
2490 // update cycle, which will be visible as a blink. Instead, unless the blank spot is just
2491 // us overshooting, we brute force the loaded table inside the already existing viewport.
2492 const qreal h = qMin(viewportRect.bottom(), q->contentHeight() + endExtent.height());
2493 if (loadedTableOuterRect.bottom() < h) {
2494 const qreal diff = loadedTableOuterRect.bottom() - h;
2497 tableMovedVertically = true;
2498 }
2499 }
2500 endExtent.rheight() = loadedTableOuterRect.bottom() - q->contentHeight();
2502 } else if (loadedTableOuterRect.bottom() >= q->contentHeight() + endExtent.height() - cellSpacing.height()) {
2503 // The bottom-most row is outside the end of the content view, and we
2504 // still have more visible rows in the model. This can happen if the application
2505 // has set a fixed content height.
2506 const int rowsRemaining = tableSize.height() - nextBottomRow;
2507 const qreal remainingRowHeigts = rowsRemaining * averageEdgeSize.height();
2508 const qreal remainingSpacing = rowsRemaining * cellSpacing.height();
2509 const qreal estimatedRemainingHeight = remainingRowHeigts + remainingSpacing;
2510 const qreal pixelsOutsideContentHeight = loadedTableOuterRect.bottom() - q->contentHeight();
2511 endExtent.rheight() = pixelsOutsideContentHeight + estimatedRemainingHeight;
2513 }
2514
2515 if (tableMovedHorizontally || tableMovedVertically) {
2516 qCDebug(lcTableViewDelegateLifecycle) << "move table to" << loadedTableOuterRect;
2517
2518 // relayoutTableItems() will take care of moving the existing
2519 // delegate items into the new loadedTableOuterRect.
2521
2522 // Inform the sync children that they need to rebuild to stay in sync
2523 for (auto syncChild : std::as_const(syncChildren)) {
2524 auto syncChild_d = syncChild->d_func();
2525 syncChild_d->scheduledRebuildOptions |= RebuildOption::ViewportOnly;
2526 if (tableMovedHorizontally)
2527 syncChild_d->scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftColumn;
2528 if (tableMovedVertically)
2529 syncChild_d->scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftRow;
2530 }
2531 }
2532
2534 qCDebug(lcTableViewDelegateLifecycle) << "move origin and endExtent to:" << origin << endExtent;
2535 // updateBeginningEnd() will let the new extents take effect. This will also change the
2536 // visualArea of the flickable, which again will cause any attached scrollbars to adjust
2537 // the position of the handle. Note the latter will cause the viewport to move once more.
2539 }
2540}
2541
2543{
2545 const qreal accColumnSpacing = (tableSize.width() - 1) * cellSpacing.width();
2547 } else {
2548 const qreal accColumnSpacing = (loadedColumns.count() - 1) * cellSpacing.width();
2550 }
2551}
2552
2554{
2556 const qreal accRowSpacing = (tableSize.height() - 1) * cellSpacing.height();
2558 } else {
2559 const qreal accRowSpacing = (loadedRows.count() - 1) * cellSpacing.height();
2561 }
2562}
2563
2565{
2566 const QPoint topLeft = QPoint(leftColumn(), topRow());
2567 const QPoint bottomRight = QPoint(rightColumn(), bottomRow());
2568 QRectF topLeftRect = loadedTableItem(topLeft)->geometry();
2569 QRectF bottomRightRect = loadedTableItem(bottomRight)->geometry();
2570 loadedTableOuterRect = QRectF(topLeftRect.topLeft(), bottomRightRect.bottomRight());
2571 loadedTableInnerRect = QRectF(topLeftRect.bottomRight(), bottomRightRect.topLeft());
2572}
2573
2575{
2576 // Move the tracked table rects to the new position. For this to
2577 // take visual effect (move the delegate items to be inside the table
2578 // rect), it needs to be followed by a relayoutTableItems().
2579 // Also note that the position of the viewport needs to be adjusted
2580 // separately for it to overlap the loaded table.
2582 loadedTableOuterRect.moveTopLeft(newPosition);
2583 loadedTableInnerRect.moveTopLeft(newPosition + innerDiff);
2584}
2585
2586QQuickTableViewPrivate::RebuildOptions QQuickTableViewPrivate::checkForVisibilityChanges()
2587{
2588 // This function will check if there are any visibility changes among
2589 // the _already loaded_ rows and columns. Note that there can be rows
2590 // and columns to the bottom or right that was not loaded, but should
2591 // now become visible (in case there is free space around the table).
2592 if (loadedItems.isEmpty()) {
2593 // Report no changes
2594 return RebuildOption::None;
2595 }
2596
2597 RebuildOptions rebuildOptions = RebuildOption::None;
2598
2599 if (loadedTableOuterRect.x() == origin.x() && leftColumn() != 0) {
2600 // Since the left column is at the origin of the viewport, but still not the first
2601 // column in the model, we need to calculate a new left column since there might be
2602 // columns in front of it that used to be hidden, but should now be visible (QTBUG-93264).
2605 } else {
2606 // Go through all loaded columns from first to last, find the columns that used
2607 // to be hidden and not loaded, and check if they should become visible
2608 // (and vice versa). If there is a change, we need to rebuild.
2609 for (int column = leftColumn(); column <= rightColumn(); ++column) {
2610 const bool wasVisibleFromBefore = loadedColumns.contains(column);
2611 const bool isVisibleNow = !qFuzzyIsNull(getColumnWidth(column));
2612 if (wasVisibleFromBefore == isVisibleNow)
2613 continue;
2614
2615 // A column changed visibility. This means that it should
2616 // either be loaded or unloaded. So we need a rebuild.
2617 qCDebug(lcTableViewDelegateLifecycle) << "Column" << column << "changed visibility to" << isVisibleNow;
2619 if (column == leftColumn()) {
2620 // The first loaded column should now be hidden. This means that we
2621 // need to calculate which column should now be first instead.
2623 }
2624 break;
2625 }
2626 }
2627
2628 if (loadedTableOuterRect.y() == origin.y() && topRow() != 0) {
2629 // Since the top row is at the origin of the viewport, but still not the first
2630 // row in the model, we need to calculate a new top row since there might be
2631 // rows in front of it that used to be hidden, but should now be visible (QTBUG-93264).
2634 } else {
2635 // Go through all loaded rows from first to last, find the rows that used
2636 // to be hidden and not loaded, and check if they should become visible
2637 // (and vice versa). If there is a change, we need to rebuild.
2638 for (int row = topRow(); row <= bottomRow(); ++row) {
2639 const bool wasVisibleFromBefore = loadedRows.contains(row);
2640 const bool isVisibleNow = !qFuzzyIsNull(getRowHeight(row));
2641 if (wasVisibleFromBefore == isVisibleNow)
2642 continue;
2643
2644 // A row changed visibility. This means that it should
2645 // either be loaded or unloaded. So we need a rebuild.
2646 qCDebug(lcTableViewDelegateLifecycle) << "Row" << row << "changed visibility to" << isVisibleNow;
2648 if (row == topRow())
2650 break;
2651 }
2652 }
2653
2654 return rebuildOptions;
2655}
2656
2658{
2660 RebuildOptions rebuildOptions = RebuildOption::None;
2661
2662 const QSize actualTableSize = calculateTableSize();
2663 if (tableSize != actualTableSize) {
2664 // The table size will have changed if forceLayout is called after
2665 // the row count in the model has changed, but before we received
2666 // a rowsInsertedCallback about it (and vice versa for columns).
2668 }
2669
2670 // Resizing a column (or row) can result in the table going from being
2671 // e.g completely inside the viewport to go outside. And in the latter
2672 // case, the user needs to be able to scroll the viewport, also if
2673 // flags such as Flickable.StopAtBounds is in use. So we need to
2674 // update contentWidth/Height to support that case.
2679
2681
2682 if (immediate) {
2683 auto rootView = rootSyncView();
2684 const bool updated = rootView->d_func()->updateTableRecursive();
2685 if (!updated) {
2686 qWarning() << "TableView::forceLayout(): Cannot do an immediate re-layout during an ongoing layout!";
2687 rootView->polish();
2688 }
2689 }
2690}
2691
2693{
2694 if (loadRequest.edge() == Qt::Edge(0)) {
2695 // No edge means we're loading the top-left item
2698 return;
2699 }
2700
2701 switch (loadRequest.edge()) {
2702 case Qt::LeftEdge:
2703 case Qt::RightEdge:
2705 break;
2706 case Qt::TopEdge:
2707 case Qt::BottomEdge:
2709 break;
2710 }
2711}
2712
2714{
2715 const int modelIndex = modelIndexAtCell(cell);
2716 Q_TABLEVIEW_ASSERT(loadedItems.contains(modelIndex), modelIndex << cell);
2717 return loadedItems.value(modelIndex);
2718}
2719
2721{
2722 Q_Q(QQuickTableView);
2723
2724 bool ownItem = false;
2725
2726 int modelIndex = modelIndexAtCell(isTransposed ? QPoint(logicalRowIndex(cell.x()), logicalColumnIndex(cell.y())) :
2727 QPoint(logicalColumnIndex(cell.x()), logicalRowIndex(cell.y())));
2728
2729 QObject* object = model->object(modelIndex, incubationMode);
2730 if (!object) {
2731 if (model->incubationStatus(modelIndex) == QQmlIncubator::Loading) {
2732 // Item is incubating. Return nullptr for now, and let the table call this
2733 // function again once we get a callback to itemCreatedCallback().
2734 return nullptr;
2735 }
2736
2737 qWarning() << "TableView: failed loading index:" << modelIndex;
2738 object = new QQuickItem();
2739 ownItem = true;
2740 }
2741
2743 if (!item) {
2744 // The model could not provide an QQuickItem for the
2745 // given index, so we create a placeholder instead.
2746 qWarning() << "TableView: delegate is not an item:" << modelIndex;
2747 model->release(object);
2748 item = new QQuickItem();
2749 ownItem = true;
2750 } else {
2753 qmlWarning(item) << "TableView: detected anchors on delegate with index: " << modelIndex
2754 << ". Use implicitWidth and implicitHeight instead.";
2755 }
2756
2757 if (ownItem) {
2758 // Parent item is normally set early on from initItemCallback (to
2759 // allow bindings to the parent property). But if we created the item
2760 // within this function, we need to set it explicit.
2761 item->setImplicitWidth(kDefaultColumnWidth);
2762 item->setImplicitHeight(kDefaultRowHeight);
2763 item->setParentItem(q->contentItem());
2764 }
2765 Q_TABLEVIEW_ASSERT(item->parentItem() == q->contentItem(), item->parentItem());
2766
2767 FxTableItem *fxTableItem = new FxTableItem(item, q, ownItem);
2768 fxTableItem->setVisible(false);
2769 fxTableItem->cell = cell;
2770 fxTableItem->index = modelIndex;
2771 return fxTableItem;
2772}
2773
2775{
2776#ifdef QT_DEBUG
2777 // Since TableView needs to work flawlessly when e.g incubating inside an async
2778 // loader, being able to override all loading to async while debugging can be helpful.
2779 static const bool forcedAsync = forcedIncubationMode == QLatin1String("async");
2780 if (forcedAsync)
2781 incubationMode = QQmlIncubator::Asynchronous;
2782#endif
2783
2784 // Note that even if incubation mode is asynchronous, the item might
2785 // be ready immediately since the model has a cache of items.
2787 auto item = createFxTableItem(cell, incubationMode);
2788 qCDebug(lcTableViewDelegateLifecycle) << cell << "ready?" << bool(item);
2789 return item;
2790}
2791
2793 // Make a copy and clear the list of items first to avoid destroyed
2794 // items being accessed during the loop (QTBUG-61294)
2795 auto const tmpList = loadedItems;
2797 for (FxTableItem *item : tmpList)
2799}
2800
2802{
2803 Q_Q(QQuickTableView);
2804 // Note that fxTableItem->item might already have been destroyed, in case
2805 // the item is owned by the QML context rather than the model (e.g ObjectModel etc).
2806 auto item = fxTableItem->item;
2807
2808 if (fxTableItem->ownItem) {
2809 Q_TABLEVIEW_ASSERT(item, fxTableItem->index);
2810 delete item;
2811 } else if (item) {
2812 auto releaseFlag = model->release(item, reusableFlag);
2813 if (releaseFlag == QQmlInstanceModel::Pooled) {
2814 fxTableItem->setVisible(false);
2815
2816 // If the item (or a descendant) has focus, remove it, so
2817 // that the item doesn't enter with focus when it's reused.
2818 if (QQuickWindow *window = item->window()) {
2819 const auto focusItem = qobject_cast<QQuickItem *>(window->focusObject());
2820 if (focusItem) {
2821 const bool hasFocus = item == focusItem || item->isAncestorOf(focusItem);
2822 if (hasFocus) {
2823 const auto focusChild = QQuickItemPrivate::get(q)->subFocusItem;
2825 }
2826 }
2827 }
2828 }
2829 }
2830
2831 delete fxTableItem;
2832}
2833
2835{
2836 const int modelIndex = modelIndexAtCell(cell);
2837 Q_TABLEVIEW_ASSERT(loadedItems.contains(modelIndex), modelIndex << cell);
2839}
2840
2841bool QQuickTableViewPrivate::canLoadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const
2842{
2843 switch (tableEdge) {
2844 case Qt::LeftEdge:
2845 return loadedTableOuterRect.left() > fillRect.left() + cellSpacing.width();
2846 case Qt::RightEdge:
2847 return loadedTableOuterRect.right() < fillRect.right() - cellSpacing.width();
2848 case Qt::TopEdge:
2849 return loadedTableOuterRect.top() > fillRect.top() + cellSpacing.height();
2850 case Qt::BottomEdge:
2851 return loadedTableOuterRect.bottom() < fillRect.bottom() - cellSpacing.height();
2852 }
2853
2854 return false;
2855}
2856
2858{
2859 // Note: if there is only one row or column left, we cannot unload, since
2860 // they are needed as anchor point for further layouting. We also skip
2861 // unloading in the direction we're currently scrolling.
2862
2863 switch (tableEdge) {
2864 case Qt::LeftEdge:
2865 if (loadedColumns.count() <= 1)
2866 return false;
2868 const qreal to = positionXAnimation.to().toFloat();
2869 if (to < viewportRect.x())
2870 return false;
2871 }
2872 return loadedTableInnerRect.left() <= fillRect.left();
2873 case Qt::RightEdge:
2874 if (loadedColumns.count() <= 1)
2875 return false;
2877 const qreal to = positionXAnimation.to().toFloat();
2878 if (to > viewportRect.x())
2879 return false;
2880 }
2881 return loadedTableInnerRect.right() >= fillRect.right();
2882 case Qt::TopEdge:
2883 if (loadedRows.count() <= 1)
2884 return false;
2886 const qreal to = positionYAnimation.to().toFloat();
2887 if (to < viewportRect.y())
2888 return false;
2889 }
2890 return loadedTableInnerRect.top() <= fillRect.top();
2891 case Qt::BottomEdge:
2892 if (loadedRows.count() <= 1)
2893 return false;
2895 const qreal to = positionYAnimation.to().toFloat();
2896 if (to > viewportRect.y())
2897 return false;
2898 }
2899 return loadedTableInnerRect.bottom() >= fillRect.bottom();
2900 }
2901 Q_TABLEVIEW_UNREACHABLE(tableEdge);
2902 return false;
2903}
2904
2906{
2907 for (Qt::Edge edge : allTableEdges) {
2908 if (!canLoadTableEdge(edge, rect))
2909 continue;
2910 const int nextIndex = nextVisibleEdgeIndexAroundLoadedTable(edge);
2911 if (nextIndex == kEdgeIndexAtEnd)
2912 continue;
2913 return edge;
2914 }
2915
2916 return Qt::Edge(0);
2917}
2918
2920{
2921 for (Qt::Edge edge : allTableEdges) {
2922 if (canUnloadTableEdge(edge, rect))
2923 return edge;
2924 }
2925 return Qt::Edge(0);
2926}
2927
2929{
2930 // Using an items width directly is not an option, since we change
2931 // it during layout (which would also cause problems when recycling items).
2932 auto const cellItem = loadedTableItem(cell)->item;
2933 return cellItem->implicitWidth();
2934}
2935
2937{
2938 // Using an items height directly is not an option, since we change
2939 // it during layout (which would also cause problems when recycling items).
2940 auto const cellItem = loadedTableItem(cell)->item;
2941 return cellItem->implicitHeight();
2942}
2943
2945{
2946 // Find the widest cell in the column, and return its width
2947 qreal columnWidth = 0;
2948 for (const int row : loadedRows)
2949 columnWidth = qMax(columnWidth, cellWidth(QPoint(column, row)));
2950
2951 return columnWidth;
2952}
2953
2955{
2956 // Find the highest cell in the row, and return its height
2957 qreal rowHeight = 0;
2958 for (const int column : loadedColumns)
2959 rowHeight = qMax(rowHeight, cellHeight(QPoint(column, row)));
2960 return rowHeight;
2961}
2962
2964{
2965 QSize size(0, 0);
2966 if (tableModel)
2968 else if (model)
2969 size = QSize(1, model->count());
2970
2971 return isTransposed ? size.transposed() : size;
2972}
2973
2975{
2976 // Return the column width specified by the application, or go
2977 // through the loaded items and calculate it as a fallback. For
2978 // layouting, the width can never be zero (or negative), as this
2979 // can lead us to be stuck in an infinite loop trying to load and
2980 // fill out the empty viewport space with empty columns.
2981 const qreal explicitColumnWidth = getColumnWidth(column);
2982 if (explicitColumnWidth >= 0)
2983 return explicitColumnWidth;
2984
2985 if (syncHorizontally) {
2986 if (syncView->d_func()->loadedColumns.contains(column))
2987 return syncView->d_func()->getColumnLayoutWidth(column);
2988 }
2989
2990 // Iterate over the currently visible items in the column. The downside
2991 // of doing that, is that the column width will then only be based on the implicit
2992 // width of the currently loaded items (which can be different depending on which
2993 // row you're at when the column is flicked in). The upshot is that you don't have to
2994 // bother setting columnWidthProvider for small tables, or if the implicit width doesn't vary.
2995 qreal columnWidth = sizeHintForColumn(column);
2996
2997 if (qIsNaN(columnWidth) || columnWidth <= 0) {
2998 if (!layoutWarningIssued) {
2999 layoutWarningIssued = true;
3000 qmlWarning(q_func()) << "the delegate's implicitWidth needs to be greater than zero";
3001 }
3002 columnWidth = kDefaultColumnWidth;
3003 }
3004
3005 return columnWidth;
3006}
3007
3009{
3010 // Return y pos of row after layout
3012 return loadedTableItem(QPoint(leftColumn(), row))->geometry().y();
3013}
3014
3016{
3017 // Return row height after layout
3020}
3021
3023{
3024 // Return x pos of column after layout
3026 return loadedTableItem(QPoint(column, topRow()))->geometry().x();
3027}
3028
3030{
3031 // Return column width after layout
3034}
3035
3037{
3038 // Return the row height specified by the application, or go
3039 // through the loaded items and calculate it as a fallback. For
3040 // layouting, the height can never be zero (or negative), as this
3041 // can lead us to be stuck in an infinite loop trying to load and
3042 // fill out the empty viewport space with empty rows.
3043 const qreal explicitRowHeight = getRowHeight(row);
3044 if (explicitRowHeight >= 0)
3045 return explicitRowHeight;
3046
3047 if (syncVertically) {
3048 if (syncView->d_func()->loadedRows.contains(row))
3049 return syncView->d_func()->getRowLayoutHeight(row);
3050 }
3051
3052 // Iterate over the currently visible items in the row. The downside
3053 // of doing that, is that the row height will then only be based on the implicit
3054 // height of the currently loaded items (which can be different depending on which
3055 // column you're at when the row is flicked in). The upshot is that you don't have to
3056 // bother setting rowHeightProvider for small tables, or if the implicit height doesn't vary.
3057 qreal rowHeight = sizeHintForRow(row);
3058
3059 if (qIsNaN(rowHeight) || rowHeight <= 0) {
3060 if (!layoutWarningIssued) {
3061 layoutWarningIssued = true;
3062 qmlWarning(q_func()) << "the delegate's implicitHeight needs to be greater than zero";
3063 }
3064 rowHeight = kDefaultRowHeight;
3065 }
3066
3067 return rowHeight;
3068}
3069
3071{
3072 // Return the width of the given column, if explicitly set. Return 0 if the column
3073 // is hidden, and -1 if the width is not set (which means that the width should
3074 // instead be calculated from the implicit size of the delegate items. This function
3075 // can be overridden by e.g HeaderView to provide the column widths by other means.
3076 Q_Q(const QQuickTableView);
3077
3078 const int noExplicitColumnWidth = -1;
3079
3081 return cachedColumnWidth.size;
3082
3083 if (syncHorizontally)
3084 return syncView->d_func()->getColumnWidth(column);
3085
3087 // We only respect explicit column widths when no columnWidthProvider
3088 // is set. Otherwise it's the responsibility of the provider to e.g
3089 // call explicitColumnWidth() (and implicitColumnWidth()), if needed.
3090 qreal explicitColumnWidth = q->explicitColumnWidth(column);
3091 if (explicitColumnWidth >= 0)
3092 return explicitColumnWidth;
3093 return noExplicitColumnWidth;
3094 }
3095
3096 qreal columnWidth = noExplicitColumnWidth;
3097
3099 auto const columnAsArgument = QJSValueList() << QJSValue(logicalColumnIndex(column));
3100 columnWidth = columnWidthProvider.call(columnAsArgument).toNumber();
3101 if (qIsNaN(columnWidth) || columnWidth < 0)
3102 columnWidth = noExplicitColumnWidth;
3103 } else {
3104 if (!layoutWarningIssued) {
3105 layoutWarningIssued = true;
3106 qmlWarning(q_func()) << "columnWidthProvider doesn't contain a function";
3107 }
3108 columnWidth = noExplicitColumnWidth;
3109 }
3110
3112 cachedColumnWidth.size = columnWidth;
3113 return columnWidth;
3114}
3115
3117{
3118 // Return the height of the given row, if explicitly set. Return 0 if the row
3119 // is hidden, and -1 if the height is not set (which means that the height should
3120 // instead be calculated from the implicit size of the delegate items. This function
3121 // can be overridden by e.g HeaderView to provide the row heights by other means.
3122 Q_Q(const QQuickTableView);
3123
3124 const int noExplicitRowHeight = -1;
3125
3127 return cachedRowHeight.size;
3128
3129 if (syncVertically)
3130 return syncView->d_func()->getRowHeight(row);
3131
3133 // We only resepect explicit row heights when no rowHeightProvider
3134 // is set. Otherwise it's the responsibility of the provider to e.g
3135 // call explicitRowHeight() (and implicitRowHeight()), if needed.
3136 qreal explicitRowHeight = q->explicitRowHeight(row);
3137 if (explicitRowHeight >= 0)
3138 return explicitRowHeight;
3139 return noExplicitRowHeight;
3140 }
3141
3142 qreal rowHeight = noExplicitRowHeight;
3143
3145 auto const rowAsArgument = QJSValueList() << QJSValue(logicalRowIndex(row));
3146 rowHeight = rowHeightProvider.call(rowAsArgument).toNumber();
3147 if (qIsNaN(rowHeight) || rowHeight < 0)
3148 rowHeight = noExplicitRowHeight;
3149 } else {
3150 if (!layoutWarningIssued) {
3151 layoutWarningIssued = true;
3152 qmlWarning(q_func()) << "rowHeightProvider doesn't contain a function";
3153 }
3154 rowHeight = noExplicitRowHeight;
3155 }
3156
3158 cachedRowHeight.size = rowHeight;
3159 return rowHeight;
3160}
3161
3163{
3164 Q_Q(QQuickTableView);
3165
3166 qreal contentX = 0;
3167 const int columnX = getEffectiveColumnX(column);
3168
3169 if (subRect.isValid()) {
3171 // Special case: Align to the right as long as the left
3172 // edge of the cell remains visible. Otherwise align to the left.
3173 alignment = subRect.width() > q->width() ? Qt::AlignLeft : Qt::AlignRight;
3174 }
3175
3176 if (alignment & Qt::AlignLeft) {
3177 contentX = columnX + subRect.x() + offset;
3178 } else if (alignment & Qt::AlignRight) {
3179 contentX = columnX + subRect.right() - viewportRect.width() + offset;
3180 } else if (alignment & Qt::AlignHCenter) {
3181 const qreal centerDistance = (viewportRect.width() - subRect.width()) / 2;
3182 contentX = columnX + subRect.x() - centerDistance + offset;
3183 }
3184 } else {
3185 const int columnWidth = getEffectiveColumnWidth(column);
3187 alignment = columnWidth > q->width() ? Qt::AlignLeft : Qt::AlignRight;
3188
3189 if (alignment & Qt::AlignLeft) {
3190 contentX = columnX + offset;
3191 } else if (alignment & Qt::AlignRight) {
3192 contentX = columnX + columnWidth - viewportRect.width() + offset;
3193 } else if (alignment & Qt::AlignHCenter) {
3194 const qreal centerDistance = (viewportRect.width() - columnWidth) / 2;
3195 contentX = columnX - centerDistance + offset;
3196 }
3197 }
3198
3199 // Don't overshoot
3200 contentX = qBound(-q->minXExtent(), contentX, -q->maxXExtent());
3201
3202 return contentX;
3203}
3204
3206{
3207 Q_Q(QQuickTableView);
3208
3209 qreal contentY = 0;
3210 const int rowY = getEffectiveRowY(row);
3211
3212 if (subRect.isValid()) {
3214 // Special case: Align to the bottom as long as the top
3215 // edge of the cell remains visible. Otherwise align to the top.
3216 alignment = subRect.height() > q->height() ? Qt::AlignTop : Qt::AlignBottom;
3217 }
3218
3219 if (alignment & Qt::AlignTop) {
3220 contentY = rowY + subRect.y() + offset;
3221 } else if (alignment & Qt::AlignBottom) {
3222 contentY = rowY + subRect.bottom() - viewportRect.height() + offset;
3223 } else if (alignment & Qt::AlignVCenter) {
3224 const qreal centerDistance = (viewportRect.height() - subRect.height()) / 2;
3225 contentY = rowY + subRect.y() - centerDistance + offset;
3226 }
3227 } else {
3228 const int rowHeight = getEffectiveRowHeight(row);
3230 alignment = rowHeight > q->height() ? Qt::AlignTop : Qt::AlignBottom;
3231
3232 if (alignment & Qt::AlignTop) {
3233 contentY = rowY + offset;
3234 } else if (alignment & Qt::AlignBottom) {
3235 contentY = rowY + rowHeight - viewportRect.height() + offset;
3236 } else if (alignment & Qt::AlignVCenter) {
3237 const qreal centerDistance = (viewportRect.height() - rowHeight) / 2;
3238 contentY = rowY - centerDistance + offset;
3239 }
3240 }
3241
3242 // Don't overshoot
3243 contentY = qBound(-q->minYExtent(), contentY, -q->maxYExtent());
3244
3245 return contentY;
3246}
3247
3249{
3250 // A column is hidden if the width is explicit set to zero (either by
3251 // using a columnWidthProvider, or by overriding getColumnWidth()).
3253}
3254
3256{
3257 // A row is hidden if the height is explicit set to zero (either by
3258 // using a rowHeightProvider, or by overriding getRowHeight()).
3259 return qFuzzyIsNull(getRowHeight(row));
3260}
3261
3263{
3264 qCDebug(lcTableViewDelegateLifecycle);
3265
3266 if (viewportRect.isEmpty()) {
3267 // This can happen if TableView was resized down to have a zero size
3268 qCDebug(lcTableViewDelegateLifecycle()) << "Skipping relayout, viewport has zero size";
3269 return;
3270 }
3271
3272 qreal nextColumnX = loadedTableOuterRect.x();
3273 qreal nextRowY = loadedTableOuterRect.y();
3274
3275 for (const int column : loadedColumns) {
3276 // Adjust the geometry of all cells in the current column
3278
3279 for (const int row : loadedRows) {
3281 QRectF geometry = item->geometry();
3282 geometry.moveLeft(nextColumnX);
3283 geometry.setWidth(width);
3284 item->setGeometry(geometry);
3285 }
3286
3287 if (width > 0)
3288 nextColumnX += width + cellSpacing.width();
3289 }
3290
3291 for (const int row : loadedRows) {
3292 // Adjust the geometry of all cells in the current row
3294
3295 for (const int column : loadedColumns) {
3297 QRectF geometry = item->geometry();
3298 geometry.moveTop(nextRowY);
3299 geometry.setHeight(height);
3300 item->setGeometry(geometry);
3301 }
3302
3303 if (height > 0)
3304 nextRowY += height + cellSpacing.height();
3305 }
3306
3307 if (Q_UNLIKELY(lcTableViewDelegateLifecycle().isDebugEnabled())) {
3308 for (const int column : loadedColumns) {
3309 for (const int row : loadedRows) {
3310 QPoint cell = QPoint(column, row);
3311 qCDebug(lcTableViewDelegateLifecycle()) << "relayout item:" << cell << loadedTableItem(cell)->geometry();
3312 }
3313 }
3314 }
3315}
3316
3318{
3319 int columnThatNeedsLayout;
3320 int neighbourColumn;
3321 qreal columnX;
3322 qreal columnWidth;
3323
3324 if (tableEdge == Qt::LeftEdge) {
3325 columnThatNeedsLayout = leftColumn();
3326 neighbourColumn = loadedColumns.values().at(1);
3327 columnWidth = getColumnLayoutWidth(columnThatNeedsLayout);
3328 const auto neighbourItem = loadedTableItem(QPoint(neighbourColumn, topRow()));
3329 columnX = neighbourItem->geometry().left() - cellSpacing.width() - columnWidth;
3330 } else {
3331 columnThatNeedsLayout = rightColumn();
3332 neighbourColumn = loadedColumns.values().at(loadedColumns.count() - 2);
3333 columnWidth = getColumnLayoutWidth(columnThatNeedsLayout);
3334 const auto neighbourItem = loadedTableItem(QPoint(neighbourColumn, topRow()));
3335 columnX = neighbourItem->geometry().right() + cellSpacing.width();
3336 }
3337
3338 for (const int row : loadedRows) {
3339 auto fxTableItem = loadedTableItem(QPoint(columnThatNeedsLayout, row));
3340 auto const neighbourItem = loadedTableItem(QPoint(neighbourColumn, row));
3341 const qreal rowY = neighbourItem->geometry().y();
3342 const qreal rowHeight = neighbourItem->geometry().height();
3343
3344 fxTableItem->setGeometry(QRectF(columnX, rowY, columnWidth, rowHeight));
3345 fxTableItem->setVisible(true);
3346
3347 qCDebug(lcTableViewDelegateLifecycle()) << "layout item:" << QPoint(columnThatNeedsLayout, row) << fxTableItem->geometry();
3348 }
3349}
3350
3352{
3353 int rowThatNeedsLayout;
3354 int neighbourRow;
3355
3356 if (tableEdge == Qt::TopEdge) {
3357 rowThatNeedsLayout = topRow();
3358 neighbourRow = loadedRows.values().at(1);
3359 } else {
3360 rowThatNeedsLayout = bottomRow();
3361 neighbourRow = loadedRows.values().at(loadedRows.count() - 2);
3362 }
3363
3364 // Set the width first, since text items in QtQuick will calculate
3365 // implicitHeight based on the text items width.
3366 for (const int column : loadedColumns) {
3367 auto fxTableItem = loadedTableItem(QPoint(column, rowThatNeedsLayout));
3368 auto const neighbourItem = loadedTableItem(QPoint(column, neighbourRow));
3369 const qreal columnX = neighbourItem->geometry().x();
3370 const qreal columnWidth = neighbourItem->geometry().width();
3371 fxTableItem->item->setX(columnX);
3372 fxTableItem->item->setWidth(columnWidth);
3373 }
3374
3375 qreal rowY;
3376 qreal rowHeight;
3377 if (tableEdge == Qt::TopEdge) {
3378 rowHeight = getRowLayoutHeight(rowThatNeedsLayout);
3379 const auto neighbourItem = loadedTableItem(QPoint(leftColumn(), neighbourRow));
3380 rowY = neighbourItem->geometry().top() - cellSpacing.height() - rowHeight;
3381 } else {
3382 rowHeight = getRowLayoutHeight(rowThatNeedsLayout);
3383 const auto neighbourItem = loadedTableItem(QPoint(leftColumn(), neighbourRow));
3384 rowY = neighbourItem->geometry().bottom() + cellSpacing.height();
3385 }
3386
3387 for (const int column : loadedColumns) {
3388 auto fxTableItem = loadedTableItem(QPoint(column, rowThatNeedsLayout));
3389 fxTableItem->item->setY(rowY);
3390 fxTableItem->item->setHeight(rowHeight);
3391 fxTableItem->setVisible(true);
3392
3393 qCDebug(lcTableViewDelegateLifecycle()) << "layout item:" << QPoint(column, rowThatNeedsLayout) << fxTableItem->geometry();
3394 }
3395}
3396
3398{
3399 const QPoint cell(loadRequest.column(), loadRequest.row());
3400 auto topLeftItem = loadedTableItem(cell);
3401 auto item = topLeftItem->item;
3402
3403 item->setPosition(loadRequest.startPosition());
3404 item->setSize(QSizeF(getColumnLayoutWidth(cell.x()), getRowLayoutHeight(cell.y())));
3405 topLeftItem->setVisible(true);
3406 qCDebug(lcTableViewDelegateLifecycle) << "geometry:" << topLeftItem->geometry();
3407}
3408
3410{
3411 if (loadRequest.edge() == Qt::Edge(0)) {
3412 // No edge means we're loading the top-left item
3414 return;
3415 }
3416
3417 switch (loadRequest.edge()) {
3418 case Qt::LeftEdge:
3419 case Qt::RightEdge:
3421 break;
3422 case Qt::TopEdge:
3423 case Qt::BottomEdge:
3425 break;
3426 }
3427}
3428
3430{
3431 Q_Q(QQuickTableView);
3433
3434 while (loadRequest.hasCurrentCell()) {
3436 FxTableItem *fxTableItem = loadFxTableItem(cell, loadRequest.incubationMode());
3437
3438 if (!fxTableItem) {
3439 // Requested item is not yet ready. Just leave, and wait for this
3440 // function to be called again when the item is ready.
3441 return;
3442 }
3443
3444 loadedItems.insert(modelIndexAtCell(cell), fxTableItem);
3446 }
3447
3448 qCDebug(lcTableViewDelegateLifecycle()) << "all items loaded!";
3449
3453
3455 // Loading of this edge was not done as a part of a rebuild, but
3456 // instead as an incremental build after e.g a flick.
3457 updateExtents();
3459
3460 switch (loadRequest.edge()) {
3461 case Qt::LeftEdge:
3462 emit q->leftColumnChanged();
3463 break;
3464 case Qt::RightEdge:
3465 emit q->rightColumnChanged();
3466 break;
3467 case Qt::TopEdge:
3468 emit q->topRowChanged();
3469 break;
3470 case Qt::BottomEdge:
3471 emit q->bottomRowChanged();
3472 break;
3473 }
3474
3475 if (editIndex.isValid())
3477
3478 emit q->layoutChanged();
3479 }
3480
3482
3483 qCDebug(lcTableViewDelegateLifecycle()) << "current table:" << tableLayoutToString();
3484 qCDebug(lcTableViewDelegateLifecycle()) << "Load request completed!";
3485 qCDebug(lcTableViewDelegateLifecycle()) << "****************************************";
3486}
3487
3489{
3490 Q_Q(QQuickTableView);
3491
3493 qCDebug(lcTableViewDelegateLifecycle()) << "begin rebuild:" << q << "options:" << rebuildOptions;
3496 : QMargins(q->leftColumn(), q->topRow(), q->rightColumn(), q->bottomRow());
3497 }
3498
3500
3504 return;
3505 }
3506
3508 if (loadedItems.isEmpty()) {
3509 qCDebug(lcTableViewDelegateLifecycle()) << "no items loaded!";
3513 } else if (!moveToNextRebuildState()) {
3514 return;
3515 }
3516 }
3517
3522 return;
3523 }
3524
3529 return;
3530 }
3531
3535 return;
3536 }
3537
3538 const bool preload = (rebuildOptions & RebuildOption::All
3540
3542 if (preload && !atTableEnd(Qt::RightEdge))
3545 return;
3546 }
3547
3549 if (preload && !atTableEnd(Qt::BottomEdge))
3552 return;
3553 }
3554
3556 while (Qt::Edge edge = nextEdgeToUnload(viewportRect))
3557 unloadEdge(edge);
3559 return;
3560 }
3561
3564 emit q->columnsChanged();
3566 emit q->rowsChanged();
3567 if (edgesBeforeRebuild.left() != q->leftColumn())
3568 emit q->leftColumnChanged();
3569 if (edgesBeforeRebuild.right() != q->rightColumn())
3570 emit q->rightColumnChanged();
3571 if (edgesBeforeRebuild.top() != q->topRow())
3572 emit q->topRowChanged();
3573 if (edgesBeforeRebuild.bottom() != q->bottomRow())
3574 emit q->bottomRowChanged();
3575
3576 if (editIndex.isValid())
3579
3580 emit q->layoutChanged();
3581
3582 qCDebug(lcTableViewDelegateLifecycle()) << "current table:" << tableLayoutToString();
3583 qCDebug(lcTableViewDelegateLifecycle()) << "rebuild completed!";
3584 qCDebug(lcTableViewDelegateLifecycle()) << "################################################";
3585 qCDebug(lcTableViewDelegateLifecycle());
3586 }
3587
3589}
3590
3592{
3593 if (loadRequest.isActive()) {
3594 // Items are still loading async, which means
3595 // that the current state is not yet done.
3596 return false;
3597 }
3598
3602 else
3604
3605 qCDebug(lcTableViewDelegateLifecycle()) << rebuildState;
3606 return true;
3607}
3608
3610{
3611 if (tableSize.isEmpty()) {
3612 // There is no cell that can be top left
3613 topLeftCell.rx() = kEdgeIndexAtEnd;
3614 topLeftCell.ry() = kEdgeIndexAtEnd;
3615 return;
3616 }
3617
3619 const auto syncView_d = syncView->d_func();
3620
3621 if (syncView_d->loadedItems.isEmpty()) {
3622 topLeftCell.rx() = 0;
3623 topLeftCell.ry() = 0;
3624 return;
3625 }
3626
3627 // Get sync view top left, and use that as our own top left (if possible)
3628 const QPoint syncViewTopLeftCell(syncView_d->leftColumn(), syncView_d->topRow());
3629 const auto syncViewTopLeftFxItem = syncView_d->loadedTableItem(syncViewTopLeftCell);
3630 const QPointF syncViewTopLeftPos = syncViewTopLeftFxItem->geometry().topLeft();
3631
3632 if (syncHorizontally) {
3633 topLeftCell.rx() = syncViewTopLeftCell.x();
3634 topLeftPos.rx() = syncViewTopLeftPos.x();
3635
3636 if (topLeftCell.x() >= tableSize.width()) {
3637 // Top left is outside our own model.
3638 topLeftCell.rx() = kEdgeIndexAtEnd;
3639 topLeftPos.rx() = kEdgeIndexAtEnd;
3640 }
3641 }
3642
3643 if (syncVertically) {
3644 topLeftCell.ry() = syncViewTopLeftCell.y();
3645 topLeftPos.ry() = syncViewTopLeftPos.y();
3646
3647 if (topLeftCell.y() >= tableSize.height()) {
3648 // Top left is outside our own model.
3649 topLeftCell.ry() = kEdgeIndexAtEnd;
3650 topLeftPos.ry() = kEdgeIndexAtEnd;
3651 }
3652 }
3653
3655 // We have a valid top left, so we're done
3656 return;
3657 }
3658 }
3659
3660 // Since we're not sync-ing both horizontal and vertical, calculate the missing
3661 // dimention(s) ourself. If we rebuild all, we find the first visible top-left
3662 // item starting from cell(0, 0). Otherwise, guesstimate which row or column that
3663 // should be the new top-left given the geometry of the viewport.
3664
3665 if (!syncHorizontally) {
3667 // Find the first visible column from the beginning
3668 topLeftCell.rx() = nextVisibleEdgeIndex(Qt::RightEdge, 0);
3669 if (topLeftCell.x() == kEdgeIndexAtEnd) {
3670 // No visible column found
3671 return;
3672 }
3674 // Guesstimate new top left
3675 const int newColumn = int(viewportRect.x() / (averageEdgeSize.width() + cellSpacing.width()));
3676 topLeftCell.rx() = qBound(0, newColumn, tableSize.width() - 1);
3677 topLeftPos.rx() = topLeftCell.x() * (averageEdgeSize.width() + cellSpacing.width());
3679 topLeftCell.rx() = qBound(0, positionViewAtColumnAfterRebuild, tableSize.width() - 1);
3680 topLeftPos.rx() = qFloor(topLeftCell.x()) * (averageEdgeSize.width() + cellSpacing.width());
3681 } else {
3682 // Keep the current top left, unless it's outside model
3683 topLeftCell.rx() = qBound(0, leftColumn(), tableSize.width() - 1);
3684 // We begin by loading the columns where the viewport is at
3685 // now. But will move the whole table and the viewport
3686 // later, when we do a layoutAfterLoadingInitialTable().
3687 topLeftPos.rx() = loadedTableOuterRect.x();
3688 }
3689 }
3690
3691 if (!syncVertically) {
3693 // Find the first visible row from the beginning
3694 topLeftCell.ry() = nextVisibleEdgeIndex(Qt::BottomEdge, 0);
3695 if (topLeftCell.y() == kEdgeIndexAtEnd) {
3696 // No visible row found
3697 return;
3698 }
3700 // Guesstimate new top left
3701 const int newRow = int(viewportRect.y() / (averageEdgeSize.height() + cellSpacing.height()));
3702 topLeftCell.ry() = qBound(0, newRow, tableSize.height() - 1);
3703 topLeftPos.ry() = topLeftCell.y() * (averageEdgeSize.height() + cellSpacing.height());
3705 topLeftCell.ry() = qBound(0, positionViewAtRowAfterRebuild, tableSize.height() - 1);
3706 topLeftPos.ry() = qFloor(topLeftCell.y()) * (averageEdgeSize.height() + cellSpacing.height());
3707 } else {
3708 topLeftCell.ry() = qBound(0, topRow(), tableSize.height() - 1);
3709 topLeftPos.ry() = loadedTableOuterRect.y();
3710 }
3711 }
3712}
3713
3715{
3717
3722 }
3723
3728 }
3729
3730 QPoint topLeft;
3731 QPointF topLeftPos;
3732 calculateTopLeft(topLeft, topLeftPos);
3733 qCDebug(lcTableViewDelegateLifecycle()) << "initial viewport rect:" << viewportRect;
3734 qCDebug(lcTableViewDelegateLifecycle()) << "initial top left cell:" << topLeft << ", pos:" << topLeftPos;
3735
3736 if (!loadedItems.isEmpty()) {
3741 }
3742
3744 origin = QPointF(0, 0);
3745 endExtent = QSizeF(0, 0);
3749 }
3750
3752 loadedRows.clear();
3756
3757 if (syncHorizontally)
3759
3760 if (syncVertically)
3762
3764 setLocalViewportX(topLeftPos.x());
3765
3767 setLocalViewportY(topLeftPos.y());
3768
3770
3771 if (!model) {
3772 qCDebug(lcTableViewDelegateLifecycle()) << "no model found, leaving table empty";
3773 return;
3774 }
3775
3776 if (model->count() == 0) {
3777 qCDebug(lcTableViewDelegateLifecycle()) << "empty model found, leaving table empty";
3778 return;
3779 }
3780
3781 if (tableModel && !tableModel->delegate()) {
3782 qCDebug(lcTableViewDelegateLifecycle()) << "no delegate found, leaving table empty";
3783 return;
3784 }
3785
3786 if (topLeft.x() == kEdgeIndexAtEnd || topLeft.y() == kEdgeIndexAtEnd) {
3787 qCDebug(lcTableViewDelegateLifecycle()) << "no visible row or column found, leaving table empty";
3788 return;
3789 }
3790
3791 if (topLeft.x() == kEdgeIndexNotSet || topLeft.y() == kEdgeIndexNotSet) {
3792 qCDebug(lcTableViewDelegateLifecycle()) << "could not resolve top-left item, leaving table empty";
3793 return;
3794 }
3795
3796 if (viewportRect.isEmpty()) {
3797 qCDebug(lcTableViewDelegateLifecycle()) << "viewport has zero size, leaving table empty";
3798 return;
3799 }
3800
3801 // Load top-left item. After loaded, loadItemsInsideRect() will take
3802 // care of filling out the rest of the table.
3806}
3807
3809{
3810 const bool allColumnsLoaded = atTableEnd(Qt::LeftEdge) && atTableEnd(Qt::RightEdge);
3811 if (rebuildOptions.testFlag(RebuildOption::CalculateNewContentWidth) || allColumnsLoaded) {
3814 }
3815
3816 const bool allRowsLoaded = atTableEnd(Qt::TopEdge) && atTableEnd(Qt::BottomEdge);
3817 if (rebuildOptions.testFlag(RebuildOption::CalculateNewContentHeight) || allRowsLoaded) {
3820 }
3821
3822 updateExtents();
3823}
3824
3836
3838{
3839 // Check if we are supposed to position the viewport at a certain column
3841 return;
3842 // The requested column might have been hidden or is outside model bounds
3844 return;
3845
3846 const qreal newContentX = getAlignmentContentX(
3851
3852 setLocalViewportX(newContentX);
3854}
3855
3857{
3858 // Check if we are supposed to position the viewport at a certain row
3860 return;
3861 // The requested row might have been hidden or is outside model bounds
3863 return;
3864
3865 const qreal newContentY = getAlignmentContentY(
3870
3871 setLocalViewportY(newContentY);
3873}
3874
3876{
3877 Q_Q(QQuickTableView);
3878
3879 // Note: we only want to cancel overshoot from a rebuild if we're supposed to position
3880 // the view on a specific cell. The app is allowed to overshoot by setting contentX and
3881 // contentY manually. Also, if this view is a sync child, we should always stay in sync
3882 // with the syncView, so then we don't do anything.
3883 const bool positionVertically = rebuildOptions.testFlag(RebuildOption::PositionViewAtRow);
3884 const bool positionHorizontally = rebuildOptions.testFlag(RebuildOption::PositionViewAtColumn);
3885 const bool cancelVertically = positionVertically && !syncVertically;
3886 const bool cancelHorizontally = positionHorizontally && !syncHorizontally;
3887
3888 if (cancelHorizontally && !qFuzzyIsNull(q->horizontalOvershoot())) {
3889 qCDebug(lcTableViewDelegateLifecycle()) << "cancelling overshoot horizontally:" << q->horizontalOvershoot();
3890 setLocalViewportX(q->horizontalOvershoot() < 0 ? -q->minXExtent() : -q->maxXExtent());
3892 }
3893
3894 if (cancelVertically && !qFuzzyIsNull(q->verticalOvershoot())) {
3895 qCDebug(lcTableViewDelegateLifecycle()) << "cancelling overshoot vertically:" << q->verticalOvershoot();
3896 setLocalViewportY(q->verticalOvershoot() < 0 ? -q->minYExtent() : -q->maxYExtent());
3898 }
3899}
3900
3902{
3903 Q_Q(QQuickTableView);
3904 qCDebug(lcTableViewDelegateLifecycle) << edge;
3905
3906 switch (edge) {
3907 case Qt::LeftEdge: {
3908 const int column = leftColumn();
3909 for (int row : loadedRows)
3914 emit q->leftColumnChanged();
3915 break; }
3916 case Qt::RightEdge: {
3917 const int column = rightColumn();
3918 for (int row : loadedRows)
3923 emit q->rightColumnChanged();
3924 break; }
3925 case Qt::TopEdge: {
3926 const int row = topRow();
3927 for (int col : loadedColumns)
3928 unloadItem(QPoint(col, row));
3932 emit q->topRowChanged();
3933 break; }
3934 case Qt::BottomEdge: {
3935 const int row = bottomRow();
3936 for (int col : loadedColumns)
3937 unloadItem(QPoint(col, row));
3941 emit q->bottomRowChanged();
3942 break; }
3943 }
3944
3946 emit q->layoutChanged();
3947
3948 qCDebug(lcTableViewDelegateLifecycle) << tableLayoutToString();
3949}
3950
3952{
3953 const int edgeIndex = nextVisibleEdgeIndexAroundLoadedTable(edge);
3954 qCDebug(lcTableViewDelegateLifecycle) << edge << edgeIndex << q_func();
3955
3956 const auto &visibleCells = edge & (Qt::LeftEdge | Qt::RightEdge)
3958 loadRequest.begin(edge, edgeIndex, visibleCells, incubationMode);
3960}
3961
3963{
3964 // Unload table edges that have been moved outside the visible part of the
3965 // table (including buffer area), and load new edges that has been moved inside.
3966 // Note: an important point is that we always keep the table rectangular
3967 // and without holes to reduce complexity (we never leave the table in
3968 // a half-loaded state, or keep track of multiple patches).
3969 // We load only one edge (row or column) at a time. This is especially
3970 // important when loading into the buffer, since we need to be able to
3971 // cancel the buffering quickly if the user starts to flick, and then
3972 // focus all further loading on the edges that are flicked into view.
3973
3974 if (loadRequest.isActive()) {
3975 // Don't start loading more edges while we're
3976 // already waiting for another one to load.
3977 return;
3978 }
3979
3980 if (loadedItems.isEmpty()) {
3981 // We need at least the top-left item to be loaded before we can
3982 // start loading edges around it. Not having a top-left item at
3983 // this point means that the model is empty (or no delegate).
3984 return;
3985 }
3986
3987 bool tableModified;
3988
3989 do {
3990 tableModified = false;
3991
3993 tableModified = true;
3994 unloadEdge(edge);
3995 }
3996
3997 if (Qt::Edge edge = nextEdgeToLoad(viewportRect)) {
3998 tableModified = true;
3999 loadEdge(edge, incubationMode);
4000 if (loadRequest.isActive())
4001 return;
4002 }
4003 } while (tableModified);
4004
4005}
4006
4008{
4009 Q_Q(QQuickTableView);
4010
4012 return;
4013
4014 if (!qFuzzyIsNull(q->verticalOvershoot()) || !qFuzzyIsNull(q->horizontalOvershoot())) {
4015 // Don't drain while we're overshooting, since this will fill up the
4016 // pool, but we expect to reuse them all once the content item moves back.
4017 return;
4018 }
4019
4020 // When loading edges, we don't want to drain the reuse pool too aggressively. Normally,
4021 // all the items in the pool are reused rapidly as the content view is flicked around
4022 // anyway. Even if the table is temporarily flicked to a section that contains fewer
4023 // cells than what used to be (e.g if the flicked-in rows are taller than average), it
4024 // still makes sense to keep all the items in circulation; Chances are, that soon enough,
4025 // thinner rows are flicked back in again (meaning that we can fit more items into the
4026 // view). But at the same time, if a delegate chooser is in use, the pool might contain
4027 // items created from different delegates. And some of those delegates might be used only
4028 // occasionally. So to avoid situations where an item ends up in the pool for too long, we
4029 // call drain after each load request, but with a sufficiently large pool time. (If an item
4030 // in the pool has a large pool time, it means that it hasn't been reused for an equal
4031 // amount of load cycles, and should be released).
4032 //
4033 // We calculate an appropriate pool time by figuring out what the minimum time must be to
4034 // not disturb frequently reused items. Since the number of items in a row might be higher
4035 // than in a column (or vice versa), the minimum pool time should take into account that
4036 // you might be flicking out a single row (filling up the pool), before you continue
4037 // flicking in several new columns (taking them out again, but now in smaller chunks). This
4038 // will increase the number of load cycles items are kept in the pool (poolTime), but still,
4039 // we shouldn't release them, as they are still being reused frequently.
4040 // To get a flexible maxValue (that e.g tolerates rows and columns being flicked
4041 // in with varying sizes, causing some items not to be resued immediately), we multiply the
4042 // value by 2. Note that we also add an extra +1 to the column count, because the number of
4043 // visible columns will fluctuate between +1/-1 while flicking.
4044 const int w = loadedColumns.count();
4045 const int h = loadedRows.count();
4046 const int minTime = int(std::ceil(w > h ? qreal(w + 1) / h : qreal(h + 1) / w));
4047 const int maxTime = minTime * 2;
4049}
4050
4052 if (!q_func()->isComponentComplete()) {
4053 // We'll rebuild the table once complete anyway
4054 return;
4055 }
4056
4057 scheduledRebuildOptions |= options;
4058 q_func()->polish();
4059}
4060
4062{
4063 QQuickTableView *root = const_cast<QQuickTableView *>(q_func());
4064 while (QQuickTableView *view = root->d_func()->syncView)
4065 root = view;
4066 return root;
4067}
4068
4070{
4071 // We always start updating from the top of the syncView tree, since
4072 // the layout of a syncView child will depend on the layout of the syncView.
4073 // E.g when a new column is flicked in, the syncView should load and layout
4074 // the column first, before any syncChildren gets a chance to do the same.
4075 Q_TABLEVIEW_ASSERT(!polishing, "recursive updatePolish() calls are not allowed!");
4076 rootSyncView()->d_func()->updateTableRecursive();
4077}
4078
4080{
4081 if (polishing) {
4082 // We're already updating the Table in this view, so
4083 // we cannot continue. Signal this back by returning false.
4084 // The caller can then choose to call "polish()" instead, to
4085 // do the update later.
4086 return false;
4087 }
4088
4089 const bool updateComplete = updateTable();
4090 if (!updateComplete)
4091 return false;
4092
4093 const auto children = syncChildren;
4094 for (auto syncChild : children) {
4095 auto syncChild_d = syncChild->d_func();
4096 const int mask =
4101 syncChild_d->scheduledRebuildOptions |= rebuildOptions & ~mask;
4102
4103 const bool descendantUpdateComplete = syncChild_d->updateTableRecursive();
4104 if (!descendantUpdateComplete)
4105 return false;
4106 }
4107
4109
4110 return true;
4111}
4112
4114{
4115 // Whenever something changes, e.g viewport moves, spacing is set to a
4116 // new value, model changes etc, this function will end up being called. Here
4117 // we check what needs to be done, and load/unload cells accordingly.
4118 // If we cannot complete the update (because we need to wait for an item
4119 // to load async), we return false.
4120
4121 Q_TABLEVIEW_ASSERT(!polishing, "recursive updatePolish() calls are not allowed!");
4122 QBoolBlocker polishGuard(polishing, true);
4123
4124 if (loadRequest.isActive()) {
4125 // We're currently loading items async to build a new edge in the table. We see the loading
4126 // as an atomic operation, which means that we don't continue doing anything else until all
4127 // items have been received and laid out. Note that updatePolish is then called once more
4128 // after the loadRequest has completed to handle anything that might have occurred in-between.
4129 return false;
4130 }
4131
4135 }
4136
4138
4142 }
4143
4144 if (loadedItems.isEmpty())
4145 return !loadRequest.isActive();
4146
4149
4150 return !loadRequest.isActive();
4151}
4152
4154{
4155 if (inUpdateContentSize) {
4156 // We update the content size dynamically as we load and unload edges.
4157 // Unfortunately, this also triggers a call to this function. The base
4158 // implementation will do things like start a momentum animation or move
4159 // the content view somewhere else, which causes glitches. This can
4160 // especially happen if flicking on one of the syncView children, which triggers
4161 // an update to our content size. In that case, the base implementation don't know
4162 // that the view is being indirectly dragged, and will therefore do strange things as
4163 // it tries to 'fixup' the geometry. So we use a guard to prevent this from happening.
4164 return;
4165 }
4166
4167 QQuickFlickablePrivate::fixup(data, minExtent, maxExtent);
4168}
4169
4171{
4172 const auto data = QQmlData::get(q_func());
4173 if (!data || !data->propertyCache)
4174 return QTypeRevision::zero();
4175
4176 const auto cppMetaObject = data->propertyCache->firstCppMetaObject();
4177 const auto qmlTypeView = QQmlMetaType::qmlType(cppMetaObject);
4178
4179 // TODO: did we rather want qmlTypeView.revision() here?
4180 return qmlTypeView.metaObjectRevision();
4181}
4182
4184{
4185 Q_Q(QQuickTableView);
4186 // When the assigned model is not an instance model, we create a wrapper
4187 // model (QQmlTableInstanceModel) that keeps a pointer to both the
4188 // assigned model and the assigned delegate. This model will give us a
4189 // common interface to any kind of model (js arrays, QAIM, number etc), and
4190 // help us create delegate instances.
4193 model = tableModel;
4194}
4195
4197{
4198 if (!selectionModel)
4199 return false;
4200
4202 if (!model)
4203 return false;
4204
4205 return selectionModel->isSelected(q_func()->modelIndex(cell));
4206}
4207
4209{
4210 if (!selectionModel)
4211 return false;
4212
4214 if (!model)
4215 return false;
4216
4217 return selectionModel->currentIndex() == q_func()->modelIndex(cell);
4218}
4219
4221{
4223 // The selection model was manipulated outside of TableView
4224 // and SelectionRectangle. In that case we cancel any ongoing
4225 // selection tracking.
4227 }
4228
4229 const auto &selectedIndexes = selected.indexes();
4230 const auto &deselectedIndexes = deselected.indexes();
4231 for (int i = 0; i < selectedIndexes.size(); ++i)
4232 setSelectedOnDelegateItem(selectedIndexes.at(i), true);
4233 for (int i = 0; i < deselectedIndexes.size(); ++i)
4234 setSelectedOnDelegateItem(deselectedIndexes.at(i), false);
4235}
4236
4238{
4239 if (modelIndex.isValid() && modelIndex.model() != selectionSourceModel()) {
4240 qmlWarning(q_func())
4241 << "Cannot select cells: TableView.selectionModel.model is not "
4242 << "compatible with the model displayed in the view";
4243 return;
4244 }
4245
4246 const int cellIndex = modelIndexToCellIndex(modelIndex);
4247 if (!loadedItems.contains(cellIndex))
4248 return;
4249 const QPoint cell = cellAtModelIndex(cellIndex);
4252}
4253
4255{
4256 // TableView.selectionModel.model should always be the same as TableView.model.
4257 // After all, when the user selects an index in the view, the same index should
4258 // be selected in the selection model. We therefore set the model in
4259 // selectionModel.model automatically.
4260 // But it's not always the case that the model shown in the view is the same
4261 // as TableView.model. Subclasses with a proxy model will instead show the
4262 // proxy model (e.g TreeView and HeaderView). And then it's no longer clear if
4263 // we should use the proxy model or the TableView.model as source model in
4264 // TableView.selectionModel. It's up to the subclass. But in short, if the proxy
4265 // model shares the same model items as TableView.model (just with e.g a filter
4266 // applied, or sorted etc), then TableView.model should be used. If the proxy
4267 // model is a completely different model that shares no model items with
4268 // TableView.model, then the proxy model should be used (e.g HeaderView).
4269 return qaim(modelImpl());
4270}
4271
4273{
4274 // If modelAsVariant wraps a qaim, return it
4275 if (modelAsVariant.userType() == qMetaTypeId<QJSValue>())
4276 modelAsVariant = modelAsVariant.value<QJSValue>().toVariant();
4277 return qvariant_cast<QAbstractItemModel *>(modelAsVariant);
4278}
4279
4281{
4283
4284 for (auto it = loadedItems.keyBegin(), end = loadedItems.keyEnd(); it != end; ++it) {
4285 const int cellIndex = *it;
4286 const QPoint cell = cellAtModelIndex(cellIndex);
4287 const bool selected = selectedInSelectionModel(cell);
4288 const bool current = currentInSelectionModel(cell);
4290 const bool editing = editIndex == q_func()->modelIndex(cell);
4294 }
4295}
4296
4298{
4299 if (current.isValid() && current.model() != selectionSourceModel()) {
4300 qmlWarning(q_func())
4301 << "Cannot change current index: TableView.selectionModel.model is not "
4302 << "compatible with the model displayed in the view";
4303 return;
4304 }
4305
4307 setCurrentOnDelegateItem(previous, false);
4308 setCurrentOnDelegateItem(current, true);
4309}
4310
4312{
4313 Q_Q(QQuickTableView);
4314
4316 const QPoint currentCell = q->cellAtIndex(currentIndex);
4317 if (currentCell.x() != currentColumn) {
4318 currentColumn = currentCell.x();
4319 emit q->currentColumnChanged();
4320 }
4321
4322 if (currentCell.y() != currentRow) {
4323 currentRow = currentCell.y();
4324 emit q->currentRowChanged();
4325 }
4326}
4327
4329{
4330 const int cellIndex = modelIndexToCellIndex(index);
4331 if (!loadedItems.contains(cellIndex))
4332 return;
4333
4334 const QPoint cell = cellAtModelIndex(cellIndex);
4337}
4338
4340{
4342 return;
4343
4344 qCDebug(lcTableViewDelegateLifecycle) << "item done loading:"
4345 << cellAtModelIndex(modelIndex);
4346
4347 // Since the item we waited for has finished incubating, we can
4348 // continue with the load request. processLoadRequest will
4349 // ask the model for the requested item once more, which will be
4350 // quick since the model has cached it.
4353 updatePolish();
4354}
4355
4357{
4358 Q_Q(QQuickTableView);
4359
4360 auto item = qobject_cast<QQuickItem*>(object);
4361 if (!item)
4362 return;
4363
4364 item->setParentItem(q->contentItem());
4365 item->setZ(1);
4366
4367 const QPoint cell = cellAtModelIndex(modelIndex);
4368 const QPoint visualCell = QPoint(visualColumnIndex(cell.x()), visualRowIndex(cell.y()));
4369 const bool current = currentInSelectionModel(visualCell);
4370 const bool selected = selectedInSelectionModel(visualCell);
4371 setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(current), modelIndex, object, true);
4372 setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(selected), modelIndex, object, true);
4374
4375 if (auto attached = getAttachedObject(object))
4376 attached->setView(q);
4377}
4378
4380{
4381 Q_UNUSED(modelIndex);
4382
4383 if (auto attached = getAttachedObject(object))
4384 emit attached->pooled();
4385}
4386
4388{
4389 const QPoint cell = cellAtModelIndex(modelIndex);
4390 const QPoint visualCell = QPoint(visualColumnIndex(cell.x()), visualRowIndex(cell.y()));
4391 const bool current = currentInSelectionModel(visualCell);
4392 const bool selected = selectedInSelectionModel(visualCell);
4393 setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(current), modelIndex, object, false);
4394 setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(selected), modelIndex, object, false);
4395 // Note: the edit item will never be reused, so no reason to set kRequiredProperty_editing
4396
4397 if (auto item = qobject_cast<QQuickItem*>(object))
4398 QQuickItemPrivate::get(item)->setCulled(false);
4399
4400 if (auto attached = getAttachedObject(object))
4401 emit attached->reused();
4402}
4403
4405{
4406 // The application can change properties like the model or the delegate while
4407 // we're e.g in the middle of e.g loading a new row. Since this will lead to
4408 // unpredicted behavior, and possibly a crash, we need to postpone taking
4409 // such assignments into effect until we're in a state that allows it.
4410
4412 syncModel();
4413 syncDelegate();
4414 syncSyncView();
4416
4418}
4419
4448
4450{
4451 if (!tableModel) {
4452 // Only the tableModel uses the delegate assigned to a
4453 // TableView. DelegateModel has it's own delegate, and
4454 // ObjectModel etc. doesn't use one.
4455 return;
4456 }
4457
4460}
4461
4466
4468{
4469 assignedModel = newModel;
4471 emit q_func()->modelChanged();
4472}
4473
4475{
4477 return;
4478
4479 if (model) {
4482 }
4483
4485 QVariant effectiveModelVariant = modelVariant;
4486 if (effectiveModelVariant.userType() == qMetaTypeId<QJSValue>())
4487 effectiveModelVariant = effectiveModelVariant.value<QJSValue>().toVariant();
4488
4489 const auto instanceModel = qobject_cast<QQmlInstanceModel *>(qvariant_cast<QObject*>(effectiveModelVariant));
4490
4491 if (instanceModel) {
4492 if (tableModel) {
4493 delete tableModel;
4494 tableModel = nullptr;
4495 }
4496 model = instanceModel;
4497 } else {
4498 if (!tableModel)
4500 tableModel->setModel(effectiveModelVariant);
4501 }
4502
4504}
4505
4507{
4508 Q_Q(QQuickTableView);
4509
4510 if (assignedSyncView != syncView) {
4511 if (syncView)
4512 syncView->d_func()->syncChildren.removeOne(q);
4513
4514 if (assignedSyncView) {
4516
4517 while (view) {
4518 if (view == q) {
4519 if (!layoutWarningIssued) {
4520 layoutWarningIssued = true;
4521 qmlWarning(q) << "TableView: recursive syncView connection detected!";
4522 }
4523 syncView = nullptr;
4524 return;
4525 }
4526 view = view->d_func()->syncView;
4527 }
4528
4529 assignedSyncView->d_func()->syncChildren.append(q);
4531 }
4532
4534 }
4535
4538
4539 if (syncHorizontally) {
4540 QBoolBlocker fixupGuard(inUpdateContentSize, true);
4541 q->setColumnSpacing(syncView->columnSpacing());
4542 q->setLeftMargin(syncView->leftMargin());
4543 q->setRightMargin(syncView->rightMargin());
4545
4546 if (syncView->leftColumn() != q->leftColumn()) {
4547 // The left column is no longer the same as the left
4548 // column in syncView. This requires a rebuild.
4551 }
4552 }
4553
4554 if (syncVertically) {
4555 QBoolBlocker fixupGuard(inUpdateContentSize, true);
4556 q->setRowSpacing(syncView->rowSpacing());
4557 q->setTopMargin(syncView->topMargin());
4558 q->setBottomMargin(syncView->bottomMargin());
4560
4561 if (syncView->topRow() != q->topRow()) {
4562 // The top row is no longer the same as the top
4563 // row in syncView. This requires a rebuild.
4566 }
4567 }
4568
4569 if (syncView && loadedItems.isEmpty() && !tableSize.isEmpty()) {
4570 // When we have a syncView, we can sometimes temporarily end up with no loaded items.
4571 // This can happen if the syncView has a model with more rows or columns than us, in
4572 // which case the viewport can end up in a place where we have no rows or columns to
4573 // show. In that case, check now if the viewport has been flicked back again, and
4574 // that we can rebuild the table with a visible top-left cell.
4575 const auto syncView_d = syncView->d_func();
4576 if (!syncView_d->loadedItems.isEmpty()) {
4577 if (syncHorizontally && syncView_d->leftColumn() <= tableSize.width() - 1)
4579 else if (syncVertically && syncView_d->topRow() <= tableSize.height() - 1)
4581 }
4582 }
4583}
4584
4586{
4587 // Only positionViewAtRowAfterRebuild/positionViewAtColumnAfterRebuild are critical
4588 // to sync before a rebuild to avoid them being overwritten
4589 // by the setters while building. The other position properties
4590 // can change without it causing trouble.
4593}
4594
4596{
4597 Q_Q(QQuickTableView);
4599
4602 QObjectPrivate::connect(model, &QQmlTableInstanceModel::itemPooled, this, &QQuickTableViewPrivate::itemPooledCallback);
4603 QObjectPrivate::connect(model, &QQmlTableInstanceModel::itemReused, this, &QQuickTableViewPrivate::itemReusedCallback);
4604
4605 // Connect atYEndChanged to a function that fetches data if more is available
4607
4608 if (auto const aim = model->abstractItemModel()) {
4609 // When the model exposes a QAIM, we connect to it directly. This means that if the current model is
4610 // a QQmlDelegateModel, we just ignore all the change sets it emits. In most cases, the model will instead
4611 // be our own QQmlTableInstanceModel, which doesn't bother creating change sets at all. For models that are
4612 // not based on QAIM (like QQmlObjectModel, QQmlListModel, javascript arrays etc), there is currently no way
4613 // to modify the model at runtime without also re-setting the model on the view.
4622 } else {
4624 }
4625}
4626
4628{
4629 Q_Q(QQuickTableView);
4631
4634 QObjectPrivate::disconnect(model, &QQmlTableInstanceModel::itemPooled, this, &QQuickTableViewPrivate::itemPooledCallback);
4635 QObjectPrivate::disconnect(model, &QQmlTableInstanceModel::itemReused, this, &QQuickTableViewPrivate::itemReusedCallback);
4636
4638
4639 if (auto const aim = model->abstractItemModel()) {
4648 } else {
4650 }
4651}
4652
4663
4664void QQuickTableViewPrivate::rowsMovedCallback(const QModelIndex &parent, int, int, const QModelIndex &, int )
4665{
4666 if (parent != QModelIndex())
4667 return;
4668
4670}
4671
4673{
4674 if (parent != QModelIndex())
4675 return;
4676
4678}
4679
4687
4689{
4690 Q_Q(QQuickTableView);
4691
4692 if (parent != QModelIndex())
4693 return;
4694
4695 // If editIndex was a part of the removed rows, it will now be invalid.
4696 if (!editIndex.isValid() && editItem)
4697 q->closeEditor();
4698
4700}
4701
4703{
4704 if (parent != QModelIndex())
4705 return;
4706
4707 // Adding a column (or row) can result in the table going from being
4708 // e.g completely inside the viewport to go outside. And in the latter
4709 // case, the user needs to be able to scroll the viewport, also if
4710 // flags such as Flickable.StopAtBounds is in use. So we need to
4711 // update contentWidth to support that case.
4713}
4714
4716{
4717 Q_Q(QQuickTableView);
4718
4719 if (parent != QModelIndex())
4720 return;
4721
4722 // If editIndex was a part of the removed columns, it will now be invalid.
4723 if (!editIndex.isValid() && editItem)
4724 q->closeEditor();
4725
4727}
4728
4730{
4731 Q_UNUSED(parents);
4732 Q_UNUSED(hint);
4733
4735}
4736
4744
4751
4752bool QQuickTableViewPrivate::compareModel(const QVariant& model1, const QVariant& model2) const
4753{
4754 return (model1 == model2 ||
4755 (model1.userType() == qMetaTypeId<QJSValue>() && model2.userType() == qMetaTypeId<QJSValue>() &&
4756 model1.value<QJSValue>().strictlyEquals(model2.value<QJSValue>())));
4757}
4758
4760{
4761 Qt::Alignment verticalAlignment = alignment & (Qt::AlignTop | Qt::AlignVCenter | Qt::AlignBottom);
4762 Q_TABLEVIEW_ASSERT(verticalAlignment, alignment);
4763
4764 if (syncHorizontally) {
4765 syncView->d_func()->positionViewAtRow(row, verticalAlignment, offset, subRect);
4766 } else {
4767 if (!scrollToRow(row, verticalAlignment, offset, subRect)) {
4768 // Could not scroll, so rebuild instead
4770 positionViewAtRowAlignment = verticalAlignment;
4772 positionViewAtRowSubRect = subRect;
4775 }
4776 }
4777}
4778
4780{
4781 Qt::Alignment horizontalAlignment = alignment & (Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight);
4782 Q_TABLEVIEW_ASSERT(horizontalAlignment, alignment);
4783
4784 if (syncVertically) {
4785 syncView->d_func()->positionViewAtColumn(column, horizontalAlignment, offset, subRect);
4786 } else {
4787 if (!scrollToColumn(column, horizontalAlignment, offset, subRect)) {
4788 // Could not scroll, so rebuild instead
4790 positionViewAtColumnAlignment = horizontalAlignment;
4795 }
4796 }
4797}
4798
4799bool QQuickTableViewPrivate::scrollToRow(int row, Qt::Alignment alignment, qreal offset, const QRectF subRect)
4800{
4801 Q_Q(QQuickTableView);
4802
4803 // This function will only scroll to rows that are loaded (since we
4804 // don't know the location of unloaded rows). But as an exception, to
4805 // allow moving currentIndex out of the viewport, we support scrolling
4806 // to a row that is adjacent to the loaded table. So start by checking
4807 // if we should load en extra row.
4808 if (row < topRow()) {
4810 return false;
4812 } else if (row > bottomRow()) {
4814 return false;
4816 } else if (row < topRow() || row > bottomRow()) {
4817 return false;
4818 }
4819
4820 if (!loadedRows.contains(row))
4821 return false;
4822
4823 const qreal newContentY = getAlignmentContentY(row, alignment, offset, subRect);
4824 if (qFuzzyCompare(newContentY, q->contentY()))
4825 return true;
4826
4827 if (animate) {
4828 const qreal diffY = qAbs(newContentY - q->contentY());
4829 const qreal duration = qBound(700., diffY * 5, 1500.);
4830 positionYAnimation.setTo(newContentY);
4833 } else {
4835 q->setContentY(newContentY);
4836 }
4837
4838 return true;
4839}
4840
4842{
4843 Q_Q(QQuickTableView);
4844
4845 // This function will only scroll to columns that are loaded (since we
4846 // don't know the location of unloaded columns). But as an exception, to
4847 // allow moving currentIndex out of the viewport, we support scrolling
4848 // to a column that is adjacent to the loaded table. So start by checking
4849 // if we should load en extra column.
4850 if (column < leftColumn()) {
4852 return false;
4854 } else if (column > rightColumn()) {
4856 return false;
4858 } else if (column < leftColumn() || column > rightColumn()) {
4859 return false;
4860 }
4861
4863 return false;
4864
4865 const qreal newContentX = getAlignmentContentX(column, alignment, offset, subRect);
4866 if (qFuzzyCompare(newContentX, q->contentX()))
4867 return true;
4868
4869 if (animate) {
4870 const qreal diffX = qAbs(newContentX - q->contentX());
4871 const qreal duration = qBound(700., diffX * 5, 1500.);
4872 positionXAnimation.setTo(newContentX);
4875 } else {
4877 q->setContentX(newContentX);
4878 }
4879
4880 return true;
4881}
4882
4884{
4885 Q_Q(QQuickTableView);
4886 // If the viewport has moved more than one page vertically or horizontally, we switch
4887 // strategy from refilling edges around the current table to instead rebuild the table
4888 // from scratch inside the new viewport. This will greatly improve performance when flicking
4889 // a long distance in one go, which can easily happen when dragging on scrollbars.
4890 // Note that we don't want to update the content size in this case, since first of all, the
4891 // content size should logically not change as a result of flicking. But more importantly, updating
4892 // the content size in combination with fast-flicking has a tendency to cause flicker in the viewport.
4893
4894 // Check the viewport moved more than one page vertically
4895 if (!viewportRect.intersects(QRectF(viewportRect.x(), q->contentY(), 1, q->height()))) {
4898 }
4899
4900 // Check the viewport moved more than one page horizontally
4901 if (!viewportRect.intersects(QRectF(q->contentX(), viewportRect.y(), q->width(), 1))) {
4904 }
4905}
4906
4908{
4909 // Set the new viewport position if changed, but don't trigger any
4910 // rebuilds or updates. We use this function internally to distinguish
4911 // external flicking from internal sync-ing of the content view.
4912 Q_Q(QQuickTableView);
4913 QBoolBlocker blocker(inSetLocalViewportPos, true);
4914
4915 if (qFuzzyCompare(contentX, q->contentX()))
4916 return;
4917
4918 q->setContentX(contentX);
4919}
4920
4922{
4923 // Set the new viewport position if changed, but don't trigger any
4924 // rebuilds or updates. We use this function internally to distinguish
4925 // external flicking from internal sync-ing of the content view.
4926 Q_Q(QQuickTableView);
4927 QBoolBlocker blocker(inSetLocalViewportPos, true);
4928
4929 if (qFuzzyCompare(contentY, q->contentY()))
4930 return;
4931
4932 q->setContentY(contentY);
4933}
4934
4936{
4937 // Sync viewportRect so that it contains the actual geometry of the viewport.
4938 // Since the column (and row) size of a sync child is decided by the column size
4939 // of its sync view, the viewport width of a sync view needs to be the maximum of
4940 // the sync views width, and its sync childrens width. This to ensure that no sync
4941 // child loads a column which is not yet loaded by the sync view, since then the
4942 // implicit column size cannot be resolved.
4943 Q_Q(QQuickTableView);
4944
4945 qreal w = q->width();
4946 qreal h = q->height();
4947
4948 for (auto syncChild : std::as_const(syncChildren)) {
4949 auto syncChild_d = syncChild->d_func();
4950 if (syncChild_d->syncHorizontally)
4951 w = qMax(w, syncChild->width());
4952 if (syncChild_d->syncHorizontally)
4953 h = qMax(h, syncChild->height());
4954 }
4955
4956 viewportRect = QRectF(q->contentX(), q->contentY(), w, h);
4957}
4958
4960{
4961 Q_Q(QQuickTableView);
4962
4964 q->setActiveFocusOnTab(true);
4965
4969
4973
4974 auto tapHandler = new QQuickTableViewTapHandler(q);
4975
4980
4981 // To allow for a more snappy UX, we try to change the current index already upon
4982 // receiving a pointer press. But we should only do that if the view is not interactive
4983 // (so that it doesn't interfere with flicking), and if the resizeHandler is not
4984 // being hovered/dragged. For those cases, we fall back to setting the current index
4985 // on tap instead. A double tap on a resize area should also revert the section size
4986 // back to its implicit size.
4987 QObject::connect(tapHandler, &QQuickTapHandler::pressedChanged, [this, q, tapHandler] {
4988 if (!tapHandler->isPressed())
4989 return;
4990
4993
4994 if (!q->isInteractive())
4995 handleTap(tapHandler->point());
4996 });
4997
4998 QObject::connect(tapHandler, &QQuickTapHandler::singleTapped, [this, q, tapHandler] {
4999 if (q->isInteractive())
5000 handleTap(tapHandler->point());
5001 });
5002
5003 QObject::connect(tapHandler, &QQuickTapHandler::doubleTapped, [this, q, tapHandler] {
5004 const bool resizeRow = resizableRows && hoverHandler->m_row != -1;
5005 const bool resizeColumn = resizableColumns && hoverHandler->m_column != -1;
5006
5007 if (resizeRow || resizeColumn) {
5008 if (resizeRow)
5009 q->setRowHeight(hoverHandler->m_row, -1);
5010 if (resizeColumn)
5011 q->setColumnWidth(hoverHandler->m_column, -1);
5013 const QPointF pos = tapHandler->point().pressPosition();
5014 const QPoint cell = q->cellAtPosition(pos);
5015 const QModelIndex index = q->modelIndex(cell);
5016 if (canEdit(index, false))
5017 q->edit(index);
5018 }
5019 });
5020}
5021
5023{
5024 Q_Q(QQuickTableView);
5025
5027 q->forceActiveFocus(Qt::MouseFocusReason);
5028
5029 if (point.modifiers() != Qt::NoModifier)
5030 return;
5031 if (resizableRows && hoverHandler->m_row != -1)
5032 return;
5034 return;
5036 return;
5037
5038 const QModelIndex tappedIndex = q->modelIndex(q->cellAtPosition(point.position()));
5039 bool tappedCellIsSelected = false;
5040
5041 if (selectionModel)
5042 tappedCellIsSelected = selectionModel->isSelected(tappedIndex);
5043
5044 if (canEdit(tappedIndex, false)) {
5048 q->edit(tappedIndex);
5049 return;
5050 } else if (editTriggers & QQuickTableView::SelectedTapped && tappedCellIsSelected) {
5051 q->edit(tappedIndex);
5052 return;
5053 }
5054 }
5055
5056 // Since the tap didn't result in selecting or editing cells, we clear
5057 // the current selection and move the current index instead.
5059 q->closeEditor();
5063 }
5065 }
5066}
5067
5068bool QQuickTableViewPrivate::canEdit(const QModelIndex tappedIndex, bool warn)
5069{
5070 // Check that a call to edit(tappedIndex) would not
5071 // result in warnings being printed.
5072 Q_Q(QQuickTableView);
5073
5074 if (!tappedIndex.isValid()) {
5075 if (warn)
5076 qmlWarning(q) << "cannot edit: index is not valid!";
5077 return false;
5078 }
5079
5080 if (auto const qaim = model->abstractItemModel()) {
5081 if (!(qaim->flags(tappedIndex) & Qt::ItemIsEditable)) {
5082 if (warn)
5083 qmlWarning(q) << "cannot edit: QAbstractItemModel::flags(index) doesn't contain Qt::ItemIsEditable";
5084 return false;
5085 }
5086 }
5087
5088 const QPoint cell = q->cellAtIndex(tappedIndex);
5089 const QQuickItem *cellItem = q->itemAtCell(cell);
5090 if (!cellItem) {
5091 if (warn)
5092 qmlWarning(q) << "cannot edit: the cell to edit is not inside the viewport!";
5093 return false;
5094 }
5095
5096 auto attached = getAttachedObject(cellItem);
5097 if (!attached || !attached->editDelegate()) {
5098 if (warn)
5099 qmlWarning(q) << "cannot edit: no TableView.editDelegate set!";
5100 return false;
5101 }
5102
5103 return true;
5104}
5105
5107{
5108 Q_Q(QQuickTableView);
5109 QBoolBlocker recursionGuard(inSyncViewportPosRecursive, true);
5110
5111 if (syncView) {
5112 auto syncView_d = syncView->d_func();
5113 if (!syncView_d->inSyncViewportPosRecursive) {
5114 if (syncHorizontally)
5115 syncView_d->setLocalViewportX(q->contentX());
5116 if (syncVertically)
5117 syncView_d->setLocalViewportY(q->contentY());
5118 syncView_d->syncViewportPosRecursive();
5119 }
5120 }
5121
5122 for (auto syncChild : std::as_const(syncChildren)) {
5123 auto syncChild_d = syncChild->d_func();
5124 if (!syncChild_d->inSyncViewportPosRecursive) {
5125 if (syncChild_d->syncHorizontally)
5126 syncChild_d->setLocalViewportX(q->contentX());
5127 if (syncChild_d->syncVertically)
5128 syncChild_d->setLocalViewportY(q->contentY());
5129 syncChild_d->syncViewportPosRecursive();
5130 }
5131 }
5132}
5133
5135{
5136 Q_Q(QQuickTableView);
5137
5138 const QPoint cell = q->cellAtPosition(pos);
5139 if (!cellIsValid(cell))
5140 return;
5141
5142 setCurrentIndex(cell);
5143}
5144
5146{
5147 if (!selectionModel)
5148 return;
5149
5150 const auto index = q_func()->modelIndex(cell);
5152}
5153
5155{
5156 Q_Q(QQuickTableView);
5157
5159 return false;
5160
5161 const QModelIndex currentIndex = selectionModel->currentIndex();
5162 const QPoint currentCell = q->cellAtIndex(currentIndex);
5163
5164 if (!q->activeFocusOnTab()) {
5165 switch (e->key()) {
5166 case Qt::Key_Tab:
5167 case Qt::Key_Backtab:
5168 return false;
5169 }
5170 }
5171
5172 if (!cellIsValid(currentCell)) {
5173 switch (e->key()) {
5174 case Qt::Key_Up:
5175 case Qt::Key_Down:
5176 case Qt::Key_Left:
5177 case Qt::Key_Right:
5178 case Qt::Key_PageUp:
5179 case Qt::Key_PageDown:
5180 case Qt::Key_Home:
5181 case Qt::Key_End:
5182 case Qt::Key_Tab:
5183 case Qt::Key_Backtab:
5184 // Special case: the current index doesn't map to a cell in the view (perhaps
5185 // because it isn't set yet). In that case, we set it to be the top-left cell.
5186 const QModelIndex topLeftIndex = q->index(topRow(), leftColumn());
5188 return true;
5189 }
5190 return false;
5191 }
5192
5193 auto beginMoveCurrentIndex = [&](){
5194 const bool shouldSelect = (e->modifiers() & Qt::ShiftModifier) && (e->key() != Qt::Key_Backtab);
5195 const bool startNewSelection = selectionRectangle().isEmpty();
5196 if (!shouldSelect) {
5199 } else if (startNewSelection) {
5200 // Try to start a new selection if no selection exists from before.
5201 // The startSelection() call is theoretically allowed to refuse, although this
5202 // is less likely when starting a selection using the keyboard.
5203 const int serializedStartIndex = modelIndexToCellIndex(selectionModel->currentIndex());
5204 if (loadedItems.contains(serializedStartIndex)) {
5205 const QRectF startGeometry = loadedItems.value(serializedStartIndex)->geometry();
5206 if (startSelection(startGeometry.center(), Qt::ShiftModifier)) {
5207 setSelectionStartPos(startGeometry.center());
5210 }
5211 }
5212 }
5213 };
5214
5215 auto endMoveCurrentIndex = [&](const QPoint &cell){
5216 const bool isSelecting = selectionFlag != QItemSelectionModel::NoUpdate;
5217 if (isSelecting) {
5218 if (polishScheduled)
5219 forceLayout(true);
5220 const int serializedEndIndex = modelIndexAtCell(cell);
5221 if (loadedItems.contains(serializedEndIndex)) {
5222 const QRectF endGeometry = loadedItems.value(serializedEndIndex)->geometry();
5223 setSelectionEndPos(endGeometry.center());
5226 }
5227 }
5229 };
5230
5231 switch (e->key()) {
5232 case Qt::Key_Up: {
5233 beginMoveCurrentIndex();
5234 const int nextRow = nextVisibleEdgeIndex(Qt::TopEdge, currentCell.y() - 1);
5235 if (nextRow == kEdgeIndexAtEnd)
5236 break;
5237 const qreal marginY = atTableEnd(Qt::TopEdge, nextRow - 1) ? -q->topMargin() : 0;
5238 q->positionViewAtRow(nextRow, QQuickTableView::Contain, marginY);
5239 endMoveCurrentIndex({currentCell.x(), nextRow});
5240 break; }
5241 case Qt::Key_Down: {
5242 beginMoveCurrentIndex();
5243 const int nextRow = nextVisibleEdgeIndex(Qt::BottomEdge, currentCell.y() + 1);
5244 if (nextRow == kEdgeIndexAtEnd)
5245 break;
5246 const qreal marginY = atTableEnd(Qt::BottomEdge, nextRow + 1) ? q->bottomMargin() : 0;
5247 q->positionViewAtRow(nextRow, QQuickTableView::Contain, marginY);
5248 endMoveCurrentIndex({currentCell.x(), nextRow});
5249 break; }
5250 case Qt::Key_Left: {
5251 beginMoveCurrentIndex();
5252 const int nextColumn = nextVisibleEdgeIndex(Qt::LeftEdge, currentCell.x() - 1);
5253 if (nextColumn == kEdgeIndexAtEnd)
5254 break;
5255 const qreal marginX = atTableEnd(Qt::LeftEdge, nextColumn - 1) ? -q->leftMargin() : 0;
5256 q->positionViewAtColumn(nextColumn, QQuickTableView::Contain, marginX);
5257 endMoveCurrentIndex({nextColumn, currentCell.y()});
5258 break; }
5259 case Qt::Key_Right: {
5260 beginMoveCurrentIndex();
5261 const int nextColumn = nextVisibleEdgeIndex(Qt::RightEdge, currentCell.x() + 1);
5262 if (nextColumn == kEdgeIndexAtEnd)
5263 break;
5264 const qreal marginX = atTableEnd(Qt::RightEdge, nextColumn + 1) ? q->rightMargin() : 0;
5265 q->positionViewAtColumn(nextColumn, QQuickTableView::Contain, marginX);
5266 endMoveCurrentIndex({nextColumn, currentCell.y()});
5267 break; }
5268 case Qt::Key_PageDown: {
5269 int newBottomRow = -1;
5270 beginMoveCurrentIndex();
5271 if (currentCell.y() < bottomRow()) {
5272 // The first PageDown should just move currentIndex to the bottom
5273 newBottomRow = bottomRow();
5274 q->positionViewAtRow(newBottomRow, QQuickTableView::AlignBottom, 0);
5275 } else {
5276 q->positionViewAtRow(bottomRow(), QQuickTableView::AlignTop, 0);
5278 newBottomRow = topRow() != bottomRow() ? bottomRow() : bottomRow() + 1;
5279 const qreal marginY = atTableEnd(Qt::BottomEdge, newBottomRow + 1) ? q->bottomMargin() : 0;
5280 q->positionViewAtRow(newBottomRow, QQuickTableView::AlignTop | QQuickTableView::AlignBottom, marginY);
5282 }
5283 endMoveCurrentIndex(QPoint(currentCell.x(), newBottomRow));
5284 break; }
5285 case Qt::Key_PageUp: {
5286 int newTopRow = -1;
5287 beginMoveCurrentIndex();
5288 if (currentCell.y() > topRow()) {
5289 // The first PageUp should just move currentIndex to the top
5290 newTopRow = topRow();
5291 q->positionViewAtRow(newTopRow, QQuickTableView::AlignTop, 0);
5292 } else {
5293 q->positionViewAtRow(topRow(), QQuickTableView::AlignBottom, 0);
5295 newTopRow = topRow() != bottomRow() ? topRow() : topRow() - 1;
5296 const qreal marginY = atTableEnd(Qt::TopEdge, newTopRow - 1) ? -q->topMargin() : 0;
5297 q->positionViewAtRow(newTopRow, QQuickTableView::AlignTop, marginY);
5299 }
5300 endMoveCurrentIndex(QPoint(currentCell.x(), newTopRow));
5301 break; }
5302 case Qt::Key_Home: {
5303 beginMoveCurrentIndex();
5304 const int firstColumn = nextVisibleEdgeIndex(Qt::RightEdge, 0);
5305 q->positionViewAtColumn(firstColumn, QQuickTableView::AlignLeft, -q->leftMargin());
5306 endMoveCurrentIndex(QPoint(firstColumn, currentCell.y()));
5307 break; }
5308 case Qt::Key_End: {
5309 beginMoveCurrentIndex();
5310 const int lastColumn = nextVisibleEdgeIndex(Qt::LeftEdge, tableSize.width() - 1);
5311 q->positionViewAtColumn(lastColumn, QQuickTableView::AlignRight, q->rightMargin());
5312 endMoveCurrentIndex(QPoint(lastColumn, currentCell.y()));
5313 break; }
5314 case Qt::Key_Tab: {
5315 beginMoveCurrentIndex();
5316 int nextRow = currentCell.y();
5317 int nextColumn = nextVisibleEdgeIndex(Qt::RightEdge, currentCell.x() + 1);
5318 if (nextColumn == kEdgeIndexAtEnd) {
5319 nextRow = nextVisibleEdgeIndex(Qt::BottomEdge, currentCell.y() + 1);
5320 if (nextRow == kEdgeIndexAtEnd)
5322 nextColumn = nextVisibleEdgeIndex(Qt::RightEdge, 0);
5323 const qreal marginY = atTableEnd(Qt::BottomEdge, nextRow + 1) ? q->bottomMargin() : 0;
5324 q->positionViewAtRow(nextRow, QQuickTableView::Contain, marginY);
5325 }
5326
5327 qreal marginX = 0;
5328 if (atTableEnd(Qt::RightEdge, nextColumn + 1))
5329 marginX = q->leftMargin();
5330 else if (atTableEnd(Qt::LeftEdge, nextColumn - 1))
5331 marginX = -q->leftMargin();
5332
5333 q->positionViewAtColumn(nextColumn, QQuickTableView::Contain, marginX);
5334 endMoveCurrentIndex({nextColumn, nextRow});
5335 break; }
5336 case Qt::Key_Backtab: {
5337 beginMoveCurrentIndex();
5338 int nextRow = currentCell.y();
5339 int nextColumn = nextVisibleEdgeIndex(Qt::LeftEdge, currentCell.x() - 1);
5340 if (nextColumn == kEdgeIndexAtEnd) {
5341 nextRow = nextVisibleEdgeIndex(Qt::TopEdge, currentCell.y() - 1);
5342 if (nextRow == kEdgeIndexAtEnd)
5344 nextColumn = nextVisibleEdgeIndex(Qt::LeftEdge, tableSize.width() - 1);
5345 const qreal marginY = atTableEnd(Qt::TopEdge, nextRow - 1) ? -q->topMargin() : 0;
5346 q->positionViewAtRow(nextRow, QQuickTableView::Contain, marginY);
5347 }
5348
5349 qreal marginX = 0;
5350 if (atTableEnd(Qt::RightEdge, nextColumn + 1))
5351 marginX = q->leftMargin();
5352 else if (atTableEnd(Qt::LeftEdge, nextColumn - 1))
5353 marginX = -q->leftMargin();
5354
5355 q->positionViewAtColumn(nextColumn, QQuickTableView::Contain, marginX);
5356 endMoveCurrentIndex({nextColumn, nextRow});
5357 break; }
5358 default:
5359 return false;
5360 }
5361
5362 return true;
5363}
5364
5366{
5367 Q_Q(QQuickTableView);
5368
5370 return false;
5372 return false;
5373
5375 const QPoint cell = q->cellAtIndex(index);
5376 const QQuickItem *cellItem = q->itemAtCell(cell);
5377 if (!cellItem)
5378 return false;
5379
5380 auto attached = getAttachedObject(cellItem);
5381 if (!attached || !attached->editDelegate())
5382 return false;
5383
5384 bool anyKeyPressed = false;
5385 bool editKeyPressed = false;
5386
5387 switch (e->key()) {
5388 case Qt::Key_Return:
5389 case Qt::Key_Enter:
5390#ifndef Q_OS_MACOS
5391 case Qt::Key_F2:
5392#endif
5393 anyKeyPressed = true;
5394 editKeyPressed = true;
5395 break;
5396 case Qt::Key_Shift:
5397 case Qt::Key_Alt:
5398 case Qt::Key_Control:
5399 case Qt::Key_Meta:
5400 case Qt::Key_Tab:
5401 case Qt::Key_Backtab:
5402 break;
5403 default:
5404 anyKeyPressed = true;
5405 }
5406
5407 const bool anyKeyAccepted = anyKeyPressed && (editTriggers & QQuickTableView::AnyKeyPressed);
5408 const bool editKeyAccepted = editKeyPressed && (editTriggers & QQuickTableView::EditKeyPressed);
5409
5410 if (!(editKeyAccepted || anyKeyAccepted))
5411 return false;
5412
5413 if (!canEdit(index, false)) {
5414 // If canEdit() returns false at this point (e.g because currentIndex is not
5415 // editable), we still want to eat the key event, to keep a consistent behavior
5416 // when some cells are editable, but others not.
5417 return true;
5418 }
5419
5420 q->edit(index);
5421
5422 if (editIndex.isValid() && anyKeyAccepted && !editKeyPressed) {
5423 // Replay the key event to the focus object (which should at this point
5424 // be the edit item, or an item inside the edit item).
5426 }
5427
5428 return true;
5429}
5430
5431#if QT_CONFIG(cursor)
5432void QQuickTableViewPrivate::updateCursor()
5433{
5434 int row = resizableRows ? hoverHandler->m_row : -1;
5436
5437 const auto resizeState = resizeHandler->state();
5439 || resizeState == QQuickTableViewResizeHandler::Dragging) {
5440 // Don't change the cursor while resizing, even if
5441 // the pointer is not actually hovering the grid.
5444 }
5445
5446 if (row != -1 || column != -1) {
5447 Qt::CursorShape shape;
5448 if (row != -1 && column != -1)
5449 shape = Qt::SizeFDiagCursor;
5450 else if (row != -1)
5451 shape = Qt::SplitVCursor;
5452 else
5453 shape = Qt::SplitHCursor;
5454
5455 if (m_cursorSet)
5456 qApp->changeOverrideCursor(shape);
5457 else
5458 qApp->setOverrideCursor(shape);
5459
5460 m_cursorSet = true;
5461 } else if (m_cursorSet) {
5462 qApp->restoreOverrideCursor();
5463 m_cursorSet = false;
5464 }
5465}
5466#endif
5467
5469{
5470 Q_Q(QQuickTableView);
5471
5472 if (!editItem)
5473 return;
5474
5475 const QPoint cell = q->cellAtIndex(editIndex);
5476 auto cellItem = q->itemAtCell(cell);
5477 if (!cellItem) {
5478 // The delegate item that is being edited has left the viewport. But since we
5479 // added an extra reference to it when editing began, the delegate item has
5480 // not been unloaded! It's therefore still on the content item (outside the
5481 // viewport), but its position will no longer be updated until the row and column
5482 // it's a part of enters the viewport again. To avoid glitches related to the
5483 // item showing up on wrong places (e.g after resizing a column in front of it),
5484 // we move it far out of the viewport. This way it will be "hidden", but continue
5485 // to have edit focus. When the row and column that it's a part of are eventually
5486 // flicked back in again, a relayout will move it back to the correct place.
5487 editItem->parentItem()->setX(-editItem->width() - 10000);
5488 }
5489}
5490
5492 : QQuickFlickable(*(new QQuickTableViewPrivate), parent)
5493{
5494 d_func()->init();
5495}
5496
5498 : QQuickFlickable(dd, parent)
5499{
5500 d_func()->init();
5501}
5502
5504{
5505 Q_D(QQuickTableView);
5506
5507 if (d->syncView) {
5508 // Remove this TableView as a sync child from the syncView
5509 auto syncView_d = d->syncView->d_func();
5510 syncView_d->syncChildren.removeOne(this);
5511 syncView_d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly);
5512 }
5513}
5514
5516{
5517 // componentComplete() is called on us after all static values have been assigned, but
5518 // before bindings to any anchestors has been evaluated. Especially this means that
5519 // if our size is bound to the parents size, it will still be empty at that point.
5520 // And we cannot build the table without knowing our own size. We could wait until we
5521 // got the first updatePolish() callback, but at that time, any asynchronous loaders that we
5522 // might be inside have already finished loading, which means that we would load all
5523 // the delegate items synchronously instead of asynchronously. We therefore use componentFinalized
5524 // which gets called after all the bindings we rely on has been evaluated.
5525 // When receiving this call, we load the delegate items (and build the table).
5526
5527 // Now that all bindings are evaluated, and we know
5528 // our final geometery, we can build the table.
5529 Q_D(QQuickTableView);
5530 qCDebug(lcTableViewDelegateLifecycle);
5531 d->updatePolish();
5532}
5533
5535{
5536 return QQuickFlickable::minXExtent() - d_func()->origin.x();
5537}
5538
5540{
5541 return QQuickFlickable::maxXExtent() - d_func()->endExtent.width();
5542}
5543
5545{
5546 return QQuickFlickable::minYExtent() - d_func()->origin.y();
5547}
5548
5550{
5551 return QQuickFlickable::maxYExtent() - d_func()->endExtent.height();
5552}
5553
5555{
5556 return d_func()->tableSize.height();
5557}
5558
5560{
5561 return d_func()->tableSize.width();
5562}
5563
5565{
5566 return d_func()->cellSpacing.height();
5567}
5568
5570{
5571 Q_D(QQuickTableView);
5573 return;
5574 if (qFuzzyCompare(d->cellSpacing.height(), spacing))
5575 return;
5576
5577 d->cellSpacing.setHeight(spacing);
5581}
5582
5584{
5585 return d_func()->cellSpacing.width();
5586}
5587
5589{
5590 Q_D(QQuickTableView);
5592 return;
5593 if (qFuzzyCompare(d->cellSpacing.width(), spacing))
5594 return;
5595
5596 d->cellSpacing.setWidth(spacing);
5600}
5601
5603{
5604 return d_func()->rowHeightProvider;
5605}
5606
5608{
5609 Q_D(QQuickTableView);
5610 if (provider.strictlyEquals(d->rowHeightProvider))
5611 return;
5612
5613 d->rowHeightProvider = provider;
5617}
5618
5620{
5621 return d_func()->columnWidthProvider;
5622}
5623
5625{
5626 Q_D(QQuickTableView);
5627 if (provider.strictlyEquals(d->columnWidthProvider))
5628 return;
5629
5630 d->columnWidthProvider = provider;
5634}
5635
5637{
5638 return d_func()->modelImpl();
5639}
5640
5642{
5643 Q_D(QQuickTableView);
5644 if (d->compareModel(newModel, d->assignedModel))
5645 return;
5646
5647 closeEditor();
5648 d->setModelImpl(newModel);
5649 if (d->selectionModel)
5650 d->selectionModel->setModel(d->selectionSourceModel());
5651}
5652
5654{
5655 return d_func()->assignedDelegate;
5656}
5657
5659{
5660 Q_D(QQuickTableView);
5661 if (newDelegate == d->assignedDelegate)
5662 return;
5663
5664 d->assignedDelegate = newDelegate;
5665 d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::All);
5666
5668}
5669
5670QQuickTableView::EditTriggers QQuickTableView::editTriggers() const
5671{
5672 return d_func()->editTriggers;
5673}
5674
5675void QQuickTableView::setEditTriggers(QQuickTableView::EditTriggers editTriggers)
5676{
5677 Q_D(QQuickTableView);
5678 if (editTriggers == d->editTriggers)
5679 return;
5680
5681 d->editTriggers = editTriggers;
5682
5683 emit editTriggersChanged();
5684}
5685
5687{
5688 return bool(d_func()->reusableFlag == QQmlTableInstanceModel::Reusable);
5689}
5690
5692{
5693 Q_D(QQuickTableView);
5694 if (reuseItems() == reuse)
5695 return;
5696
5698
5699 if (!reuse && d->tableModel) {
5700 // When we're told to not reuse items, we
5701 // immediately, as documented, drain the pool.
5702 d->tableModel->drainReusableItemsPool(0);
5703 }
5704
5706}
5707
5709{
5710 Q_D(QQuickTableView);
5711 d->explicitContentWidth = width;
5713}
5714
5716{
5717 Q_D(QQuickTableView);
5718 d->explicitContentHeight = height;
5720}
5721
5740{
5741 return d_func()->assignedSyncView;
5742}
5743
5745{
5746 Q_D(QQuickTableView);
5747 if (d->assignedSyncView == view)
5748 return;
5749
5750 // Clear existing index mapping information maintained
5751 // in the current view
5752 d->clearIndexMapping();
5753
5754 d->assignedSyncView = view;
5756
5757 emit syncViewChanged();
5758}
5759
5778Qt::Orientations QQuickTableView::syncDirection() const
5779{
5780 return d_func()->assignedSyncDirection;
5781}
5782
5784{
5785 Q_D(QQuickTableView);
5786 if (d->assignedSyncDirection == direction)
5787 return;
5788
5789 d->assignedSyncDirection = direction;
5790 if (d->assignedSyncView)
5792
5793 emit syncDirectionChanged();
5794}
5795
5797{
5798 return d_func()->selectionModel;
5799}
5800
5802{
5803 Q_D(QQuickTableView);
5804 if (d->selectionModel == selectionModel)
5805 return;
5806
5807 // Note: There is no need to rebuild the table when the selection model
5808 // changes, since selections only affect the internals of the delegate
5809 // items, and not the layout of the TableView.
5810
5811 if (d->selectionModel) {
5816 }
5817
5818 d->selectionModel = selectionModel;
5819
5820 if (d->selectionModel) {
5821 d->selectionModel->setModel(d->selectionSourceModel());
5826 }
5827
5828 d->updateSelectedOnAllDelegateItems();
5829
5830 emit selectionModelChanged();
5831}
5832
5834{
5835 return d_func()->animate;
5836}
5837
5839{
5840 Q_D(QQuickTableView);
5841 if (d->animate == animate)
5842 return;
5843
5844 d->animate = animate;
5845 if (!animate) {
5846 d->positionXAnimation.stop();
5847 d->positionYAnimation.stop();
5848 }
5849
5850 emit animateChanged();
5851}
5852
5854{
5855 return d_func()->keyNavigationEnabled;
5856}
5857
5859{
5860 Q_D(QQuickTableView);
5861 if (d->keyNavigationEnabled == enabled)
5862 return;
5863
5864 d->keyNavigationEnabled = enabled;
5865
5866 emit keyNavigationEnabledChanged();
5867}
5868
5870{
5871 return d_func()->pointerNavigationEnabled;
5872}
5873
5875{
5876 Q_D(QQuickTableView);
5877 if (d->pointerNavigationEnabled == enabled)
5878 return;
5879
5880 d->pointerNavigationEnabled = enabled;
5881
5882 emit pointerNavigationEnabledChanged();
5883}
5884
5886{
5887 Q_D(const QQuickTableView);
5888 return d->loadedItems.isEmpty() ? -1 : d_func()->leftColumn();
5889}
5890
5892{
5893 Q_D(const QQuickTableView);
5894 return d->loadedItems.isEmpty() ? -1 : d_func()->rightColumn();
5895}
5896
5898{
5899 Q_D(const QQuickTableView);
5900 return d->loadedItems.isEmpty() ? -1 : d_func()->topRow();
5901}
5902
5904{
5905 Q_D(const QQuickTableView);
5906 return d->loadedItems.isEmpty() ? -1 : d_func()->bottomRow();
5907}
5908
5910{
5911 return d_func()->currentRow;
5912}
5913
5915{
5916 return d_func()->currentColumn;
5917}
5918
5919void QQuickTableView::positionViewAtRow(int row, PositionMode mode, qreal offset, const QRectF &subRect)
5920{
5921 Q_D(QQuickTableView);
5922 if (row < 0 || row >= rows() || d->loadedRows.isEmpty())
5923 return;
5924
5925 // Note: PositionMode::Contain is from here on translated to (Qt::AlignTop | Qt::AlignBottom).
5926 // This is an internal (unsupported) combination which means "align bottom if the whole cell
5927 // fits inside the viewport, otherwise align top".
5928
5929 if (mode & (AlignTop | AlignBottom | AlignVCenter)) {
5931 d->positionViewAtRow(row, Qt::Alignment(int(mode)), offset, subRect);
5932 } else if (mode == Contain) {
5933 if (row < topRow()) {
5934 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
5935 } else if (row > bottomRow()) {
5936 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5937 } else if (row == topRow()) {
5938 if (!subRect.isValid()) {
5939 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
5940 } else {
5941 const qreal subRectTop = d->loadedTableOuterRect.top() + subRect.top();
5942 const qreal subRectBottom = d->loadedTableOuterRect.top() + subRect.bottom();
5943 if (subRectTop < d->viewportRect.y())
5944 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
5945 else if (subRectBottom > d->viewportRect.bottom())
5946 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5947 }
5948 } else if (row == bottomRow()) {
5949 if (!subRect.isValid()) {
5950 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5951 } else {
5952 // Note: entering here means that topRow() != bottomRow(). So at least two rows are
5953 // visible in the viewport, which means that the top side of the subRect is visible.
5954 const qreal subRectBottom = d->loadedTableInnerRect.bottom() + subRect.bottom();
5955 if (subRectBottom > d->viewportRect.bottom())
5956 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5957 }
5958 }
5959 } else if (mode == Visible) {
5960 if (row < topRow()) {
5961 d->positionViewAtRow(row, Qt::AlignTop, -offset, subRect);
5962 } else if (row > bottomRow()) {
5963 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5964 } else if (subRect.isValid()) {
5965 if (row == topRow()) {
5966 const qreal subRectTop = d->loadedTableOuterRect.top() + subRect.top();
5967 const qreal subRectBottom = d->loadedTableOuterRect.top() + subRect.bottom();
5968 if (subRectBottom < d->viewportRect.top())
5969 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
5970 else if (subRectTop > d->viewportRect.bottom())
5971 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5972 } else if (row == bottomRow()) {
5973 // Note: entering here means that topRow() != bottomRow(). So at least two rows are
5974 // visible in the viewport, which means that the top side of the subRect is visible.
5975 const qreal subRectTop = d->loadedTableInnerRect.bottom() + subRect.top();
5976 if (subRectTop > d->viewportRect.bottom())
5977 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
5978 }
5979 }
5980 } else {
5981 qmlWarning(this) << "Unsupported mode:" << int(mode);
5982 }
5983}
5984
5985void QQuickTableView::positionViewAtColumn(int column, PositionMode mode, qreal offset, const QRectF &subRect)
5986{
5987 Q_D(QQuickTableView);
5988 if (column < 0 || column >= columns() || d->loadedColumns.isEmpty())
5989 return;
5990
5991 // Note: PositionMode::Contain is from here on translated to (Qt::AlignLeft | Qt::AlignRight).
5992 // This is an internal (unsupported) combination which means "align right if the whole cell
5993 // fits inside the viewport, otherwise align left".
5994
5995 if (mode & (AlignLeft | AlignRight | AlignHCenter)) {
5997 d->positionViewAtColumn(column, Qt::Alignment(int(mode)), offset, subRect);
5998 } else if (mode == Contain) {
5999 if (column < leftColumn()) {
6000 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
6001 } else if (column > rightColumn()) {
6002 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6003 } else if (column == leftColumn()) {
6004 if (!subRect.isValid()) {
6005 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
6006 } else {
6007 const qreal subRectLeft = d->loadedTableOuterRect.left() + subRect.left();
6008 const qreal subRectRight = d->loadedTableOuterRect.left() + subRect.right();
6009 if (subRectLeft < d->viewportRect.left())
6010 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
6011 else if (subRectRight > d->viewportRect.right())
6012 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6013 }
6014 } else if (column == rightColumn()) {
6015 if (!subRect.isValid()) {
6016 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6017 } else {
6018 // Note: entering here means that leftColumn() != rightColumn(). So at least two columns
6019 // are visible in the viewport, which means that the left side of the subRect is visible.
6020 const qreal subRectRight = d->loadedTableInnerRect.right() + subRect.right();
6021 if (subRectRight > d->viewportRect.right())
6022 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6023 }
6024 }
6025 } else if (mode == Visible) {
6026 if (column < leftColumn()) {
6027 d->positionViewAtColumn(column, Qt::AlignLeft, -offset, subRect);
6028 } else if (column > rightColumn()) {
6029 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6030 } else if (subRect.isValid()) {
6031 if (column == leftColumn()) {
6032 const qreal subRectLeft = d->loadedTableOuterRect.left() + subRect.left();
6033 const qreal subRectRight = d->loadedTableOuterRect.left() + subRect.right();
6034 if (subRectRight < d->viewportRect.left())
6035 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
6036 else if (subRectLeft > d->viewportRect.right())
6037 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6038 } else if (column == rightColumn()) {
6039 // Note: entering here means that leftColumn() != rightColumn(). So at least two columns
6040 // are visible in the viewport, which means that the left side of the subRect is visible.
6041 const qreal subRectLeft = d->loadedTableInnerRect.right() + subRect.left();
6042 if (subRectLeft > d->viewportRect.right())
6043 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6044 }
6045 }
6046 } else {
6047 qmlWarning(this) << "Unsupported mode:" << int(mode);
6048 }
6049}
6050
6051void QQuickTableView::positionViewAtCell(const QPoint &cell, PositionMode mode, const QPointF &offset, const QRectF &subRect)
6052{
6053 PositionMode horizontalMode = mode & ~(AlignTop | AlignBottom | AlignVCenter);
6054 PositionMode verticalMode = mode & ~(AlignLeft | AlignRight | AlignHCenter);
6055 if (!horizontalMode && !verticalMode) {
6056 qmlWarning(this) << "Unsupported mode:" << int(mode);
6057 return;
6058 }
6059
6060 if (horizontalMode)
6061 positionViewAtColumn(cell.x(), horizontalMode, offset.x(), subRect);
6062 if (verticalMode)
6063 positionViewAtRow(cell.y(), verticalMode, offset.y(), subRect);
6064}
6065
6066void QQuickTableView::positionViewAtIndex(const QModelIndex &index, PositionMode mode, const QPointF &offset, const QRectF &subRect)
6067{
6068 PositionMode horizontalMode = mode & ~(AlignTop | AlignBottom | AlignVCenter);
6069 PositionMode verticalMode = mode & ~(AlignLeft | AlignRight | AlignHCenter);
6070 if (!horizontalMode && !verticalMode) {
6071 qmlWarning(this) << "Unsupported mode:" << int(mode);
6072 return;
6073 }
6074
6075 if (horizontalMode)
6076 positionViewAtColumn(columnAtIndex(index), horizontalMode, offset.x(), subRect);
6077 if (verticalMode)
6078 positionViewAtRow(rowAtIndex(index), verticalMode, offset.y(), subRect);
6079}
6080
6081#if QT_DEPRECATED_SINCE(6, 5)
6082void QQuickTableView::positionViewAtCell(int column, int row, PositionMode mode, const QPointF &offset, const QRectF &subRect)
6083{
6084 PositionMode horizontalMode = mode & ~(AlignTop | AlignBottom | AlignVCenter);
6085 PositionMode verticalMode = mode & ~(AlignLeft | AlignRight | AlignHCenter);
6086 if (!horizontalMode && !verticalMode) {
6087 qmlWarning(this) << "Unsupported mode:" << int(mode);
6088 return;
6089 }
6090
6091 if (horizontalMode)
6092 positionViewAtColumn(column, horizontalMode, offset.x(), subRect);
6093 if (verticalMode)
6094 positionViewAtRow(row, verticalMode, offset.y(), subRect);
6095}
6096#endif
6097
6098void QQuickTableView::moveColumn(int source, int destination)
6099{
6100 Q_D(QQuickTableView);
6101 d->moveSection(source, destination, Qt::Horizontal);
6102}
6103
6104void QQuickTableView::moveRow(int source, int destination)
6105{
6106 Q_D(QQuickTableView);
6107 d->moveSection(source, destination, Qt::Vertical);
6108}
6109
6110void QQuickTableViewPrivate::moveSection(int source, int destination, Qt::Orientations orientation)
6111{
6112 Q_Q(QQuickTableView);
6113
6114 if (source < 0 || destination < 0 ||
6115 (orientation == Qt::Horizontal &&
6116 (source >= tableSize.width() || destination >= tableSize.width())) ||
6117 (orientation == Qt::Vertical &&
6119 return;
6120
6121 if (source == destination)
6122 return;
6123
6126 if (syncView)
6127 syncView->d_func()->moveSection(source, destination, orientation);
6128 else {
6129 // Initialize the visual and logical index mapping
6131
6132 // Set current index mapping according to moving rows or columns
6133 SectionData *visualIndex = nullptr;
6134 SectionData *logicalIndex = nullptr;
6135
6136 if (orientation == Qt::Horizontal) {
6137 visualIndex = visualIndices[0].data();
6138 logicalIndex = logicalIndices[0].data();
6139 } else if (orientation == Qt::Vertical) {
6140 visualIndex = visualIndices[1].data();
6141 logicalIndex = logicalIndices[1].data();
6142 }
6143
6144 const int logical = logicalIndex[source].index;
6145 int visual = source;
6146
6147 if (destination > source) {
6148 while (visual < destination) {
6149 SectionData &visualData = visualIndex[logicalIndex[visual + 1].index];
6150 SectionData &logicalData = logicalIndex[visual];
6151 visualData.prevIndex = visualData.index;
6152 visualData.index = visual;
6153 logicalData.prevIndex = logicalData.index;
6154 logicalData.index = logicalIndex[visual + 1].index;
6155 ++visual;
6156 }
6157 } else {
6158 while (visual > destination) {
6159 SectionData &visualData = visualIndex[logicalIndex[visual - 1].index];
6160 SectionData &logicalData = logicalIndex[visual];
6161 visualData.prevIndex = visualData.index;
6162 visualData.index = visual;
6163 logicalData.prevIndex = logicalData.index;
6164 logicalData.index = logicalIndex[visual - 1].index;
6165 --visual;
6166 }
6167 }
6168
6169 visualIndex[logical].prevIndex = visualIndex[logical].index;
6170 visualIndex[logical].index = destination;
6171 logicalIndex[destination].prevIndex = logicalIndex[destination].index;
6172 logicalIndex[destination].index = logical;
6173
6174 // Trigger section move for horizontal and vertical child views
6175 // Used in a case where moveSection() triggered for table view
6176 for (auto syncChild : std::as_const(syncChildren)) {
6177 auto syncChild_d = syncChild->d_func();
6178 if (syncChild_d->m_sectionState != SectionState::Moving &&
6179 ((syncChild_d->syncHorizontally && orientation == Qt::Horizontal) ||
6180 (syncChild_d->syncVertically && orientation == Qt::Vertical)))
6181 syncChild_d->moveSection(source, destination, orientation);
6182 }
6183 }
6184
6185 // Rebuild the view to reflect the section order
6188
6189 // Emit section moved signal for the sections moved in the view
6190 const int startIndex = (source > destination) ? destination : source;
6191 const int endIndex = (source > destination) ? source : destination;
6192 const int mapIndex = static_cast<int>(orientation) - 1;
6193 for (int index = startIndex; index <= endIndex; index++) {
6194 const SectionData *logicalDataIndices = (syncView ? syncView->d_func()->logicalIndices[mapIndex].constData() : logicalIndices[mapIndex].constData());
6195 const SectionData *visualDataIndices = syncView ? syncView->d_func()->visualIndices[mapIndex].constData() : visualIndices[mapIndex].constData();
6196 const int prevLogicalIndex = logicalDataIndices[index].prevIndex;
6197 if (orientation == Qt::Horizontal)
6198 emit q->columnMoved(prevLogicalIndex, visualDataIndices[prevLogicalIndex].prevIndex, visualDataIndices[prevLogicalIndex].index);
6199 else
6200 emit q->rowMoved(prevLogicalIndex, visualDataIndices[prevLogicalIndex].prevIndex, visualDataIndices[prevLogicalIndex].index);
6201 }
6202 }
6203}
6204
6205void QQuickTableView::clearColumnReordering()
6206{
6207 Q_D(QQuickTableView);
6208 d->clearSection(Qt::Horizontal);
6209}
6210
6211void QQuickTableView::clearRowReordering()
6212{
6213 Q_D(QQuickTableView);
6214 d->clearSection(Qt::Vertical);
6215}
6216
6217void QQuickTableViewPrivate::clearSection(Qt::Orientations orientation)
6218{
6219 Q_Q(QQuickTableView);
6220
6221 const int mapIndex = static_cast<int>(orientation) - 1;
6222 const QList<SectionData> oldLogicalIndices = syncView ? syncView->d_func()->logicalIndices[mapIndex] : logicalIndices[mapIndex];
6223 const QList<SectionData> oldVisualIndices = syncView ? syncView->d_func()->visualIndices[mapIndex] : visualIndices[mapIndex];;
6224
6225 if (syncView)
6226 syncView->d_func()->clearSection(orientation);
6227 else {
6228 // Clear the index mapping and rebuild the table
6229 logicalIndices[mapIndex].clear();
6230 visualIndices[mapIndex].clear();
6232 }
6233
6234 // Emit section moved signal for the sections moved in the view
6235 for (int index = 0; index < oldLogicalIndices.size(); index++) {
6236 const SectionData *logicalDataIndices = oldLogicalIndices.constData();
6237 const SectionData *visualDataIndices = oldVisualIndices.constData();
6238 if (logicalDataIndices[index].index != index) {
6239 const int currentIndex = logicalDataIndices[index].index;
6240 if (orientation == Qt::Horizontal)
6241 emit q->columnMoved(currentIndex, visualDataIndices[currentIndex].index, index);
6242 else
6243 emit q->rowMoved(currentIndex, visualDataIndices[currentIndex].index, index);
6244 }
6245 }
6246}
6247
6249{
6250 Q_D(const QQuickTableView);
6251 const int modelIndex = d->modelIndexAtCell(cell);
6252 if (!d->loadedItems.contains(modelIndex))
6253 return nullptr;
6254 return d->loadedItems.value(modelIndex)->item;
6255}
6256
6257#if QT_DEPRECATED_SINCE(6, 5)
6259{
6260 return itemAtCell(QPoint(column, row));
6261}
6262#endif
6263
6264QQuickItem *QQuickTableView::itemAtIndex(const QModelIndex &index) const
6265{
6266 Q_D(const QQuickTableView);
6267 const int serializedIndex = d->modelIndexToCellIndex(index);
6268 if (!d->loadedItems.contains(serializedIndex))
6269 return nullptr;
6270 return d->loadedItems.value(serializedIndex)->item;
6271}
6272
6273#if QT_DEPRECATED_SINCE(6, 4)
6274QPoint QQuickTableView::cellAtPos(qreal x, qreal y, bool includeSpacing) const
6275{
6276 return cellAtPosition(mapToItem(contentItem(), {x, y}), includeSpacing);
6277}
6278
6279QPoint QQuickTableView::cellAtPos(const QPointF &position, bool includeSpacing) const
6280{
6281 return cellAtPosition(mapToItem(contentItem(), position), includeSpacing);
6282}
6283#endif
6284
6285QPoint QQuickTableView::cellAtPosition(qreal x, qreal y, bool includeSpacing) const
6286{
6287 return cellAtPosition(QPoint(x, y), includeSpacing);
6288}
6289
6290QPoint QQuickTableView::cellAtPosition(const QPointF &position, bool includeSpacing) const
6291{
6292 Q_D(const QQuickTableView);
6293
6294 if (!d->loadedTableOuterRect.contains(position))
6295 return QPoint(-1, -1);
6296
6297 const qreal hSpace = d->cellSpacing.width();
6298 const qreal vSpace = d->cellSpacing.height();
6299 qreal currentColumnEnd = d->loadedTableOuterRect.x();
6300 qreal currentRowEnd = d->loadedTableOuterRect.y();
6301
6302 int foundColumn = -1;
6303 int foundRow = -1;
6304
6305 for (const int column : d->loadedColumns) {
6306 currentColumnEnd += d->getEffectiveColumnWidth(column);
6307 if (position.x() < currentColumnEnd) {
6308 foundColumn = column;
6309 break;
6310 }
6311 currentColumnEnd += hSpace;
6312 if (!includeSpacing && position.x() < currentColumnEnd) {
6313 // Hit spacing
6314 return QPoint(-1, -1);
6315 } else if (includeSpacing && position.x() < currentColumnEnd - (hSpace / 2)) {
6316 foundColumn = column;
6317 break;
6318 }
6319 }
6320
6321 for (const int row : d->loadedRows) {
6322 currentRowEnd += d->getEffectiveRowHeight(row);
6323 if (position.y() < currentRowEnd) {
6324 foundRow = row;
6325 break;
6326 }
6327 currentRowEnd += vSpace;
6328 if (!includeSpacing && position.y() < currentRowEnd) {
6329 // Hit spacing
6330 return QPoint(-1, -1);
6331 }
6332 if (includeSpacing && position.y() < currentRowEnd - (vSpace / 2)) {
6333 foundRow = row;
6334 break;
6335 }
6336 }
6337
6338 return QPoint(foundColumn, foundRow);
6339}
6340
6341bool QQuickTableView::isColumnLoaded(int column) const
6342{
6343 Q_D(const QQuickTableView);
6344 if (!d->loadedColumns.contains(column))
6345 return false;
6346
6347 if (d->rebuildState != QQuickTableViewPrivate::RebuildState::Done) {
6348 // TableView is rebuilding, and none of the rows and columns
6349 // are completely loaded until we reach the layout phase.
6351 return false;
6352 }
6353
6354 return true;
6355}
6356
6357bool QQuickTableView::isRowLoaded(int row) const
6358{
6359 Q_D(const QQuickTableView);
6360 if (!d->loadedRows.contains(row))
6361 return false;
6362
6363 if (d->rebuildState != QQuickTableViewPrivate::RebuildState::Done) {
6364 // TableView is rebuilding, and none of the rows and columns
6365 // are completely loaded until we reach the layout phase.
6367 return false;
6368 }
6369
6370 return true;
6371}
6372
6373qreal QQuickTableView::columnWidth(int column) const
6374{
6375 Q_D(const QQuickTableView);
6376 if (!isColumnLoaded(column))
6377 return -1;
6378
6379 return d->getEffectiveColumnWidth(column);
6380}
6381
6382qreal QQuickTableView::rowHeight(int row) const
6383{
6384 Q_D(const QQuickTableView);
6385 if (!isRowLoaded(row))
6386 return -1;
6387
6388 return d->getEffectiveRowHeight(row);
6389}
6390
6391qreal QQuickTableView::implicitColumnWidth(int column) const
6392{
6393 Q_D(const QQuickTableView);
6394 if (!isColumnLoaded(column))
6395 return -1;
6396
6397 return d->sizeHintForColumn(column);
6398}
6399
6400qreal QQuickTableView::implicitRowHeight(int row) const
6401{
6402 Q_D(const QQuickTableView);
6403 if (!isRowLoaded(row))
6404 return -1;
6405
6406 return d->sizeHintForRow(row);
6407}
6408
6409void QQuickTableView::setColumnWidth(int column, qreal size)
6410{
6411 Q_D(QQuickTableView);
6412 if (column < 0) {
6413 qmlWarning(this) << "column must be greather than, or equal to, zero";
6414 return;
6415 }
6416
6417 if (d->syncHorizontally) {
6418 d->syncView->setColumnWidth(column, size);
6419 return;
6420 }
6421
6422 if (qFuzzyCompare(explicitColumnWidth(column), size))
6423 return;
6424
6425 if (size < 0)
6426 d->explicitColumnWidths.remove(d->logicalColumnIndex(column));
6427 else
6428 d->explicitColumnWidths.insert(d->logicalColumnIndex(column), size);
6429
6430 if (d->loadedItems.isEmpty())
6431 return;
6432
6433 const bool allColumnsLoaded = d->atTableEnd(Qt::LeftEdge) && d->atTableEnd(Qt::RightEdge);
6434 if (column >= leftColumn() || column <= rightColumn() || allColumnsLoaded)
6435 d->forceLayout(false);
6436}
6437
6438void QQuickTableView::clearColumnWidths()
6439{
6440 Q_D(QQuickTableView);
6441
6442 if (d->syncHorizontally) {
6443 d->syncView->clearColumnWidths();
6444 return;
6445 }
6446
6447 if (d->explicitColumnWidths.isEmpty())
6448 return;
6449
6450 d->explicitColumnWidths.clear();
6451 d->forceLayout(false);
6452}
6453
6454qreal QQuickTableView::explicitColumnWidth(int column) const
6455{
6456 Q_D(const QQuickTableView);
6457
6458 if (d->syncHorizontally)
6459 return d->syncView->explicitColumnWidth(column);
6460
6461 const auto it = d->explicitColumnWidths.constFind(d->logicalColumnIndex(column));
6462 if (it != d->explicitColumnWidths.constEnd())
6463 return *it;
6464 return -1;
6465}
6466
6467void QQuickTableView::setRowHeight(int row, qreal size)
6468{
6469 Q_D(QQuickTableView);
6470 if (row < 0) {
6471 qmlWarning(this) << "row must be greather than, or equal to, zero";
6472 return;
6473 }
6474
6475 if (d->syncVertically) {
6476 d->syncView->setRowHeight(row, size);
6477 return;
6478 }
6479
6480 if (qFuzzyCompare(explicitRowHeight(row), size))
6481 return;
6482
6483 if (size < 0)
6484 d->explicitRowHeights.remove(d->logicalRowIndex(row));
6485 else
6486 d->explicitRowHeights.insert(d->logicalRowIndex(row), size);
6487
6488 if (d->loadedItems.isEmpty())
6489 return;
6490
6491 const bool allRowsLoaded = d->atTableEnd(Qt::TopEdge) && d->atTableEnd(Qt::BottomEdge);
6492 if (row >= topRow() || row <= bottomRow() || allRowsLoaded)
6493 d->forceLayout(false);
6494}
6495
6496void QQuickTableView::clearRowHeights()
6497{
6498 Q_D(QQuickTableView);
6499
6500 if (d->syncVertically) {
6501 d->syncView->clearRowHeights();
6502 return;
6503 }
6504
6505 if (d->explicitRowHeights.isEmpty())
6506 return;
6507
6508 d->explicitRowHeights.clear();
6509 d->forceLayout(false);
6510}
6511
6512qreal QQuickTableView::explicitRowHeight(int row) const
6513{
6514 Q_D(const QQuickTableView);
6515
6516 if (d->syncVertically)
6517 return d->syncView->explicitRowHeight(row);
6518
6519 const auto it = d->explicitRowHeights.constFind(d->logicalRowIndex(row));
6520 if (it != d->explicitRowHeights.constEnd())
6521 return *it;
6522 return -1;
6523}
6524
6525QModelIndex QQuickTableView::modelIndex(const QPoint &cell) const
6526{
6527 Q_D(const QQuickTableView);
6528 if (cell.x() < 0 || cell.x() >= columns() || cell.y() < 0 || cell.y() >= rows())
6529 return {};
6530
6531 auto const qaim = d->model->abstractItemModel();
6532 if (!qaim)
6533 return {};
6534
6535 return qaim->index(d->logicalRowIndex(cell.y()), d->logicalColumnIndex(cell.x()));
6536}
6537
6538QPoint QQuickTableView::cellAtIndex(const QModelIndex &index) const
6539{
6540 if (!index.isValid() || index.parent().isValid())
6541 return {-1, -1};
6542 Q_D(const QQuickTableView);
6543 return {d->visualColumnIndex(index.column()), d->visualRowIndex(index.row())};
6544}
6545
6546#if QT_DEPRECATED_SINCE(6, 4)
6547QModelIndex QQuickTableView::modelIndex(int row, int column) const
6548{
6549 static bool compat6_4 = qEnvironmentVariable("QT_QUICK_TABLEVIEW_COMPAT_VERSION") == QStringLiteral("6.4");
6550 if (compat6_4) {
6551 // In Qt 6.4.0 and 6.4.1, a source incompatible change led to row and column
6552 // being documented to be specified in the opposite order.
6553 // QT_QUICK_TABLEVIEW_COMPAT_VERSION can therefore be set to force tableview
6554 // to continue accepting calls to modelIndex(column, row).
6555 return modelIndex({row, column});
6556 } else {
6557 qmlWarning(this) << "modelIndex(row, column) is deprecated. "
6558 "Use index(row, column) instead. For more information, see "
6559 "https://doc.qt.io/qt-6/qml-qtquick-tableview-obsolete.html";
6560 return modelIndex({column, row});
6561 }
6562}
6563#endif
6564
6565QModelIndex QQuickTableView::index(int row, int column) const
6566{
6567 return modelIndex({column, row});
6568}
6569
6570int QQuickTableView::rowAtIndex(const QModelIndex &index) const
6571{
6572 return cellAtIndex(index).y();
6573}
6574
6575int QQuickTableView::columnAtIndex(const QModelIndex &index) const
6576{
6577 return cellAtIndex(index).x();
6578}
6579
6581{
6582 d_func()->forceLayout(true);
6583}
6584
6585void QQuickTableView::edit(const QModelIndex &index)
6586{
6587 Q_D(QQuickTableView);
6588
6589 if (!d->canEdit(index, true))
6590 return;
6591
6592 if (d->editIndex == index)
6593 return;
6594
6595 if (!d->tableModel)
6596 return;
6597
6598 if (!d->editModel) {
6599 d->editModel = new QQmlTableInstanceModel(qmlContext(this));
6600 d->editModel->useImportVersion(d->resolveImportVersion());
6602 [this, d] (int serializedModelIndex, QObject *object) {
6603 // initItemCallback will call setRequiredProperty for each required property in the
6604 // delegate, both for this class, but also also for any subclasses. setRequiredProperty
6605 // is currently dependent of the QQmlTableInstanceModel that was used to create the object
6606 // in order to initialize required properties, so we need to set the editItem variable
6607 // early on, so that we can use it in setRequiredProperty.
6608 const QPoint cell = d->cellAtModelIndex(serializedModelIndex);
6609 d->editIndex = modelIndex({d->visualColumnIndex(cell.x()), d->visualRowIndex(cell.y())});
6610 d->editItem = qmlobject_cast<QQuickItem*>(object);
6611 if (!d->editItem)
6612 return;
6613 // Initialize required properties
6614 d->initItemCallback(serializedModelIndex, object);
6615 const auto cellItem = itemAtCell(cellAtIndex(d->editIndex));
6616 Q_ASSERT(cellItem);
6617 d->editItem->setParentItem(cellItem);
6618 // Move the cell item to the top of the other items, to ensure
6619 // that e.g a focus frame ends up on top of all the cells
6620 cellItem->setZ(2);
6621 });
6622 }
6623
6624 if (d->selectionModel)
6625 d->selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
6626
6627 if (d->editIndex.isValid())
6628 closeEditor();
6629
6630 const auto cellItem = itemAtCell(cellAtIndex(index));
6631 Q_ASSERT(cellItem);
6632 const auto attached = d->getAttachedObject(cellItem);
6633 Q_ASSERT(attached);
6634
6635 d->editModel->setModel(d->tableModel->model());
6636 d->editModel->setDelegate(attached->editDelegate());
6637
6638 const int cellIndex = d->modelIndexToCellIndex(index, false);
6639 QObject* object = d->editModel->object(cellIndex, QQmlIncubator::Synchronous);
6640 if (!object) {
6641 d->editIndex = QModelIndex();
6642 d->editItem = nullptr;
6643 qmlWarning(this) << "cannot edit: TableView.editDelegate could not be instantiated!";
6644 return;
6645 }
6646
6647 // Note: at this point, editIndex and editItem has been set from initItem!
6648
6649 if (!d->editItem) {
6650 qmlWarning(this) << "cannot edit: TableView.editDelegate is not an Item!";
6651 d->editItem = nullptr;
6652 d->editIndex = QModelIndex();
6653 d->editModel->release(object, QQmlInstanceModel::NotReusable);
6654 return;
6655 }
6656
6657 // Reference the cell item once more, so that it doesn't
6658 // get reused or deleted if it leaves the viewport.
6659 d->model->object(cellIndex, QQmlIncubator::Synchronous);
6660
6661 // Inform the delegate, and the edit delegate, that they're being edited
6662 d->setRequiredProperty(kRequiredProperty_editing, QVariant::fromValue(true), cellIndex, cellItem, false);
6663
6664 // Transfer focus to the edit item
6665 d->editItem->forceActiveFocus(Qt::MouseFocusReason);
6666
6667 // Install an event filter on the focus object to handle Enter and Tab.
6668 // Note that the focusObject doesn't need to be the editItem itself, in
6669 // case the editItem is a FocusScope.
6670 if (QObject *focusObject = d->editItem->window()->focusObject()) {
6671 QQuickItem *focusItem = qobject_cast<QQuickItem *>(focusObject);
6672 if (focusItem == d->editItem || d->editItem->isAncestorOf(focusItem))
6673 focusItem->installEventFilter(this);
6674 }
6675}
6676
6677void QQuickTableView::closeEditor()
6678{
6679 Q_D(QQuickTableView);
6680
6681 if (!d->editItem)
6682 return;
6683
6684 QQuickItem *cellItem = d->editItem->parentItem();
6685 d->editModel->release(d->editItem, QQmlInstanceModel::NotReusable);
6686 d->editItem = nullptr;
6687
6688 cellItem->setZ(1);
6689 const int cellIndex = d->modelIndexToCellIndex(d->editIndex, false);
6690 d->setRequiredProperty(kRequiredProperty_editing, QVariant::fromValue(false), cellIndex, cellItem, false);
6691 // Remove the extra reference we sat on the cell item from edit()
6692 d->model->release(cellItem, QQmlInstanceModel::NotReusable);
6693
6694 if (d->editIndex.isValid()) {
6695 // Note: we can have an invalid editIndex, even when we
6696 // have an editItem, if the model has changed (e.g been reset)!
6697 d->editIndex = QModelIndex();
6698 }
6699}
6700
6705
6706void QQuickTableView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
6707{
6708 Q_D(QQuickTableView);
6709 QQuickFlickable::geometryChange(newGeometry, oldGeometry);
6710
6711 if (d->tableModel) {
6712 // When the view changes size, we force the pool to
6713 // shrink by releasing all pooled items.
6714 d->tableModel->drainReusableItemsPool(0);
6715 }
6716
6717 d->forceLayout(false);
6718}
6719
6720void QQuickTableView::viewportMoved(Qt::Orientations orientation)
6721{
6722 Q_D(QQuickTableView);
6723
6724 // If the new viewport position was set from the setLocalViewportXY()
6725 // functions, we just update the position silently and return. Otherwise, if
6726 // the viewport was flicked by the user, or some other control, we
6727 // recursively sync all the views in the hierarchy to the same position.
6728 QQuickFlickable::viewportMoved(orientation);
6729 if (d->inSetLocalViewportPos)
6730 return;
6731
6732 // Move all views in the syncView hierarchy to the same contentX/Y.
6733 // We need to start from this view (and not the root syncView) to
6734 // ensure that we respect all the individual syncDirection flags
6735 // between the individual views in the hierarchy.
6736 d->syncViewportPosRecursive();
6737
6738 auto rootView = d->rootSyncView();
6739 auto rootView_d = rootView->d_func();
6740
6741 rootView_d->scheduleRebuildIfFastFlick();
6742
6743 if (!rootView_d->polishScheduled) {
6744 if (rootView_d->scheduledRebuildOptions) {
6745 // When we need to rebuild, collecting several viewport
6746 // moves and do a single polish gives a quicker UI.
6747 rootView->polish();
6748 } else {
6749 // Updating the table right away when flicking
6750 // slowly gives a smoother experience.
6751 const bool updated = rootView->d_func()->updateTableRecursive();
6752 if (!updated) {
6753 // One, or more, of the views are already in an
6754 // update, so we need to wait a cycle.
6755 rootView->polish();
6756 }
6757 }
6758 }
6759}
6760
6762{
6763 Q_D(QQuickTableView);
6764
6765 if (!d->keyNavigationEnabled) {
6767 return;
6768 }
6769
6770 if (d->tableSize.isEmpty())
6771 return;
6772
6773 if (d->editIndex.isValid()) {
6774 // While editing, we limit the keys that we
6775 // handle to not interfere with editing.
6776 return;
6777 }
6778
6779 if (d->setCurrentIndexFromKeyEvent(e))
6780 return;
6781
6782 if (d->editFromKeyEvent(e))
6783 return;
6784
6786}
6787
6789{
6790 Q_D(QQuickTableView);
6791
6792 if (event->type() == QEvent::KeyPress) {
6793 Q_ASSERT(d->editItem);
6794 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
6795 switch (keyEvent->key()) {
6796 case Qt::Key_Enter:
6797 case Qt::Key_Return:
6798 if (auto attached = d->getAttachedObject(d->editItem))
6799 emit attached->commit();
6800 closeEditor();
6801 return true;
6802 case Qt::Key_Tab:
6803 case Qt::Key_Backtab:
6804 if (activeFocusOnTab()) {
6805 if (auto attached = d->getAttachedObject(d->editItem))
6806 emit attached->commit();
6807 closeEditor();
6808 if (d->setCurrentIndexFromKeyEvent(keyEvent)) {
6809 const QModelIndex currentIndex = d->selectionModel->currentIndex();
6810 if (d->canEdit(currentIndex, false))
6811 edit(currentIndex);
6812 }
6813 return true;
6814 }
6815 break;
6816 case Qt::Key_Escape:
6817 closeEditor();
6818 return true;
6819 }
6820 }
6821
6823}
6824
6826{
6827 return d_func()->alternatingRows;
6828}
6829
6830void QQuickTableView::setAlternatingRows(bool alternatingRows)
6831{
6832 Q_D(QQuickTableView);
6833 if (d->alternatingRows == alternatingRows)
6834 return;
6835
6836 d->alternatingRows = alternatingRows;
6837 emit alternatingRowsChanged();
6838}
6839
6841{
6842 return d_func()->selectionBehavior;
6843}
6844
6846{
6847 Q_D(QQuickTableView);
6848 if (d->selectionBehavior == selectionBehavior)
6849 return;
6850
6851 d->selectionBehavior = selectionBehavior;
6852 emit selectionBehaviorChanged();
6853}
6854
6856{
6857 return d_func()->selectionMode;
6858}
6859
6861{
6862 Q_D(QQuickTableView);
6863 if (d->selectionMode == selectionMode)
6864 return;
6865
6866 d->selectionMode = selectionMode;
6867 emit selectionModeChanged();
6868}
6869
6871{
6872 return d_func()->resizableColumns;
6873}
6874
6876{
6877 Q_D(QQuickTableView);
6878 if (d->resizableColumns == enabled)
6879 return;
6880
6881 d->resizableColumns = enabled;
6882 d->resizeHandler->setEnabled(d->resizableRows || d->resizableColumns);
6883 d->hoverHandler->setEnabled(d->resizableRows || d->resizableColumns);
6884
6885 emit resizableColumnsChanged();
6886}
6887
6889{
6890 return d_func()->resizableRows;
6891}
6892
6894{
6895 Q_D(QQuickTableView);
6896 if (d->resizableRows == enabled)
6897 return;
6898
6899 d->resizableRows = enabled;
6900 d->resizeHandler->setEnabled(d->resizableRows || d->resizableColumns);
6901 d->hoverHandler->setEnabled(d->resizableRows || d->resizableColumns);
6902
6903 emit resizableRowsChanged();
6904}
6905
6906// ----------------------------------------------
6908 : QQuickHoverHandler(view->contentItem())
6909{
6910 setMargin(5);
6911
6913 if (!isHoveringGrid())
6914 return;
6915 m_row = -1;
6916 m_column = -1;
6917#if QT_CONFIG(cursor)
6918 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
6919 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
6920 tableViewPrivate->updateCursor();
6921#endif
6922 });
6923}
6924
6926{
6928
6929 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
6930#if QT_CONFIG(cursor)
6931 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
6932#endif
6933
6934 const QPoint cell = tableView->cellAtPosition(point.position(), true);
6935 const auto item = tableView->itemAtCell(cell);
6936 if (!item) {
6937 m_row = -1;
6938 m_column = -1;
6939#if QT_CONFIG(cursor)
6940 tableViewPrivate->updateCursor();
6941#endif
6942 return;
6943 }
6944
6945 const QPointF itemPos = item->mapFromItem(tableView->contentItem(), point.position());
6946 const bool hoveringRow = (itemPos.y() < margin() || itemPos.y() > item->height() - margin());
6947 const bool hoveringColumn = (itemPos.x() < margin() || itemPos.x() > item->width() - margin());
6948 m_row = hoveringRow ? itemPos.y() < margin() ? cell.y() - 1 : cell.y() : -1;
6949 m_column = hoveringColumn ? itemPos.x() < margin() ? cell.x() - 1 : cell.x() : -1;
6950#if QT_CONFIG(cursor)
6951 tableViewPrivate->updateCursor();
6952#endif
6953}
6954
6955// ----------------------------------------------
6956
6958 : QQuickSinglePointHandler(view->contentItem())
6959{
6960 setMargin(5);
6961 // Set a grab permission that stops the flickable, as well as
6962 // any drag handler inside the delegate, from stealing the drag.
6964 setObjectName("tableViewResizeHandler");
6965}
6966
6990
6992{
6994 return false;
6995
6996 // If we have a mouse wheel event then we do not want to do anything related to resizing.
6997 if (event->type() == QEvent::Type::Wheel)
6998 return false;
6999
7000 // When the user is flicking, we disable resizing, so that
7001 // he doesn't start to resize by accident.
7002 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7003 return !tableView->isMoving();
7004}
7005
7007{
7008 // Resolve which state we're in first...
7010 // ...and act on it next
7012}
7013
7015{
7016 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7017 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7018
7021
7022 if (point.state() == QEventPoint::Pressed) {
7023 m_row = tableViewPrivate->resizableRows ? tableViewPrivate->hoverHandler->m_row : -1;
7024 m_column = tableViewPrivate->resizableColumns ? tableViewPrivate->hoverHandler->m_column : -1;
7025 if (m_row != -1 || m_column != -1)
7026 m_state = Tracking;
7027 } else if (point.state() == QEventPoint::Released) {
7030 else
7032 } else if (point.state() == QEventPoint::Updated) {
7033 switch (m_state) {
7034 case Listening:
7035 break;
7036 case Tracking: {
7037 const qreal distX = m_column != -1 ? point.position().x() - point.pressPosition().x() : 0;
7038 const qreal distY = m_row != -1 ? point.position().y() - point.pressPosition().y() : 0;
7039 const qreal dragDist = qSqrt(distX * distX + distY * distY);
7040 if (dragDist > qApp->styleHints()->startDragDistance())
7042 break;}
7043 case DraggingStarted:
7044 m_state = Dragging;
7045 break;
7046 case Dragging:
7047 break;
7048 case DraggingFinished:
7049 // Handled at the top of the function
7050 Q_UNREACHABLE();
7051 break;
7052 }
7053 }
7054}
7055
7057{
7058 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7059#if QT_CONFIG(cursor)
7060 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7061#endif
7062
7063 switch (m_state) {
7064 case Listening:
7065 break;
7066 case Tracking:
7067 setPassiveGrab(event, point, true);
7068 // Disable flicking while dragging. TableView uses filtering instead of
7069 // pointer handlers to do flicking, so setting an exclusive grab (together
7070 // with grab permissions) doens't work ATM.
7071 tableView->setFiltersChildMouseEvents(false);
7072 break;
7073 case DraggingStarted:
7076 m_columnStartWidth = tableView->columnWidth(m_column);
7078 m_rowStartHeight = tableView->rowHeight(m_row);
7079#if QT_CONFIG(cursor)
7080 tableViewPrivate->updateCursor();
7081#endif
7082 Q_FALLTHROUGH();
7083 case Dragging: {
7084 const qreal distX = point.position().x() - m_columnStartX;
7085 const qreal distY = point.position().y() - m_rowStartY;
7086 if (m_column != -1)
7087 tableView->setColumnWidth(m_column, qMax(0.001, m_columnStartWidth + distX));
7088 if (m_row != -1)
7089 tableView->setRowHeight(m_row, qMax(0.001, m_rowStartHeight + distY));
7090 break; }
7091 case DraggingFinished: {
7092 tableView->setFiltersChildMouseEvents(true);
7093#if QT_CONFIG(cursor)
7094 tableViewPrivate->updateCursor();
7095#endif
7096 break; }
7097 }
7098}
7099
7101{
7102 auto initIndices = [](auto& visualIndex, auto& logicalIndex, int size) {
7103 visualIndex.resize(size);
7104 logicalIndex.resize(size);
7105 for (int index = 0; index < size; ++index)
7106 visualIndex[index].index = logicalIndex[index].index = index;
7107 };
7108
7109 if (!tableSize.isEmpty()) {
7110 if (visualIndices[0].size() != tableSize.width()
7111 || logicalIndices[0].size() != tableSize.width())
7112 initIndices(visualIndices[0], logicalIndices[0], tableSize.width());
7113
7114 if (visualIndices[1].size() != tableSize.height()
7115 || logicalIndices[1].size() != tableSize.height())
7116 initIndices(visualIndices[1], logicalIndices[1], tableSize.height());
7117 }
7118}
7119
7121{
7122 logicalIndices[0].clear();
7123 visualIndices[0].clear();
7124
7125 logicalIndices[1].clear();
7126 visualIndices[1].clear();
7127}
7128
7129int QQuickTableViewPrivate::logicalRowIndex(const int visualIndex) const
7130{
7131 if (syncView)
7132 return syncView->d_func()->logicalRowIndex(visualIndex);
7133 if (logicalIndices[1].isEmpty() || visualIndex < 0)
7134 return visualIndex;
7135 return logicalIndices[1].constData()[visualIndex].index;
7136}
7137
7138int QQuickTableViewPrivate::logicalColumnIndex(const int visualIndex) const
7139{
7140 if (syncView)
7141 return syncView->d_func()->logicalColumnIndex(visualIndex);
7142 if (logicalIndices[0].isEmpty() || visualIndex < 0)
7143 return visualIndex;
7144 return logicalIndices[0].constData()[visualIndex].index;
7145}
7146
7147int QQuickTableViewPrivate::visualRowIndex(const int logicalIndex) const
7148{
7149 if (syncView)
7150 return syncView->d_func()->visualRowIndex(logicalIndex);
7151 if (visualIndices[1].isEmpty() || logicalIndex < 0)
7152 return logicalIndex;
7153 return visualIndices[1].constData()[logicalIndex].index;
7154}
7155
7156int QQuickTableViewPrivate::visualColumnIndex(const int logicalIndex) const
7157{
7158 if (syncView)
7159 return syncView->d_func()->visualColumnIndex(logicalIndex);
7160 if (visualIndices[0].isEmpty() || logicalIndex < 0)
7161 return logicalIndex;
7162 return visualIndices[0].constData()[logicalIndex].index;
7163}
7164
7165// ----------------------------------------------
7166
7172
7174{
7175 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7176 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7177 return tableViewPrivate->pointerNavigationEnabled && QQuickTapHandler::wantsEventPoint(event, point);
7178}
7179
7181
7182#include "moc_qquicktableview_p.cpp"
7183#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 int row() const noexcept
Returns the row this model index refers to.
constexpr const QAbstractItemModel * model() const noexcept
Returns a pointer to the model containing the item that this index refers to.
constexpr int column() const noexcept
Returns the column this model 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)
QList< SectionData > visualIndices[Qt::Vertical]
qreal getEffectiveRowHeight(int row) const
void rowsMovedCallback(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)
void moveSection(int source, int destination, Qt::Orientations orientation)
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)
virtual int logicalColumnIndex(const int visualIndex) const
QList< SectionData > logicalIndices[Qt::Vertical]
virtual int logicalRowIndex(const int visualIndex) const
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
virtual int visualRowIndex(const int logicalIndex) const
QMinimalFlatSet< int > loadedColumns
QHash< int, FxTableItem * > loadedItems
QAbstractItemModel * qaim(QVariant modelAsVariant) const
void setLocalViewportY(qreal contentY)
int modelIndexToCellIndex(const QModelIndex &modelIndex, bool visualIndex=true) const
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)
void clearSection(Qt::Orientations orientation)
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)
virtual int visualColumnIndex(const int logicalIndex) 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:517
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:537
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
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char * destination
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
GLsizei GLsizei GLchar * source
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]