From 612ac8749782af9c58a0e823b7180e0018a42be8 Mon Sep 17 00:00:00 2001 From: Mitch Curtis Date: Mon, 3 Dec 2018 11:39:52 +0100 Subject: Doc: explain when the move velocity and duration properties are used Change-Id: Iaf7cf035f76a1f198c60f4792d394b0fd19ef901 Reviewed-by: Shawn Rutledge --- src/quick/items/qquicklistview.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/quick/items/qquicklistview.cpp b/src/quick/items/qquicklistview.cpp index 62cbfcef2b..75a24c7d76 100644 --- a/src/quick/items/qquicklistview.cpp +++ b/src/quick/items/qquicklistview.cpp @@ -2460,6 +2460,11 @@ QString QQuickListView::currentSection() const if both the velocity and duration are set, the animation will use whichever gives the shorter duration. + The move velocity and duration properties are used to control movement due + to index changes; for example, when incrementCurrentIndex() is called. When + the user flicks a ListView, the velocity from the flick is used to control + the movement instead. + To set only one property, the other can be set to \c -1. For example, if you only want to animate the duration and not velocity, use the following code: -- cgit v1.2.3 From bb2dce1d902405acfd5aaf997860c0f0318bce77 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 7 Feb 2019 12:34:36 +0100 Subject: Try to fix flakiness in flickableinterop test The usual problem is that Flickable doesn't instantly jump to the expected position but moves there after a delay. Change-Id: Iafc9dd493b97629377e7f7c60ae7adde13427bae Reviewed-by: Shawn Rutledge --- .../quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp b/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp index cf2ac4a830..f4ed051e1f 100644 --- a/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp +++ b/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp @@ -741,7 +741,7 @@ void tst_FlickableInterop::touchAndDragHandlerOnFlickable() QQuickTouchUtils::flush(window); } if (!(buttonDragHandler && !pressDelay)) - QVERIFY(flickable->contentY() >= dragThreshold); + QTRY_VERIFY(flickable->contentY() >= dragThreshold); if (buttonTapHandler) QCOMPARE(buttonTapHandler->isPressed(), false); touchSeq.release(1, p1, window).commit(); -- cgit v1.2.3 From 00685756021fbacb7f7b0419cd5e6062e2698074 Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Tue, 8 Jan 2019 11:10:13 +0100 Subject: QQuickTableView: add support for hiding rows and columns This patch will add support for hiding rows and columns to TableView. You can now hide a column by returning 0 width for it from the columnWidthProvider. The same can be done to hide a row (by using the rowHeightProvider). If you return NaN or negative number, TableView will fall back to calculate the size of the column/row by looking at the delegate items, like before. This to make it possible to hide some rows/columns, without having to calculate and return the heights and widths of the other rows and columns. [ChangeLog][QtQuick][TableView] Added support for hiding rows and columns by setting their size to 0 from the columnsWidthProvider/rowHeightProvider. Change-Id: If9e1a8db91e257d36cb2787bab4856e6201456ac Reviewed-by: Mitch Curtis --- src/quick/items/qquicktableview.cpp | 813 +++++++++++++-------- src/quick/items/qquicktableview_p_p.h | 119 +-- .../qquicktableview/data/hiderowsandcolumns.qml | 85 +++ .../data/usefaultyrowcolumnprovider.qml | 6 +- .../qquicktableview/data/userowcolumnprovider.qml | 14 +- .../quick/qquicktableview/tst_qquicktableview.cpp | 189 ++++- 6 files changed, 841 insertions(+), 385 deletions(-) create mode 100644 tests/auto/quick/qquicktableview/data/hiderowsandcolumns.qml diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 0b5bebb7ba..b3305ccaeb 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -150,6 +150,11 @@ must call \l forceLayout. This informs TableView that it needs to use the provider functions again to recalculate and update the layout. + Since Qt 5.13, if you want to hide a specific column, you can return \c 0 from the + \l columnWidthProvider for that column. Likewise, you can return 0 from the + \l rowHeightProvider to hide a row. If you return a negative number, TableView + will fall back to calculate the size based on the delegate items. + \note The size of a row or column should be a whole number to avoid sub-pixel alignment of items. @@ -217,7 +222,9 @@ know the height of a specific row. The function takes one argument, \c row, for which the TableView needs to know the height. - \note The height of a row must always be greater than \c 0. + Since Qt 5.13, if you want to hide a specific row, you can return \c 0 height for + that row. If you return a negative number, TableView will fall back to + calculate the height based on the delegate items. \sa columnWidthProvider, {Row heights and column widths} */ @@ -230,7 +237,9 @@ to know the width of a specific column. The function takes one argument, \c column, for which the TableView needs to know the width. - \note The width of a column must always be greater than \c 0. + Since Qt 5.13, if you want to hide a specific column, you can return \c 0 width for + that column. If you return a negative number, TableView will fall back to + calculate the width based on the delegate items. \sa rowHeightProvider, {Row heights and column widths} */ @@ -376,12 +385,41 @@ Q_LOGGING_CATEGORY(lcTableViewDelegateLifecycle, "qt.quick.tableview.lifecycle") #define Q_TABLEVIEW_ASSERT(cond, output) Q_ASSERT((cond) || [&](){ dumpTable(); qWarning() << "output:" << output; return false;}()) static const Qt::Edge allTableEdges[] = { Qt::LeftEdge, Qt::RightEdge, Qt::TopEdge, Qt::BottomEdge }; +static const int kEdgeIndexNotSet = -2; +static const int kEdgeIndexAtEnd = -3; const QPoint QQuickTableViewPrivate::kLeft = QPoint(-1, 0); const QPoint QQuickTableViewPrivate::kRight = QPoint(1, 0); const QPoint QQuickTableViewPrivate::kUp = QPoint(0, -1); const QPoint QQuickTableViewPrivate::kDown = QPoint(0, 1); +QQuickTableViewPrivate::EdgeRange::EdgeRange() + : startIndex(kEdgeIndexNotSet) + , endIndex(kEdgeIndexNotSet) + , size(0) +{} + +bool QQuickTableViewPrivate::EdgeRange::containsIndex(Qt::Edge edge, int index) +{ + if (startIndex == kEdgeIndexNotSet) + return false; + + if (endIndex == kEdgeIndexAtEnd) { + switch (edge) { + case Qt::LeftEdge: + case Qt::TopEdge: + return index <= startIndex; + case Qt::RightEdge: + case Qt::BottomEdge: + return index >= startIndex; + } + } + + const int s = std::min(startIndex, endIndex); + const int e = std::max(startIndex, endIndex); + return index >= s && index <= e; +} + QQuickTableViewPrivate::QQuickTableViewPrivate() : QQuickFlickablePrivate() { @@ -448,6 +486,118 @@ QPoint QQuickTableViewPrivate::cellAtModelIndex(int modelIndex) const return QPoint(column, row); } +int QQuickTableViewPrivate::edgeToArrayIndex(Qt::Edge edge) +{ + return int(log2(float(edge))); +} + +void QQuickTableViewPrivate::clearEdgeSizeCache() +{ + cachedColumnWidth.startIndex = kEdgeIndexNotSet; + cachedRowHeight.startIndex = kEdgeIndexNotSet; + + for (Qt::Edge edge : allTableEdges) + cachedNextVisibleEdgeIndex[edgeToArrayIndex(edge)].startIndex = kEdgeIndexNotSet; +} + +int QQuickTableViewPrivate::nextVisibleEdgeIndexAroundLoadedTable(Qt::Edge edge) +{ + // Find the next column (or row) around the loaded table that is + // visible, and should be loaded next if the content item moves. + int startIndex = -1; + switch (edge) { + case Qt::LeftEdge: startIndex = loadedColumns.firstKey() - 1; break; + case Qt::RightEdge: startIndex = loadedColumns.lastKey() + 1; break; + case Qt::TopEdge: startIndex = loadedRows.firstKey() - 1; break; + case Qt::BottomEdge: startIndex = loadedRows.lastKey() + 1; break; + } + + return nextVisibleEdgeIndex(edge, startIndex); +} + +int QQuickTableViewPrivate::nextVisibleEdgeIndex(Qt::Edge edge, int startIndex) +{ + // First check if we have already searched for the first visible index + // after the given startIndex recently, and if so, return the cached result. + // The cached result is valid if startIndex is inside the range between the + // startIndex and the first visible index found after it. + auto &cachedResult = cachedNextVisibleEdgeIndex[edgeToArrayIndex(edge)]; + if (cachedResult.containsIndex(edge, startIndex)) + return cachedResult.endIndex; + + // Search for the first column (or row) in the direction of edge that is + // visible, starting from the given column (startIndex). + int foundIndex = kEdgeIndexNotSet; + int testIndex = startIndex; + + switch (edge) { + case Qt::LeftEdge: { + forever { + if (testIndex < 0) { + foundIndex = kEdgeIndexAtEnd; + break; + } + + if (!isColumnHidden(testIndex)) { + foundIndex = testIndex; + break; + } + + --testIndex; + } + break; } + case Qt::RightEdge: { + forever { + if (testIndex > tableSize.width() - 1) { + foundIndex = kEdgeIndexAtEnd; + break; + } + + if (!isColumnHidden(testIndex)) { + foundIndex = testIndex; + break; + } + + ++testIndex; + } + break; } + case Qt::TopEdge: { + forever { + if (testIndex < 0) { + foundIndex = kEdgeIndexAtEnd; + break; + } + + if (!isRowHidden(testIndex)) { + foundIndex = testIndex; + break; + } + + --testIndex; + } + break; } + case Qt::BottomEdge: { + forever { + if (testIndex > tableSize.height() - 1) { + foundIndex = kEdgeIndexAtEnd; + break; + } + + if (!isRowHidden(testIndex)) { + foundIndex = testIndex; + break; + } + + ++testIndex; + } + break; } + } + + cachedResult.startIndex = startIndex; + cachedResult.endIndex = foundIndex; + return foundIndex; +} + void QQuickTableViewPrivate::updateContentWidth() { Q_Q(QQuickTableView); @@ -458,31 +608,13 @@ void QQuickTableViewPrivate::updateContentWidth() return; } - const qreal thresholdBeforeAdjust = 0.1; - int currentRightColumn = rightColumn(); - - if (currentRightColumn > contentSizeBenchMarkPoint.x()) { - contentSizeBenchMarkPoint.setX(currentRightColumn); - - const qreal spacing = currentRightColumn * cellSpacing.width(); - qreal currentWidth = loadedTableOuterRect.right(); - const qreal averageCellWidth = (currentWidth - spacing) / (currentRightColumn + 1); - qreal estimatedWidth = (tableSize.width() * (averageCellWidth + cellSpacing.width())) - cellSpacing.width(); - - if (currentRightColumn >= tableSize.width() - 1) { - // We are at the last column, and can set the exact width - if (!qFuzzyCompare(currentWidth, q->implicitWidth())) - q->QQuickFlickable::setContentWidth(currentWidth); - } else if (currentWidth >= q->implicitWidth()) { - // We are at the estimated width, but there are still more columns - q->QQuickFlickable::setContentWidth(estimatedWidth); - } else { - // Only set a new width if the new estimate is substantially different - qreal diff = 1 - (estimatedWidth / q->implicitWidth()); - if (qAbs(diff) > thresholdBeforeAdjust) - q->QQuickFlickable::setContentWidth(estimatedWidth); - } - } + const int nextColumn = nextVisibleEdgeIndexAroundLoadedTable(Qt::RightEdge); + const int columnsRemaining = nextColumn == kEdgeIndexAtEnd ? 0 : tableSize.width() - nextColumn; + const qreal remainingColumnWidths = columnsRemaining * averageEdgeSize.width(); + const qreal remainingSpacing = columnsRemaining * cellSpacing.width(); + const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing; + const qreal estimatedWidth = loadedTableOuterRect.right() + estimatedRemainingWidth; + q->QQuickFlickable::setContentWidth(estimatedWidth); } void QQuickTableViewPrivate::updateContentHeight() @@ -495,31 +627,13 @@ void QQuickTableViewPrivate::updateContentHeight() return; } - const qreal thresholdBeforeAdjust = 0.1; - int currentBottomRow = bottomRow(); - - if (currentBottomRow > contentSizeBenchMarkPoint.y()) { - contentSizeBenchMarkPoint.setY(currentBottomRow); - - const qreal spacing = currentBottomRow * cellSpacing.height(); - qreal currentHeight = loadedTableOuterRect.bottom(); - const qreal averageCellHeight = (currentHeight - spacing) / (currentBottomRow + 1); - qreal estimatedHeight = (tableSize.height() * (averageCellHeight + cellSpacing.height())) - cellSpacing.height(); - - if (currentBottomRow >= tableSize.height() - 1) { - // We are at the last row, and can set the exact height - if (!qFuzzyCompare(currentHeight, q->implicitHeight())) - q->QQuickFlickable::setContentHeight(currentHeight); - } else if (currentHeight >= q->implicitHeight()) { - // We are at the estimated height, but there are still more rows - q->QQuickFlickable::setContentHeight(estimatedHeight); - } else { - // Only set a new height if the new estimate is substantially different - qreal diff = 1 - (estimatedHeight / q->implicitHeight()); - if (qAbs(diff) > thresholdBeforeAdjust) - q->QQuickFlickable::setContentHeight(estimatedHeight); - } - } + const int nextRow = nextVisibleEdgeIndexAroundLoadedTable(Qt::BottomEdge); + const int rowsRemaining = nextRow == kEdgeIndexAtEnd ? 0 : tableSize.height() - nextRow; + const qreal remainingRowHeights = rowsRemaining * averageEdgeSize.height(); + const qreal remainingSpacing = rowsRemaining * cellSpacing.height(); + const qreal estimatedRemainingHeight = remainingRowHeights + remainingSpacing; + const qreal estimatedHeight = loadedTableOuterRect.bottom() + estimatedRemainingHeight; + q->QQuickFlickable::setContentHeight(estimatedHeight); } void QQuickTableViewPrivate::enforceTableAtOrigin() @@ -531,24 +645,36 @@ void QQuickTableViewPrivate::enforceTableAtOrigin() bool layoutNeeded = false; const qreal flickMargin = 50; - if (leftColumn() == 0 && loadedTableOuterRect.x() > 0) { - // The table is at the beginning, but not at the edge of the - // content view. So move the table to origin. - loadedTableOuterRect.moveLeft(0); - layoutNeeded = true; - } else if (loadedTableOuterRect.x() < 0) { - // The table is outside the beginning of the content view. Move - // the whole table inside, and make some room for flicking. - loadedTableOuterRect.moveLeft(leftColumn() == 0 ? 0 : flickMargin); - layoutNeeded = true; + const bool noMoreColumns = nextVisibleEdgeIndexAroundLoadedTable(Qt::LeftEdge) == kEdgeIndexAtEnd; + const bool noMoreRows = nextVisibleEdgeIndexAroundLoadedTable(Qt::TopEdge) == kEdgeIndexAtEnd; + + if (noMoreColumns) { + if (!qFuzzyIsNull(loadedTableOuterRect.left())) { + // There are no more columns, but the table rect + // is not at origin. So we move it there. + loadedTableOuterRect.moveLeft(0); + layoutNeeded = true; + } + } else { + if (loadedTableOuterRect.left() <= 0) { + // The table rect is at origin, or outside. But we still have + // more visible columns to the left. So we need to make some + // space so that they can be flicked in. + loadedTableOuterRect.moveLeft(flickMargin); + layoutNeeded = true; + } } - if (topRow() == 0 && loadedTableOuterRect.y() > 0) { - loadedTableOuterRect.moveTop(0); - layoutNeeded = true; - } else if (loadedTableOuterRect.y() < 0) { - loadedTableOuterRect.moveTop(topRow() == 0 ? 0 : flickMargin); - layoutNeeded = true; + if (noMoreRows) { + if (!qFuzzyIsNull(loadedTableOuterRect.top())) { + loadedTableOuterRect.moveTop(0); + layoutNeeded = true; + } + } else { + if (loadedTableOuterRect.top() <= 0) { + loadedTableOuterRect.moveTop(flickMargin); + layoutNeeded = true; + } } if (layoutNeeded) { @@ -559,12 +685,12 @@ void QQuickTableViewPrivate::enforceTableAtOrigin() void QQuickTableViewPrivate::updateAverageEdgeSize() { - int bottomCell = bottomRow(); - int rightCell = rightColumn(); - qreal accRowSpacing = bottomCell * cellSpacing.height(); - qreal accColumnSpacing = rightCell * cellSpacing.width(); - averageEdgeSize.setHeight((loadedTableOuterRect.bottom() - accRowSpacing) / (bottomCell + 1)); - averageEdgeSize.setWidth((loadedTableOuterRect.right() - accColumnSpacing) / (rightCell + 1)); + const int loadedRowCount = loadedRows.count(); + const int loadedColumnCount = loadedColumns.count(); + const qreal accRowSpacing = (loadedRowCount - 1) * cellSpacing.height(); + const qreal accColumnSpacing = (loadedColumnCount - 1) * cellSpacing.width(); + averageEdgeSize.setHeight((loadedTableOuterRect.height() - accRowSpacing) / loadedRowCount); + averageEdgeSize.setWidth((loadedTableOuterRect.width() - accColumnSpacing) / loadedColumnCount); } void QQuickTableViewPrivate::syncLoadedTableRectFromLoadedTable() @@ -577,32 +703,81 @@ void QQuickTableViewPrivate::syncLoadedTableRectFromLoadedTable() loadedTableInnerRect = QRectF(topLeftRect.bottomRight(), bottomRightRect.topLeft()); } +void QQuickTableViewPrivate::forceLayout() +{ + columnRowPositionsInvalid = true; + RebuildOptions rebuildOptions = RebuildOption::None; + + // Go through all columns from first to last, find the columns that used + // to be hidden and not loaded, and check if they should become visible + // (and vice versa). If there is a change, we need to rebuild. + for (int column = leftColumn(); column <= rightColumn(); ++column) { + const bool wasVisibleFromBefore = loadedColumns.contains(column); + const bool isVisibleNow = !qFuzzyIsNull(getColumnWidth(column)); + if (wasVisibleFromBefore == isVisibleNow) + continue; + + // A column changed visibility. This means that it should + // either be loaded or unloaded. So we need a rebuild. + qCDebug(lcTableViewDelegateLifecycle) << "Column" << column << "changed visibility to" << isVisibleNow; + rebuildOptions.setFlag(RebuildOption::ViewportOnly); + if (column == leftColumn()) { + // The first loaded column should now be hidden. This means that we + // need to calculate which column should now be first instead. + rebuildOptions.setFlag(RebuildOption::CalculateNewTopLeftColumn); + } + break; + } + + // Go through all rows from first to last, and do the same as above + for (int row = topRow(); row <= bottomRow(); ++row) { + const bool wasVisibleFromBefore = loadedRows.contains(row); + const bool isVisibleNow = !qFuzzyIsNull(getRowHeight(row)); + if (wasVisibleFromBefore == isVisibleNow) + continue; + + // A row changed visibility. This means that it should + // either be loaded or unloaded. So we need a rebuild. + qCDebug(lcTableViewDelegateLifecycle) << "Row" << row << "changed visibility to" << isVisibleNow; + rebuildOptions.setFlag(RebuildOption::ViewportOnly); + if (row == topRow()) + rebuildOptions.setFlag(RebuildOption::CalculateNewTopLeftRow); + break; + } + + if (rebuildOptions) + scheduleRebuildTable(rebuildOptions); + + if (polishing) { + qWarning() << "TableView::forceLayout(): Cannot do an immediate re-layout during an ongoing layout!"; + q_func()->polish(); + return; + } + + updatePolish(); +} + void QQuickTableViewPrivate::syncLoadedTableFromLoadRequest() { if (loadRequest.edge() == Qt::Edge(0)) { // No edge means we're loading the top-left item - loadedColumns.insert(loadRequest.firstCell().x(), 0); - loadedRows.insert(loadRequest.firstCell().y(), 0); + loadedColumns.insert(loadRequest.column(), 0); + loadedRows.insert(loadRequest.row(), 0); return; } switch (loadRequest.edge()) { case Qt::LeftEdge: case Qt::RightEdge: - loadedColumns.insert(loadRequest.firstCell().x(), 0); + loadedColumns.insert(loadRequest.column(), 0); break; case Qt::TopEdge: case Qt::BottomEdge: - loadedRows.insert(loadRequest.firstCell().y(), 0); + loadedRows.insert(loadRequest.row(), 0); break; } } -FxTableItem *QQuickTableViewPrivate::itemNextTo(const FxTableItem *fxTableItem, const QPoint &direction) const -{ - return loadedTableItem(fxTableItem->cell + direction); -} - FxTableItem *QQuickTableViewPrivate::loadedTableItem(const QPoint &cell) const { const int modelIndex = modelIndexAtCell(cell); @@ -734,54 +909,16 @@ void QQuickTableViewPrivate::unloadItem(const QPoint &cell) releaseItem(loadedItems.take(modelIndex), reusableFlag); } -void QQuickTableViewPrivate::unloadItems(const QLine &items) -{ - qCDebug(lcTableViewDelegateLifecycle) << items; - - if (items.dx()) { - int y = items.p1().y(); - for (int x = items.p1().x(); x <= items.p2().x(); ++x) - unloadItem(QPoint(x, y)); - } else { - int x = items.p1().x(); - for (int y = items.p1().y(); y <= items.p2().y(); ++y) - unloadItem(QPoint(x, y)); - } -} - -int QQuickTableViewPrivate::nextVisibleEdgeIndexAroundLoadedTable(Qt::Edge edge) -{ - switch (edge) { - case Qt::LeftEdge: - return leftColumn() - 1; - case Qt::RightEdge: - return rightColumn() + 1; - case Qt::TopEdge: - return topRow() - 1; - case Qt::BottomEdge: - return bottomRow() + 1; - } - return -1; -} - bool QQuickTableViewPrivate::canLoadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const { switch (tableEdge) { case Qt::LeftEdge: - if (leftColumn() == 0) - return false; return loadedTableOuterRect.left() > fillRect.left() + cellSpacing.width(); case Qt::RightEdge: - if (rightColumn() == tableSize.width() - 1) - return false; return loadedTableOuterRect.right() < fillRect.right() - cellSpacing.width(); case Qt::TopEdge: - if (topRow() == 0) - return false; return loadedTableOuterRect.top() > fillRect.top() + cellSpacing.height(); case Qt::BottomEdge: - if (bottomRow() == tableSize.height() - 1) - return false; return loadedTableOuterRect.bottom() < fillRect.bottom() - cellSpacing.height(); } @@ -817,9 +954,14 @@ bool QQuickTableViewPrivate::canUnloadTableEdge(Qt::Edge tableEdge, const QRectF Qt::Edge QQuickTableViewPrivate::nextEdgeToLoad(const QRectF rect) { for (Qt::Edge edge : allTableEdges) { - if (canLoadTableEdge(edge, rect)) - return edge; + if (!canLoadTableEdge(edge, rect)) + continue; + const int nextIndex = nextVisibleEdgeIndexAroundLoadedTable(edge); + if (nextIndex == kEdgeIndexAtEnd) + continue; + return edge; } + return Qt::Edge(0); } @@ -892,100 +1034,152 @@ void QQuickTableViewPrivate::calculateTableSize() emit q->rowsChanged(); } -qreal QQuickTableViewPrivate::resolveColumnWidth(int column) -{ - qreal columnWidth = -1; +qreal QQuickTableViewPrivate::getColumnLayoutWidth(int column) +{ + // Return the column width specified by the application, or go + // through the loaded items and calculate it as a fallback. For + // layouting, the width can never be zero (or negative), as this + // can lead us to be stuck in an infinite loop trying to load and + // fill out the empty viewport space with empty columns. + const qreal explicitColumnWidth = getColumnWidth(column); + if (explicitColumnWidth >= 0) + return explicitColumnWidth; + + // Iterate over the currently visible items in the column. The downside + // of doing that, is that the column width will then only be based on the implicit + // width of the currently loaded items (which can be different depending on which + // row you're at when the column is flicked in). The upshot is that you don't have to + // bother setting columnWidthProvider for small tables, or if the implicit width doesn't vary. + qreal columnWidth = sizeHintForColumn(column); + + if (qIsNaN(columnWidth) || columnWidth <= 0) { + if (!layoutWarningIssued) { + layoutWarningIssued = true; + qmlWarning(q_func()) << "the delegate's implicitHeight needs to be greater than zero"; + } + columnWidth = kDefaultRowHeight; + } - if (!columnWidthProvider.isUndefined()) { - if (columnWidthProvider.isCallable()) { - auto const columnAsArgument = QJSValueList() << QJSValue(column); - columnWidth = columnWidthProvider.call(columnAsArgument).toNumber(); - if (qIsNaN(columnWidth) || columnWidth <= 0) { - // The column width needs to be greater than 0, otherwise we never reach the edge - // while loading/refilling columns. This would cause the application to hang. - if (!layoutWarningIssued) { - layoutWarningIssued = true; - qmlWarning(q_func()) << "columnWidthProvider did not return a valid width for column: " << column; - } - columnWidth = kDefaultColumnWidth; - } - } else { - if (!layoutWarningIssued) { - layoutWarningIssued = true; - qmlWarning(q_func()) << "columnWidthProvider doesn't contain a function"; - } - columnWidth = kDefaultColumnWidth; + return columnWidth; +} + +qreal QQuickTableViewPrivate::getRowLayoutHeight(int row) +{ + // Return the row height specified by the application, or go + // through the loaded items and calculate it as a fallback. For + // layouting, the height can never be zero (or negative), as this + // can lead us to be stuck in an infinite loop trying to load and + // fill out the empty viewport space with empty rows. + const qreal explicitRowHeight = getRowHeight(row); + if (explicitRowHeight >= 0) + return explicitRowHeight; + + // Iterate over the currently visible items in the row. The downside + // of doing that, is that the row height will then only be based on the implicit + // height of the currently loaded items (which can be different depending on which + // column you're at when the row is flicked in). The upshot is that you don't have to + // bother setting rowHeightProvider for small tables, or if the implicit height doesn't vary. + qreal rowHeight = sizeHintForRow(row); + + if (qIsNaN(rowHeight) || rowHeight <= 0) { + if (!layoutWarningIssued) { + layoutWarningIssued = true; + qmlWarning(q_func()) << "the delegate's implicitHeight needs to be greater than zero"; } + rowHeight = kDefaultRowHeight; + } + + return rowHeight; +} + +qreal QQuickTableViewPrivate::getColumnWidth(int column) +{ + // Return the width of the given column, if explicitly set. Return 0 if the column + // is hidden, and -1 if the width is not set (which means that the width should + // instead be calculated from the implicit size of the delegate items. This function + // can be overridden by e.g HeaderView to provide the column widths by other means. + const int noExplicitColumnWidth = -1; + + if (cachedColumnWidth.startIndex == column) + return cachedColumnWidth.size; + + if (columnWidthProvider.isUndefined()) + return noExplicitColumnWidth; + + qreal columnWidth = noExplicitColumnWidth; + + if (columnWidthProvider.isCallable()) { + auto const columnAsArgument = QJSValueList() << QJSValue(column); + columnWidth = columnWidthProvider.call(columnAsArgument).toNumber(); + cachedColumnWidth.startIndex = column; + cachedColumnWidth.size = columnWidth; + if (qIsNaN(columnWidth) || columnWidth < 0) + columnWidth = noExplicitColumnWidth; } else { - // If columnWidthProvider is left unspecified, we just iterate over the currently visible items in - // the column. The downside of doing that, is that the column width will then only be based - // on the implicit width of the currently loaded items (which can be different depending on - // which row you're at when the column is flicked in). The upshot is that you don't have to - // bother setting columnWidthProvider for small tables, or if the implicit width doesn't vary. - columnWidth = sizeHintForColumn(column); - if (qIsNaN(columnWidth) || columnWidth <= 0) { - // The column width needs to be greater than 0, otherwise we never reach the edge - // while loading/refilling columns. This would cause the application to hang. - if (!layoutWarningIssued) { - layoutWarningIssued = true; - qmlWarning(q_func()) << "the delegate's implicitWidth needs to be greater than zero"; - } - columnWidth = kDefaultColumnWidth; + if (!layoutWarningIssued) { + layoutWarningIssued = true; + qmlWarning(q_func()) << "columnWidthProvider doesn't contain a function"; } + columnWidth = noExplicitColumnWidth; } return columnWidth; } -qreal QQuickTableViewPrivate::resolveRowHeight(int row) +qreal QQuickTableViewPrivate::getRowHeight(int row) { - qreal rowHeight = -1; + // Return the height of the given row, if explicitly set. Return 0 if the row + // is hidden, and -1 if the height is not set (which means that the height should + // instead be calculated from the implicit size of the delegate items. This function + // can be overridden by e.g HeaderView to provide the row heights by other means. + const int noExplicitRowHeight = -1; - if (!rowHeightProvider.isUndefined()) { - if (rowHeightProvider.isCallable()) { - auto const rowAsArgument = QJSValueList() << QJSValue(row); - rowHeight = rowHeightProvider.call(rowAsArgument).toNumber(); - if (qIsNaN(rowHeight) || rowHeight <= 0) { - // The row height needs to be greater than 0, otherwise we never reach the edge - // while loading/refilling rows. This would cause the application to hang. - if (!layoutWarningIssued) { - layoutWarningIssued = true; - qmlWarning(q_func()) << "rowHeightProvider did not return a valid height for row: " << row; - } - rowHeight = kDefaultRowHeight; - } - } else { - if (!layoutWarningIssued) { - layoutWarningIssued = true; - qmlWarning(q_func()) << "rowHeightProvider doesn't contain a function"; - } - rowHeight = kDefaultRowHeight; - } + if (cachedRowHeight.startIndex == row) + return cachedRowHeight.size; + + if (rowHeightProvider.isUndefined()) + return noExplicitRowHeight; + + qreal rowHeight = noExplicitRowHeight; + + if (rowHeightProvider.isCallable()) { + auto const rowAsArgument = QJSValueList() << QJSValue(row); + rowHeight = rowHeightProvider.call(rowAsArgument).toNumber(); + cachedRowHeight.startIndex = row; + cachedRowHeight.size = rowHeight; + if (qIsNaN(rowHeight) || rowHeight < 0) + rowHeight = noExplicitRowHeight; } else { - // If rowHeightProvider is left unspecified, we just iterate over the currently visible items in - // the row. The downside of doing that, is that the row height will then only be based - // on the implicit height of the currently loaded items (which can be different depending on - // which column you're at when the row is flicked in). The upshot is that you don't have to - // bother setting rowHeightProvider for small tables, or if the implicit height doesn't vary. - rowHeight = sizeHintForRow(row); - if (qIsNaN(rowHeight) || rowHeight <= 0) { - if (!layoutWarningIssued) { - layoutWarningIssued = true; - qmlWarning(q_func()) << "the delegate's implicitHeight needs to be greater than zero"; - } - rowHeight = kDefaultRowHeight; + if (!layoutWarningIssued) { + layoutWarningIssued = true; + qmlWarning(q_func()) << "rowHeightProvider doesn't contain a function"; } + rowHeight = noExplicitRowHeight; } return rowHeight; } +bool QQuickTableViewPrivate::isColumnHidden(int column) +{ + // A column is hidden if the width is explicit set to zero (either by + // using a columnWidthProvider, or by overriding getColumnWidth()). + return qFuzzyIsNull(getColumnWidth(column)); +} + +bool QQuickTableViewPrivate::isRowHidden(int row) +{ + // A row is hidden if the height is explicit set to zero (either by + // using a rowHeightProvider, or by overriding getRowHeight()). + return qFuzzyIsNull(getRowHeight(row)); +} + void QQuickTableViewPrivate::relayoutTable() { + clearEdgeSizeCache(); relayoutTableItems(); syncLoadedTableRectFromLoadedTable(); enforceTableAtOrigin(); - contentSizeBenchMarkPoint = QPoint(-1, -1); updateContentWidth(); updateContentHeight(); // Return back to updatePolish to loadAndUnloadVisibleEdges() @@ -1004,7 +1198,7 @@ void QQuickTableViewPrivate::relayoutTableItems() for (auto c = loadedColumns.cbegin(); c != loadedColumns.cend(); ++c) { const int column = c.key(); // Adjust the geometry of all cells in the current column - const qreal width = resolveColumnWidth(column); + const qreal width = getColumnLayoutWidth(column); for (auto r = loadedRows.cbegin(); r != loadedRows.cend(); ++r) { const int row = r.key(); @@ -1015,13 +1209,14 @@ void QQuickTableViewPrivate::relayoutTableItems() item->setGeometry(geometry); } - nextColumnX += width + cellSpacing.width(); + if (width > 0) + nextColumnX += width + cellSpacing.width(); } for (auto r = loadedRows.cbegin(); r != loadedRows.cend(); ++r) { const int row = r.key(); // Adjust the geometry of all cells in the current row - const qreal height = resolveRowHeight(row); + const qreal height = getRowLayoutHeight(row); for (auto c = loadedColumns.cbegin(); c != loadedColumns.cend(); ++c) { const int column = c.key(); @@ -1032,7 +1227,8 @@ void QQuickTableViewPrivate::relayoutTableItems() item->setGeometry(geometry); } - nextRowY += height + cellSpacing.height(); + if (height > 0) + nextRowY += height + cellSpacing.height(); } if (Q_UNLIKELY(lcTableViewDelegateLifecycle().isDebugEnabled())) { @@ -1049,68 +1245,82 @@ void QQuickTableViewPrivate::relayoutTableItems() void QQuickTableViewPrivate::layoutVerticalEdge(Qt::Edge tableEdge) { - int column = (tableEdge == Qt::LeftEdge) ? leftColumn() : rightColumn(); - QPoint neighbourDirection = (tableEdge == Qt::LeftEdge) ? kRight : kLeft; - qreal width = resolveColumnWidth(column); + int columnThatNeedsLayout; + int neighbourColumn; + qreal columnX; + qreal columnWidth; + + if (tableEdge == Qt::LeftEdge) { + columnThatNeedsLayout = leftColumn(); + neighbourColumn = loadedColumns.keys().value(1); + columnWidth = getColumnLayoutWidth(columnThatNeedsLayout); + const auto neighbourItem = loadedTableItem(QPoint(neighbourColumn, topRow())); + columnX = neighbourItem->geometry().left() - cellSpacing.width() - columnWidth; + } else { + columnThatNeedsLayout = rightColumn(); + neighbourColumn = loadedColumns.keys().value(loadedColumns.count() - 2); + columnWidth = getColumnLayoutWidth(columnThatNeedsLayout); + const auto neighbourItem = loadedTableItem(QPoint(neighbourColumn, topRow())); + columnX = neighbourItem->geometry().right() + cellSpacing.width(); + } for (auto r = loadedRows.cbegin(); r != loadedRows.cend(); ++r) { const int row = r.key(); - auto fxTableItem = loadedTableItem(QPoint(column, row)); - auto const neighbourItem = itemNextTo(fxTableItem, neighbourDirection); + auto fxTableItem = loadedTableItem(QPoint(columnThatNeedsLayout, row)); + auto const neighbourItem = loadedTableItem(QPoint(neighbourColumn, row)); + const qreal rowY = neighbourItem->geometry().y(); + const qreal rowHeight = neighbourItem->geometry().height(); - QRectF geometry = fxTableItem->geometry(); - geometry.setWidth(width); - geometry.setHeight(neighbourItem->geometry().height()); - qreal left = tableEdge == Qt::LeftEdge ? - neighbourItem->geometry().left() - cellSpacing.width() - geometry.width() : - neighbourItem->geometry().right() + cellSpacing.width(); - - geometry.moveLeft(left); - geometry.moveTop(neighbourItem->geometry().top()); - - fxTableItem->setGeometry(geometry); + fxTableItem->setGeometry(QRectF(columnX, rowY, columnWidth, rowHeight)); fxTableItem->setVisible(true); - qCDebug(lcTableViewDelegateLifecycle()) << "layout item:" << QPoint(column, row) << fxTableItem->geometry(); + qCDebug(lcTableViewDelegateLifecycle()) << "layout item:" << QPoint(columnThatNeedsLayout, row) << fxTableItem->geometry(); } } void QQuickTableViewPrivate::layoutHorizontalEdge(Qt::Edge tableEdge) { - int row = (tableEdge == Qt::TopEdge) ? topRow() : bottomRow(); - QPoint neighbourDirection = (tableEdge == Qt::TopEdge) ? kDown : kUp; - qreal height = resolveRowHeight(row); + int rowThatNeedsLayout; + int neighbourRow; + qreal rowY; + qreal rowHeight; + + if (tableEdge == Qt::TopEdge) { + rowThatNeedsLayout = topRow(); + neighbourRow = loadedRows.keys().value(1); + rowHeight = getRowLayoutHeight(rowThatNeedsLayout); + const auto neighbourItem = loadedTableItem(QPoint(leftColumn(), neighbourRow)); + rowY = neighbourItem->geometry().top() - cellSpacing.height() - rowHeight; + } else { + rowThatNeedsLayout = bottomRow(); + neighbourRow = loadedRows.keys().value(loadedRows.count() - 2); + rowHeight = getRowLayoutHeight(rowThatNeedsLayout); + const auto neighbourItem = loadedTableItem(QPoint(leftColumn(), neighbourRow)); + rowY = neighbourItem->geometry().bottom() + cellSpacing.height(); + } for (auto c = loadedColumns.cbegin(); c != loadedColumns.cend(); ++c) { const int column = c.key(); - auto fxTableItem = loadedTableItem(QPoint(column, row)); - auto const neighbourItem = itemNextTo(fxTableItem, neighbourDirection); - - QRectF geometry = fxTableItem->geometry(); - geometry.setWidth(neighbourItem->geometry().width()); - geometry.setHeight(height); - qreal top = tableEdge == Qt::TopEdge ? - neighbourItem->geometry().top() - cellSpacing.height() - geometry.height() : - neighbourItem->geometry().bottom() + cellSpacing.height(); + auto fxTableItem = loadedTableItem(QPoint(column, rowThatNeedsLayout)); + auto const neighbourItem = loadedTableItem(QPoint(column, neighbourRow)); + const qreal columnX = neighbourItem->geometry().x(); + const qreal columnWidth = neighbourItem->geometry().width(); - geometry.moveTop(top); - geometry.moveLeft(neighbourItem->geometry().left()); - - fxTableItem->setGeometry(geometry); + fxTableItem->setGeometry(QRectF(columnX, rowY, columnWidth, rowHeight)); fxTableItem->setVisible(true); - qCDebug(lcTableViewDelegateLifecycle()) << "layout item:" << QPoint(column, row) << fxTableItem->geometry(); + qCDebug(lcTableViewDelegateLifecycle()) << "layout item:" << QPoint(column, rowThatNeedsLayout) << fxTableItem->geometry(); } } void QQuickTableViewPrivate::layoutTopLeftItem() { - const QPoint cell = loadRequest.firstCell(); + const QPoint cell(loadRequest.column(), loadRequest.row()); auto topLeftItem = loadedTableItem(cell); auto item = topLeftItem->item; item->setPosition(loadRequest.startPosition()); - item->setSize(QSizeF(resolveColumnWidth(cell.x()), resolveRowHeight(cell.y()))); + item->setSize(QSizeF(getColumnLayoutWidth(cell.x()), getRowLayoutHeight(cell.y()))); topLeftItem->setVisible(true); qCDebug(lcTableViewDelegateLifecycle) << "geometry:" << topLeftItem->geometry(); } @@ -1135,29 +1345,6 @@ void QQuickTableViewPrivate::layoutTableEdgeFromLoadRequest() } } -void QQuickTableViewPrivate::cancelLoadRequest() -{ - loadRequest.markAsDone(); - model->cancel(modelIndexAtCell(loadRequest.currentCell())); - - if (rebuildScheduled) { - // No reason to rollback already loaded edge items - // since we anyway are about to reload all items. - return; - } - - if (loadRequest.atBeginning()) { - // No items have yet been loaded, so nothing to unload - return; - } - - QLine rollbackItems; - rollbackItems.setP1(loadRequest.firstCell()); - rollbackItems.setP2(loadRequest.previousCell()); - qCDebug(lcTableViewDelegateLifecycle()) << "rollback:" << rollbackItems << tableLayoutToString(); - unloadItems(rollbackItems); -} - void QQuickTableViewPrivate::processLoadRequest() { Q_TABLEVIEW_ASSERT(loadRequest.isActive(), ""); @@ -1183,9 +1370,22 @@ void QQuickTableViewPrivate::processLoadRequest() syncLoadedTableRectFromLoadedTable(); if (rebuildState == RebuildState::Done) { - enforceTableAtOrigin(); - updateContentWidth(); - updateContentHeight(); + // Loading of this edge was not done as a part of a rebuild, but + // instead as an incremental build after e.g a flick. + switch (loadRequest.edge()) { + case Qt::LeftEdge: + case Qt::TopEdge: + enforceTableAtOrigin(); + break; + case Qt::RightEdge: + updateAverageEdgeSize(); + updateContentWidth(); + break; + case Qt::BottomEdge: + updateAverageEdgeSize(); + updateContentHeight(); + break; + } drainReusePoolAfterLoadRequest(); } @@ -1206,7 +1406,7 @@ void QQuickTableViewPrivate::processRebuildTable() if (rebuildState == RebuildState::VerifyTable) { if (loadedItems.isEmpty()) { - qCDebug(lcTableViewDelegateLifecycle()) << "no items loaded, meaning empty model or no delegate"; + qCDebug(lcTableViewDelegateLifecycle()) << "no items loaded, meaning empty model, all rows or columns hidden, or no delegate"; rebuildState = RebuildState::Done; return; } @@ -1230,14 +1430,14 @@ void QQuickTableViewPrivate::processRebuildTable() && reusableFlag == QQmlTableInstanceModel::Reusable); if (rebuildState == RebuildState::PreloadColumns) { - if (preload && rightColumn() < tableSize.width() - 1) + if (preload && nextVisibleEdgeIndexAroundLoadedTable(Qt::RightEdge) != kEdgeIndexAtEnd) loadEdge(Qt::RightEdge, QQmlIncubator::AsynchronousIfNested); if (!moveToNextRebuildState()) return; } if (rebuildState == RebuildState::PreloadRows) { - if (preload && bottomRow() < tableSize.height() - 1) + if (preload && nextVisibleEdgeIndexAroundLoadedTable(Qt::BottomEdge) != kEdgeIndexAtEnd) loadEdge(Qt::BottomEdge, QQmlIncubator::AsynchronousIfNested); if (!moveToNextRebuildState()) return; @@ -1265,19 +1465,26 @@ bool QQuickTableViewPrivate::moveToNextRebuildState() return true; } -void QQuickTableViewPrivate::beginRebuildTable() +QPoint QQuickTableViewPrivate::calculateNewTopLeft() { - if (loadRequest.isActive()) - cancelLoadRequest(); + const int firstVisibleLeft = nextVisibleEdgeIndex(Qt::RightEdge, 0); + const int firstVisibleTop = nextVisibleEdgeIndex(Qt::BottomEdge, 0); - calculateTableSize(); + return QPoint(firstVisibleLeft, firstVisibleTop); +} - QPoint topLeft; - QPointF topLeftPos; +void QQuickTableViewPrivate::calculateTopLeft(QPoint &topLeft, QPointF &topLeftPos) +{ + if (tableSize.isEmpty()) { + releaseLoadedItems(QQmlTableInstanceModel::NotReusable); + topLeft = QPoint(kEdgeIndexAtEnd, kEdgeIndexAtEnd); + return; + } if (rebuildOptions & RebuildOption::All) { qCDebug(lcTableViewDelegateLifecycle()) << "RebuildOption::All"; releaseLoadedItems(QQmlTableInstanceModel::NotReusable); + topLeft = calculateNewTopLeft(); } else if (rebuildOptions & RebuildOption::ViewportOnly) { qCDebug(lcTableViewDelegateLifecycle()) << "RebuildOption::ViewportOnly"; releaseLoadedItems(reusableFlag); @@ -1301,13 +1508,27 @@ void QQuickTableViewPrivate::beginRebuildTable() } else { Q_TABLEVIEW_UNREACHABLE(rebuildOptions); } +} + +void QQuickTableViewPrivate::beginRebuildTable() +{ + calculateTableSize(); + + QPoint topLeft; + QPointF topLeftPos; + calculateTopLeft(topLeft, topLeftPos); loadedColumns.clear(); loadedRows.clear(); loadedTableOuterRect = QRect(); loadedTableInnerRect = QRect(); - contentSizeBenchMarkPoint = QPoint(-1, -1); columnRowPositionsInvalid = false; + clearEdgeSizeCache(); + + if (topLeft.x() == kEdgeIndexAtEnd || topLeft.y() == kEdgeIndexAtEnd) { + // No visible columns or rows, so nothing to load + return; + } loadInitialTopLeftItem(topLeft, topLeftPos); loadAndUnloadVisibleEdges(); @@ -1333,12 +1554,6 @@ void QQuickTableViewPrivate::loadInitialTopLeftItem(const QPoint &cell, const QP { Q_TABLEVIEW_ASSERT(loadedItems.isEmpty(), ""); - if (tableSize.isEmpty()) - return; - - if (model->count() == 0) - return; - if (tableModel && !tableModel->delegate()) return; @@ -1356,18 +1571,25 @@ void QQuickTableViewPrivate::unloadEdge(Qt::Edge edge) case Qt::LeftEdge: case Qt::RightEdge: { const int column = edge == Qt::LeftEdge ? leftColumn() : rightColumn(); - unloadItems(QLine(column, topRow(), column, bottomRow())); + for (auto r = loadedRows.cbegin(); r != loadedRows.cend(); ++r) + unloadItem(QPoint(column, r.key())); loadedColumns.remove(column); + syncLoadedTableRectFromLoadedTable(); + updateAverageEdgeSize(); + updateContentWidth(); break; } case Qt::TopEdge: case Qt::BottomEdge: { const int row = edge == Qt::TopEdge ? topRow() : bottomRow(); - unloadItems(QLine(leftColumn(), row, rightColumn(), row)); + for (auto c = loadedColumns.cbegin(); c != loadedColumns.cend(); ++c) + unloadItem(QPoint(c.key(), row)); loadedRows.remove(row); + syncLoadedTableRectFromLoadedTable(); + updateAverageEdgeSize(); + updateContentHeight(); break; } } - syncLoadedTableRectFromLoadedTable(); qCDebug(lcTableViewDelegateLifecycle) << tableLayoutToString(); } @@ -1375,20 +1597,10 @@ void QQuickTableViewPrivate::loadEdge(Qt::Edge edge, QQmlIncubator::IncubationMo { const int edgeIndex = nextVisibleEdgeIndexAroundLoadedTable(edge); qCDebug(lcTableViewDelegateLifecycle) << edge << edgeIndex; - QLine cellsToLoad; - - switch (edge) { - case Qt::LeftEdge: - case Qt::RightEdge: - cellsToLoad = QLine(edgeIndex, topRow(), edgeIndex, bottomRow()); - break; - case Qt::TopEdge: - case Qt::BottomEdge: - cellsToLoad = QLine(leftColumn(), edgeIndex, rightColumn(), edgeIndex); - break; - } - loadRequest.begin(cellsToLoad, edge, incubationMode); + const QList visibleCells = edge & (Qt::LeftEdge | Qt::RightEdge) + ? loadedRows.keys() : loadedColumns.keys(); + loadRequest.begin(edge, edgeIndex, visibleCells, incubationMode); processLoadRequest(); } @@ -1529,8 +1741,12 @@ void QQuickTableViewPrivate::updatePolish() if (loadedItems.isEmpty()) return; - if (columnRowPositionsInvalid) + if (columnRowPositionsInvalid) { relayoutTable(); + updateAverageEdgeSize(); + updateContentWidth(); + updateContentHeight(); + } loadAndUnloadVisibleEdges(); } @@ -1967,16 +2183,7 @@ void QQuickTableView::setContentHeight(qreal height) void QQuickTableView::forceLayout() { - Q_D(QQuickTableView); - d->columnRowPositionsInvalid = true; - - if (d->polishing) { - qWarning() << "TableView::forceLayout(): Cannot do an immediate re-layout during an ongoing layout!"; - polish(); - return; - } - - d->updatePolish(); + d_func()->forceLayout(); } QQuickTableViewAttached *QQuickTableView::qmlAttachedProperties(QObject *obj) diff --git a/src/quick/items/qquicktableview_p_p.h b/src/quick/items/qquicktableview_p_p.h index ed6f8026a2..9bea8040dc 100644 --- a/src/quick/items/qquicktableview_p_p.h +++ b/src/quick/items/qquicktableview_p_p.h @@ -88,54 +88,52 @@ public: public: void begin(const QPoint &cell, const QPointF &pos, QQmlIncubator::IncubationMode incubationMode) { - Q_ASSERT(!active); - active = true; - tableEdge = Qt::Edge(0); - tableCells = QLine(cell, cell); - mode = incubationMode; - cellCount = 1; - currentIndex = 0; - startPos = pos; + Q_ASSERT(!m_active); + m_active = true; + m_edge = Qt::Edge(0); + m_mode = incubationMode; + m_edgeIndex = cell.x(); + m_visibleCellsInEdge.clear(); + m_visibleCellsInEdge.append(cell.y()); + m_currentIndex = 0; + m_startPos = pos; qCDebug(lcTableViewDelegateLifecycle()) << "begin top-left:" << toString(); } - void begin(const QLine cellsToLoad, Qt::Edge edgeToLoad, QQmlIncubator::IncubationMode incubationMode) + void begin(Qt::Edge edgeToLoad, int edgeIndex, const QList visibleCellsInEdge, QQmlIncubator::IncubationMode incubationMode) { - Q_ASSERT(!active); - active = true; - tableEdge = edgeToLoad; - tableCells = cellsToLoad; - mode = incubationMode; - cellCount = tableCells.x2() - tableCells.x1() + tableCells.y2() - tableCells.y1() + 1; - currentIndex = 0; + Q_ASSERT(!m_active); + m_active = true; + m_edge = edgeToLoad; + m_edgeIndex = edgeIndex; + m_visibleCellsInEdge = visibleCellsInEdge; + m_mode = incubationMode; + m_currentIndex = 0; qCDebug(lcTableViewDelegateLifecycle()) << "begin:" << toString(); } - inline void markAsDone() { active = false; } - inline bool isActive() { return active; } + inline void markAsDone() { m_active = false; } + inline bool isActive() { return m_active; } - inline QPoint firstCell() { return tableCells.p1(); } - inline QPoint lastCell() { return tableCells.p2(); } - inline QPoint currentCell() { return cellAt(currentIndex); } - inline QPoint previousCell() { return cellAt(currentIndex - 1); } + inline QPoint currentCell() { return cellAt(m_currentIndex); } + inline bool hasCurrentCell() { return m_currentIndex < m_visibleCellsInEdge.count(); } + inline void moveToNextCell() { ++m_currentIndex; } - inline bool atBeginning() { return currentIndex == 0; } - inline bool hasCurrentCell() { return currentIndex < cellCount; } - inline void moveToNextCell() { ++currentIndex; } + inline Qt::Edge edge() { return m_edge; } + inline int row() { return cellAt(0).y(); } + inline int column() { return cellAt(0).x(); } + inline QQmlIncubator::IncubationMode incubationMode() { return m_mode; } - inline Qt::Edge edge() { return tableEdge; } - inline QQmlIncubator::IncubationMode incubationMode() { return mode; } - - inline QPointF startPosition() { return startPos; } + inline QPointF startPosition() { return m_startPos; } QString toString() { QString str; QDebug dbg(&str); dbg.nospace() << "TableSectionLoadRequest(" << "edge:" - << tableEdge << " cells:" << tableCells << " incubation:"; + << m_edge << ", edgeIndex:" << m_edgeIndex << ", incubation:"; - switch (mode) { + switch (m_mode) { case QQmlIncubator::Asynchronous: dbg << "Asynchronous"; break; @@ -151,22 +149,31 @@ public: } private: - Qt::Edge tableEdge = Qt::Edge(0); - QLine tableCells; - int currentIndex = 0; - int cellCount = 0; - bool active = false; - QQmlIncubator::IncubationMode mode = QQmlIncubator::AsynchronousIfNested; - QPointF startPos; - - QPoint cellAt(int index) - { - int x = tableCells.p1().x() + (tableCells.dx() ? index : 0); - int y = tableCells.p1().y() + (tableCells.dy() ? index : 0); - return QPoint(x, y); + Qt::Edge m_edge = Qt::Edge(0); + QList m_visibleCellsInEdge; + int m_edgeIndex = 0; + int m_currentIndex = 0; + bool m_active = false; + QQmlIncubator::IncubationMode m_mode = QQmlIncubator::AsynchronousIfNested; + QPointF m_startPos; + + inline QPoint cellAt(int index) { + return !m_edge || (m_edge & (Qt::LeftEdge | Qt::RightEdge)) + ? QPoint(m_edgeIndex, m_visibleCellsInEdge[index]) + : QPoint(m_visibleCellsInEdge[index], m_edgeIndex); } }; + class EdgeRange { + public: + EdgeRange(); + bool containsIndex(Qt::Edge edge, int index); + + int startIndex; + int endIndex; + qreal size; + }; + enum class RebuildState { Begin = 0, LoadInitalTable, @@ -234,7 +241,6 @@ public: TableEdgeLoadRequest loadRequest; - QPoint contentSizeBenchMarkPoint = QPoint(-1, -1); QSizeF cellSpacing = QSizeF(0, 0); QQmlTableInstanceModel::ReusableFlag reusableFlag = QQmlTableInstanceModel::Reusable; @@ -248,6 +254,10 @@ public: QJSValue rowHeightProvider; QJSValue columnWidthProvider; + EdgeRange cachedNextVisibleEdgeIndex[4]; + EdgeRange cachedColumnWidth; + EdgeRange cachedRowHeight; + // TableView uses contentWidth/height to report the size of the table (this // will e.g make scrollbars written for Flickable work out of the box). This // value is continuously calculated, and will change/improve as more columns @@ -281,8 +291,13 @@ public: qreal sizeHintForRow(int row); void calculateTableSize(); - qreal resolveColumnWidth(int column); - qreal resolveRowHeight(int row); + inline bool isColumnHidden(int column); + inline bool isRowHidden(int row); + + qreal getColumnLayoutWidth(int column); + qreal getRowLayoutHeight(int row); + qreal getColumnWidth(int column); + qreal getRowHeight(int row); inline int topRow() const { return loadedRows.firstKey(); } inline int bottomRow() const { return loadedRows.lastKey(); } @@ -300,12 +315,16 @@ public: void updateContentWidth(); void updateContentHeight(); void updateAverageEdgeSize(); + void forceLayout(); void enforceTableAtOrigin(); void syncLoadedTableRectFromLoadedTable(); void syncLoadedTableFromLoadRequest(); + int nextVisibleEdgeIndex(Qt::Edge edge, int startIndex); int nextVisibleEdgeIndexAroundLoadedTable(Qt::Edge edge); + inline int edgeToArrayIndex(Qt::Edge edge); + void clearEdgeSizeCache(); bool canLoadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const; bool canUnloadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const; @@ -316,7 +335,6 @@ public: qreal cellHeight(const QPoint &cell); FxTableItem *loadedTableItem(const QPoint &cell) const; - FxTableItem *itemNextTo(const FxTableItem *fxTableItem, const QPoint &direction) const; FxTableItem *createFxTableItem(const QPoint &cell, QQmlIncubator::IncubationMode incubationMode); FxTableItem *loadFxTableItem(const QPoint &cell, QQmlIncubator::IncubationMode incubationMode); @@ -324,18 +342,17 @@ public: void releaseLoadedItems(QQmlTableInstanceModel::ReusableFlag reusableFlag); void unloadItem(const QPoint &cell); - void unloadItems(const QLine &items); - void loadInitialTopLeftItem(const QPoint &cell, const QPointF &pos); void loadEdge(Qt::Edge edge, QQmlIncubator::IncubationMode incubationMode); void unloadEdge(Qt::Edge edge); void loadAndUnloadVisibleEdges(); void drainReusePoolAfterLoadRequest(); - void cancelLoadRequest(); void processLoadRequest(); void processRebuildTable(); bool moveToNextRebuildState(); + QPoint calculateNewTopLeft(); + void calculateTopLeft(QPoint &topLeft, QPointF &topLeftPos); void beginRebuildTable(); void layoutAfterLoadingInitialTable(); diff --git a/tests/auto/quick/qquicktableview/data/hiderowsandcolumns.qml b/tests/auto/quick/qquicktableview/data/hiderowsandcolumns.qml new file mode 100644 index 0000000000..b11cb1476c --- /dev/null +++ b/tests/auto/quick/qquicktableview/data/hiderowsandcolumns.qml @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Window 2.3 + +Item { + width: 640 + height: 450 + + property alias tableView: tableView + property var rowsToHide + property var columnsToHide + + TableView { + id: tableView + width: 600 + height: 400 + anchors.margins: 1 + clip: true + delegate: tableViewDelegate + columnSpacing: 1 + rowSpacing: 1 + columnWidthProvider: function(column) { + if (columnsToHide.includes(column)) + return 0; + } + rowHeightProvider: function(row) { + if (rowsToHide.includes(row)) + return 0; + } + } + + Component { + id: tableViewDelegate + Rectangle { + objectName: "tableViewDelegate" + color: "lightgray" + border.width: 1 + implicitWidth: 50 + implicitHeight: 50 + Text { + anchors.centerIn: parent + text: column + "," + row + } + } + } + +} diff --git a/tests/auto/quick/qquicktableview/data/usefaultyrowcolumnprovider.qml b/tests/auto/quick/qquicktableview/data/usefaultyrowcolumnprovider.qml index 32d1fc9d0d..1e35d65bcd 100644 --- a/tests/auto/quick/qquicktableview/data/usefaultyrowcolumnprovider.qml +++ b/tests/auto/quick/qquicktableview/data/usefaultyrowcolumnprovider.qml @@ -56,16 +56,14 @@ Item { delegate: tableViewDelegate columnSpacing: 1 rowSpacing: 1 - columnWidthProvider: function(column) { } - rowHeightProvider: function(row) { return 0 } + columnWidthProvider: function(column) { return "notAValidValue" } + rowHeightProvider: function(row) { return "notAValidValue" } } Component { id: tableViewDelegate Rectangle { objectName: "tableViewDelegate" - implicitWidth: 20 - implicitHeight: 20 color: "lightgray" border.width: 1 Text { diff --git a/tests/auto/quick/qquicktableview/data/userowcolumnprovider.qml b/tests/auto/quick/qquicktableview/data/userowcolumnprovider.qml index 04d12f8d20..e9f01b6abf 100644 --- a/tests/auto/quick/qquicktableview/data/userowcolumnprovider.qml +++ b/tests/auto/quick/qquicktableview/data/userowcolumnprovider.qml @@ -46,6 +46,8 @@ Item { property alias tableView: tableView property Component delegate: tableViewDelegate + property bool returnNegativeColumnWidth: false + property bool returnNegativeRowHeight: false TableView { id: tableView @@ -56,8 +58,16 @@ Item { delegate: tableViewDelegate columnSpacing: 1 rowSpacing: 1 - columnWidthProvider: function(column) { return column + 10 } - rowHeightProvider: function(row) { return row + 10 } + columnWidthProvider: function(column) { + if (returnNegativeColumnWidth) + return -1 + return column + 10 + } + rowHeightProvider: function(row) { + if (returnNegativeRowHeight) + return -1 + return row + 10 + } } Component { diff --git a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp index d2ec9948c9..e263427b59 100644 --- a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp +++ b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp @@ -111,10 +111,12 @@ private slots: void checkDelegateWithAnchors(); void checkColumnWidthProvider(); void checkColumnWidthProviderInvalidReturnValues(); + void checkColumnWidthProviderNegativeReturnValue(); void checkColumnWidthProviderNotCallable(); void checkRowHeightWithoutProvider(); void checkRowHeightProvider(); void checkRowHeightProviderInvalidReturnValues(); + void checkRowHeightProviderNegativeReturnValue(); void checkRowHeightProviderNotCallable(); void checkForceLayoutFunction(); void checkContentWidthAndHeight(); @@ -157,6 +159,8 @@ private slots: void checkRebuildViewportOnly(); void useDelegateChooserWithoutDefault(); void checkTableviewInsideAsyncLoader(); + void hideRowsAndColumns_data(); + void hideRowsAndColumns(); }; tst_QQuickTableView::tst_QQuickTableView() @@ -373,7 +377,7 @@ void tst_QQuickTableView::checkColumnWidthProviderInvalidReturnValues() tableView->setModel(model); - QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*Provider.*valid")); + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*implicitHeight.*zero")); WAIT_UNTIL_POLISHED; @@ -381,6 +385,23 @@ void tst_QQuickTableView::checkColumnWidthProviderInvalidReturnValues() QCOMPARE(fxItem->item->width(), kDefaultColumnWidth); } +void tst_QQuickTableView::checkColumnWidthProviderNegativeReturnValue() +{ + // Check that we fall back to use the implicit width of the delegate + // items if the columnWidthProvider return a negative number. + LOAD_TABLEVIEW("userowcolumnprovider.qml"); + + auto model = TestModelAsVariant(10, 10); + view->rootObject()->setProperty("returnNegativeColumnWidth", true); + + tableView->setModel(model); + + WAIT_UNTIL_POLISHED; + + for (auto fxItem : tableViewPrivate->loadedItems) + QCOMPARE(fxItem->item->width(), 20); +} + void tst_QQuickTableView::checkColumnWidthProviderNotCallable() { // Check that we fall back to use default columns widths, if you @@ -453,7 +474,7 @@ void tst_QQuickTableView::checkRowHeightProviderInvalidReturnValues() tableView->setModel(model); - QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*Provider.*valid")); + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*implicitHeight.*zero")); WAIT_UNTIL_POLISHED; @@ -461,6 +482,23 @@ void tst_QQuickTableView::checkRowHeightProviderInvalidReturnValues() QCOMPARE(fxItem->item->height(), kDefaultRowHeight); } +void tst_QQuickTableView::checkRowHeightProviderNegativeReturnValue() +{ + // Check that we fall back to use the implicit height of the delegate + // items if the rowHeightProvider return a negative number. + LOAD_TABLEVIEW("userowcolumnprovider.qml"); + + auto model = TestModelAsVariant(10, 10); + view->rootObject()->setProperty("returnNegativeRowHeight", true); + + tableView->setModel(model); + + WAIT_UNTIL_POLISHED; + + for (auto fxItem : tableViewPrivate->loadedItems) + QCOMPARE(fxItem->item->height(), 20); +} + void tst_QQuickTableView::checkRowHeightProviderNotCallable() { // Check that we fall back to use default row heights, if you @@ -538,6 +576,8 @@ void tst_QQuickTableView::checkContentWidthAndHeight() const qreal expectedSizeInit = (tableSize * cellSizeSmall) + ((tableSize - 1) * spacing); QCOMPARE(tableView->contentWidth(), expectedSizeInit); QCOMPARE(tableView->contentHeight(), expectedSizeInit); + QCOMPARE(tableViewPrivate->averageEdgeSize.width(), cellSizeSmall); + QCOMPARE(tableViewPrivate->averageEdgeSize.height(), cellSizeSmall); // Flick in 5 more rows and columns, but not so far that we start loading in // the ones that are bigger. Loading in more rows and columns of the same @@ -548,6 +588,8 @@ void tst_QQuickTableView::checkContentWidthAndHeight() QCOMPARE(tableView->contentWidth(), expectedSizeInit); QCOMPARE(tableView->contentHeight(), expectedSizeInit); + QCOMPARE(tableViewPrivate->averageEdgeSize.width(), cellSizeSmall); + QCOMPARE(tableViewPrivate->averageEdgeSize.height(), cellSizeSmall); // Flick to row and column 20 (smallCellCount), since there the row and // column sizes increases with 100. Check that TableView then adjusts @@ -562,6 +604,11 @@ void tst_QQuickTableView::checkContentWidthAndHeight() QVERIFY(tableViewPrivate->rebuildScheduled); WAIT_UNTIL_POLISHED; + // Check that the average cell size is now matching the + // large cells since they fill up the whole view. + QCOMPARE(tableViewPrivate->averageEdgeSize.width(), cellSizeLarge); + QCOMPARE(tableViewPrivate->averageEdgeSize.height(), cellSizeLarge); + const int largeSizeCellCountInView = qCeil(tableView->width() / cellSizeLarge); const int columnCount = smallCellCount + largeSizeCellCountInView; QCOMPARE(tableViewPrivate->leftColumn(), smallCellCount); @@ -571,41 +618,47 @@ void tst_QQuickTableView::checkContentWidthAndHeight() const qreal secondHalfOneScreenLength = largeSizeCellCountInView * cellSizeLarge; const qreal lengthAfterFlick = firstHalfLength + secondHalfOneScreenLength; - const qreal averageCellSize = lengthAfterFlick / columnCount; - const qreal expectedSizeHalf = (tableSize * averageCellSize) + accumulatedSpacing; - - QCOMPARE(tableView->contentWidth(), expectedSizeHalf); - QCOMPARE(tableView->contentHeight(), expectedSizeHalf); + // Check that loadedTableOuterRect has been calculated correct thus far + const qreal spacingAfterFlick = (smallCellCount + largeSizeCellCountInView - 1) * spacing; + QCOMPARE(tableViewPrivate->loadedTableOuterRect.left(), flickTo + spacing); + QCOMPARE(tableViewPrivate->loadedTableOuterRect.right(), lengthAfterFlick + spacingAfterFlick); + QCOMPARE(tableViewPrivate->loadedTableOuterRect.top(), flickTo + spacing); + QCOMPARE(tableViewPrivate->loadedTableOuterRect.bottom(), lengthAfterFlick + spacingAfterFlick); + + // At this point, we should have the exact content width/height set, because + // TableView knows where the large cells start in the viewport, and how many + // columns that remain in the model. It will assume that the rest of the the + // columns have the same average size as the ones currently inside the viewport. + const qreal expectedContentSize = (smallCellCount * cellSizeSmall) + (largeCellCount * cellSizeLarge) + accumulatedSpacing; + QCOMPARE(tableView->contentWidth(), expectedContentSize); + QCOMPARE(tableView->contentHeight(), expectedContentSize); // Flick to the end (row/column 100, and overshoot a bit), and // check that we then end up with the exact content width/height. const qreal secondHalfLength = largeCellCount * cellSizeLarge; const qreal expectedFullSize = (firstHalfLength + secondHalfLength) + accumulatedSpacing; - - // If we flick more than one page at a time, tableview will jump to the new - // position and rebuild the table without loading the edges in-between. Which - // row and column that ends up as new top-left is then based on a prediction, and - // therefore unreliable. To avoid this to happen (which will also affect the - // reported size of the table), we flick to the end position in smaller chuncks. - QVERIFY(!tableViewPrivate->polishScheduled); - QVERIFY(!tableViewPrivate->rebuildScheduled); - int pages = qCeil((expectedFullSize - tableView->contentX()) / tableView->width()); - for (int i = 0; i < pages; i++) { - tableView->setContentX(tableView->contentX() + tableView->width() - 1); - tableView->setContentY(tableView->contentY() + tableView->height() - 1); - QVERIFY(!tableViewPrivate->rebuildScheduled); - } + const qreal overshoot = 100; + const qreal endPosX = expectedFullSize - tableView->width() + overshoot; + const qreal endPosY = expectedFullSize - tableView->height() + overshoot; + tableView->setContentX(endPosX); + tableView->setContentY(endPosY); QCOMPARE(tableView->contentWidth(), expectedFullSize); QCOMPARE(tableView->contentHeight(), expectedFullSize); - // Flick back to start. Since we know the actual table - // size, contentWidth/Height shouldn't change. + // Flick back to start tableView->setContentX(0); tableView->setContentY(0); - QCOMPARE(tableView->contentWidth(), expectedFullSize); - QCOMPARE(tableView->contentHeight(), expectedFullSize); + // Since we move the viewport more than a page, tableview + // will jump to the new position and do a rebuild. + QVERIFY(tableViewPrivate->polishScheduled); + QVERIFY(tableViewPrivate->rebuildScheduled); + WAIT_UNTIL_POLISHED; + + // We should now have the same content width/height as when we started + QCOMPARE(tableView->contentWidth(), expectedSizeInit); + QCOMPARE(tableView->contentHeight(), expectedSizeInit); } void tst_QQuickTableView::checkPageFlicking() @@ -2001,6 +2054,92 @@ void tst_QQuickTableView::checkTableviewInsideAsyncLoader() QVERIFY(height > 0); }; +#define INT_LIST(indices) QVariant::fromValue(QList() << indices) + +void tst_QQuickTableView::hideRowsAndColumns_data() +{ + QTest::addColumn("rowsToHide"); + QTest::addColumn("columnsToHide"); + + const auto emptyList = QVariant::fromValue(QList()); + + // Hide rows + QTest::newRow("first") << INT_LIST(0) << emptyList; + QTest::newRow("middle 1") << INT_LIST(1) << emptyList; + QTest::newRow("middle 3") << INT_LIST(3) << emptyList; + QTest::newRow("last") << INT_LIST(4) << emptyList; + + QTest::newRow("subsequent 0,1") << INT_LIST(0 << 1) << emptyList; + QTest::newRow("subsequent 1,2") << INT_LIST(1 << 2) << emptyList; + QTest::newRow("subsequent 3,4") << INT_LIST(3 << 4) << emptyList; + + QTest::newRow("all but first") << INT_LIST(1 << 2 << 3 << 4) << emptyList; + QTest::newRow("all but last") << INT_LIST(0 << 1 << 2 << 3) << emptyList; + QTest::newRow("all but middle") << INT_LIST(0 << 1 << 3 << 4) << emptyList; + + // Hide columns + QTest::newRow("first") << emptyList << INT_LIST(0); + QTest::newRow("middle 1") << emptyList << INT_LIST(1); + QTest::newRow("middle 3") << emptyList << INT_LIST(3); + QTest::newRow("last") << emptyList << INT_LIST(4); + + QTest::newRow("subsequent 0,1") << emptyList << INT_LIST(0 << 1); + QTest::newRow("subsequent 1,2") << emptyList << INT_LIST(1 << 2); + QTest::newRow("subsequent 3,4") << emptyList << INT_LIST(3 << 4); + + QTest::newRow("all but first") << emptyList << INT_LIST(1 << 2 << 3 << 4); + QTest::newRow("all but last") << emptyList << INT_LIST(0 << 1 << 2 << 3); + QTest::newRow("all but middle") << emptyList << INT_LIST(0 << 1 << 3 << 4); + + // Hide both rows and columns at the same time + QTest::newRow("first") << INT_LIST(0) << INT_LIST(0); + QTest::newRow("middle 1") << INT_LIST(1) << INT_LIST(1); + QTest::newRow("middle 3") << INT_LIST(3) << INT_LIST(3); + QTest::newRow("last") << INT_LIST(4) << INT_LIST(4); + + QTest::newRow("subsequent 0,1") << INT_LIST(0 << 1) << INT_LIST(0 << 1); + QTest::newRow("subsequent 1,2") << INT_LIST(1 << 2) << INT_LIST(1 << 2); + QTest::newRow("subsequent 3,4") << INT_LIST(3 << 4) << INT_LIST(3 << 4); + + QTest::newRow("all but first") << INT_LIST(1 << 2 << 3 << 4) << INT_LIST(1 << 2 << 3 << 4); + QTest::newRow("all but last") << INT_LIST(0 << 1 << 2 << 3) << INT_LIST(0 << 1 << 2 << 3); + QTest::newRow("all but middle") << INT_LIST(0 << 1 << 3 << 4) << INT_LIST(0 << 1 << 3 << 4); + + // Hide all rows and columns + QTest::newRow("all") << INT_LIST(0 << 1 << 2 << 3 << 4) << INT_LIST(0 << 1 << 2 << 3 << 4); +} + +void tst_QQuickTableView::hideRowsAndColumns() +{ + // Check that you can hide the first row (corner case) + // and that we load the other columns as expected. + QFETCH(QVariant, rowsToHide); + QFETCH(QVariant, columnsToHide); + LOAD_TABLEVIEW("hiderowsandcolumns.qml"); + + const QList rowsToHideList = qvariant_cast>(rowsToHide); + const QList columnsToHideList = qvariant_cast>(columnsToHide); + const int modelSize = 5; + auto model = TestModelAsVariant(modelSize, modelSize); + view->rootObject()->setProperty("rowsToHide", rowsToHide); + view->rootObject()->setProperty("columnsToHide", columnsToHide); + + tableView->setModel(model); + + WAIT_UNTIL_POLISHED; + + const int expectedRowCount = modelSize - rowsToHideList.count(); + const int expectedColumnCount = modelSize - columnsToHideList.count(); + QCOMPARE(tableViewPrivate->loadedRows.count(), expectedRowCount); + QCOMPARE(tableViewPrivate->loadedColumns.count(), expectedColumnCount); + + for (const int row : tableViewPrivate->loadedRows.keys()) + QVERIFY(!rowsToHideList.contains(row)); + + for (const int column : tableViewPrivate->loadedColumns.keys()) + QVERIFY(!columnsToHideList.contains(column)); +} + QTEST_MAIN(tst_QQuickTableView) #include "tst_qquicktableview.moc" -- cgit v1.2.3 From 896d49a4e1113e3eb4832b83920b0dfd76987259 Mon Sep 17 00:00:00 2001 From: Jani Heikkinen Date: Thu, 7 Feb 2019 15:04:24 +0200 Subject: Update tests/auto/qml/ecmascripttests/test262 Task-number: QTBUG-73454 Change-Id: I12925ce49cc18f4bb6908a5515fc476b32a222dc Reviewed-by: Simon Hausmann --- tests/auto/qml/ecmascripttests/test262 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/auto/qml/ecmascripttests/test262 b/tests/auto/qml/ecmascripttests/test262 index 3c69133cc4..6b0c42c63c 160000 --- a/tests/auto/qml/ecmascripttests/test262 +++ b/tests/auto/qml/ecmascripttests/test262 @@ -1 +1 @@ -Subproject commit 3c69133cc419840c1be34638039cd8c48a7ef581 +Subproject commit 6b0c42c63c2492bd0a7a96d3179d122b5f71793f -- cgit v1.2.3 From 734f5a31c7a72d87a99103051ed327addae0ab99 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Fri, 8 Feb 2019 21:02:52 +0100 Subject: Remove blacklisting of several tests that are passing ...based on statistics visible in Grafana. Task-number: QTBUG-41895 Task-number: QTBUG-38209 Task-number: QTBUG-65978 Task-number: QTBUG-36804 Task-number: QTBUG-45466 Task-number: QTBUG-29062 Change-Id: If62d2413ede234d33b411e0ecb4f93ec6e0f8062 Reviewed-by: Shawn Rutledge --- tests/auto/quick/qquickanimations/BLACKLIST | 2 -- tests/auto/quick/qquickbehaviors/BLACKLIST | 2 -- tests/auto/quick/qquickflickable/BLACKLIST | 4 ---- tests/auto/quick/qquickgridview/BLACKLIST | 2 -- tests/auto/quick/qquickimage/BLACKLIST | 4 ---- tests/auto/quick/qquicklistview/BLACKLIST | 2 -- tests/auto/quick/qquicktextinput/BLACKLIST | 3 --- 7 files changed, 19 deletions(-) delete mode 100644 tests/auto/quick/qquickbehaviors/BLACKLIST delete mode 100644 tests/auto/quick/qquickgridview/BLACKLIST delete mode 100644 tests/auto/quick/qquickimage/BLACKLIST delete mode 100644 tests/auto/quick/qquicktextinput/BLACKLIST diff --git a/tests/auto/quick/qquickanimations/BLACKLIST b/tests/auto/quick/qquickanimations/BLACKLIST index e011db46b7..29a022b4fe 100644 --- a/tests/auto/quick/qquickanimations/BLACKLIST +++ b/tests/auto/quick/qquickanimations/BLACKLIST @@ -1,6 +1,4 @@ # QTBUG-45466 QTBUG-29062 [simpleProperty] osx-10.9 developer-build -[simplePath] -windows gcc developer-build diff --git a/tests/auto/quick/qquickbehaviors/BLACKLIST b/tests/auto/quick/qquickbehaviors/BLACKLIST deleted file mode 100644 index 9be4da176d..0000000000 --- a/tests/auto/quick/qquickbehaviors/BLACKLIST +++ /dev/null @@ -1,2 +0,0 @@ -[currentValue] -windows diff --git a/tests/auto/quick/qquickflickable/BLACKLIST b/tests/auto/quick/qquickflickable/BLACKLIST index cc91754e68..5d8719db08 100644 --- a/tests/auto/quick/qquickflickable/BLACKLIST +++ b/tests/auto/quick/qquickflickable/BLACKLIST @@ -1,7 +1,3 @@ # QTBUG-26696 [rebound] osx -[stopAtBounds] -windows developer-build -[returnToBounds] -osx diff --git a/tests/auto/quick/qquickgridview/BLACKLIST b/tests/auto/quick/qquickgridview/BLACKLIST deleted file mode 100644 index 9eb9940aa5..0000000000 --- a/tests/auto/quick/qquickgridview/BLACKLIST +++ /dev/null @@ -1,2 +0,0 @@ -[snapOneRow:horizontal, right to left] -windows diff --git a/tests/auto/quick/qquickimage/BLACKLIST b/tests/auto/quick/qquickimage/BLACKLIST deleted file mode 100644 index d15fae1b67..0000000000 --- a/tests/auto/quick/qquickimage/BLACKLIST +++ /dev/null @@ -1,4 +0,0 @@ -# QTBUG-65978 -[nullPixmapPaint] -b2qt - diff --git a/tests/auto/quick/qquicklistview/BLACKLIST b/tests/auto/quick/qquicklistview/BLACKLIST index 15aea4be19..e22d52294f 100644 --- a/tests/auto/quick/qquicklistview/BLACKLIST +++ b/tests/auto/quick/qquicklistview/BLACKLIST @@ -1,5 +1,3 @@ -[QTBUG_38209] -* [enforceRange_withoutHighlight] osx #QTBUG-53863 diff --git a/tests/auto/quick/qquicktextinput/BLACKLIST b/tests/auto/quick/qquicktextinput/BLACKLIST deleted file mode 100644 index e9f4f11c58..0000000000 --- a/tests/auto/quick/qquicktextinput/BLACKLIST +++ /dev/null @@ -1,3 +0,0 @@ -# QTBUG-41895 -[tripleClickSelectsAll] -windows -- cgit v1.2.3 From 24a686077579b04874f956a1dbb212a91836b64e Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Sun, 10 Feb 2019 21:21:36 +0100 Subject: Un-blacklist tst_qquickflickable::rebound Fixes: QTBUG-26696 Change-Id: I32cebc9e0b63b55113f24fd40c06e7017b763aaf Reviewed-by: Shawn Rutledge --- tests/auto/quick/qquickflickable/BLACKLIST | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 tests/auto/quick/qquickflickable/BLACKLIST diff --git a/tests/auto/quick/qquickflickable/BLACKLIST b/tests/auto/quick/qquickflickable/BLACKLIST deleted file mode 100644 index 5d8719db08..0000000000 --- a/tests/auto/quick/qquickflickable/BLACKLIST +++ /dev/null @@ -1,3 +0,0 @@ -# QTBUG-26696 -[rebound] -osx -- cgit v1.2.3 From f2cc7c8580f2a25554cf37426096935c62a5b7af Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Sun, 10 Feb 2019 21:17:53 +0100 Subject: Un-blacklist tst_qquickanimations macOS 10.9 is no longer supported. Fixes: QTBUG-45466 Change-Id: I44ce5e6c7d941cf21d937fb0cfe54ee418d28c4e Reviewed-by: Shawn Rutledge --- tests/auto/quick/qquickanimations/BLACKLIST | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 tests/auto/quick/qquickanimations/BLACKLIST diff --git a/tests/auto/quick/qquickanimations/BLACKLIST b/tests/auto/quick/qquickanimations/BLACKLIST deleted file mode 100644 index 29a022b4fe..0000000000 --- a/tests/auto/quick/qquickanimations/BLACKLIST +++ /dev/null @@ -1,4 +0,0 @@ -# QTBUG-45466 QTBUG-29062 -[simpleProperty] -osx-10.9 developer-build - -- cgit v1.2.3 From a804f31ee2665501c1894cbae8302db181090bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Arve=20S=C3=A6ther?= Date: Thu, 7 Feb 2019 13:22:49 +0100 Subject: Move initView() to tests/auto/quick/shared According to our "Coin - Flaky Tests" dashboard on our grafana, initView() in tst_qquickmousearea seems to have very few flakiness issues in actually exposing a QQuickView. It is therefore a good idea to share this implementation so that it can be used by other tests too. Change-Id: Ie83cbf7d00fa02bdd4699757471fa180945851e3 Reviewed-by: Shawn Rutledge --- .../quick/qquickmousearea/tst_qquickmousearea.cpp | 109 ++++++++------------- tests/auto/quick/shared/viewtestutil.cpp | 27 +++++ tests/auto/quick/shared/viewtestutil.h | 4 + 3 files changed, 73 insertions(+), 67 deletions(-) diff --git a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp index 558ca2e759..52d1458a53 100644 --- a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp +++ b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp @@ -43,31 +43,6 @@ #include #include -// Initialize view, set Url, center in available geometry, move mouse away if desired -static bool initView(QQuickView &v, const QUrl &url, bool moveMouseOut, QByteArray *errorMessage) -{ - v.setBaseSize(QSize(240,320)); - v.setSource(url); - while (v.status() == QQuickView::Loading) - QTest::qWait(10); - if (v.status() != QQuickView::Ready) { - foreach (const QQmlError &e, v.errors()) - errorMessage->append(e.toString().toLocal8Bit() + '\n'); - return false; - } - const QRect screenGeometry = v.screen()->availableGeometry(); - const QSize size = v.size(); - const QPoint offset = QPoint(size.width() / 2, size.height() / 2); - v.setFramePosition(screenGeometry.center() - offset); -#if QT_CONFIG(cursor) // Get the cursor out of the way. - if (moveMouseOut) - QCursor::setPos(v.geometry().topRight() + QPoint(100, 100)); -#else - Q_UNUSED(moveMouseOut) -#endif - return true; -} - class CircleMask : public QObject { Q_OBJECT @@ -235,7 +210,7 @@ void tst_QQuickMouseArea::dragProperties() QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("dragproperties.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("dragproperties.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QVERIFY(window.rootObject() != nullptr); @@ -333,7 +308,7 @@ void tst_QQuickMouseArea::resetDrag() QQuickView window; QByteArray errorMessage; window.rootContext()->setContextProperty("haveTarget", QVariant(true)); - QVERIFY2(initView(window, testFileUrl("dragreset.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("dragreset.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QVERIFY(window.rootObject() != nullptr); @@ -363,7 +338,7 @@ void tst_QQuickMouseArea::dragging() QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("dragging.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("dragging.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); @@ -428,7 +403,7 @@ void tst_QQuickMouseArea::dragSmoothed() { QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("dragging.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("dragging.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); @@ -482,7 +457,7 @@ void tst_QQuickMouseArea::dragThreshold() QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("dragging.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("dragging.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); @@ -540,7 +515,7 @@ void tst_QQuickMouseArea::invalidDrag() QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("dragging.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("dragging.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QVERIFY(window.rootObject() != nullptr); @@ -589,7 +564,7 @@ void tst_QQuickMouseArea::cancelDragging() { QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("dragging.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("dragging.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); @@ -644,7 +619,7 @@ void tst_QQuickMouseArea::availableDistanceLessThanDragThreshold() { QQuickView view; QByteArray errorMessage; - QVERIFY2(initView(view, testFileUrl("availableDistanceLessThanDragThreshold.qml"), true, &errorMessage), + QVERIFY2(QQuickTest::initView(view, testFileUrl("availableDistanceLessThanDragThreshold.qml"), true, &errorMessage), errorMessage.constData()); view.show(); view.requestActivate(); @@ -672,7 +647,7 @@ void tst_QQuickMouseArea::setDragOnPressed() { QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("setDragOnPressed.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("setDragOnPressed.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QVERIFY(window.rootObject() != nullptr); @@ -716,7 +691,7 @@ void tst_QQuickMouseArea::updateMouseAreaPosOnClick() { QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("updateMousePosOnClick.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("updateMousePosOnClick.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QVERIFY(window.rootObject() != nullptr); @@ -744,7 +719,7 @@ void tst_QQuickMouseArea::updateMouseAreaPosOnResize() { QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("updateMousePosOnResize.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("updateMousePosOnResize.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QVERIFY(window.rootObject() != nullptr); @@ -780,7 +755,7 @@ void tst_QQuickMouseArea::noOnClickedWithPressAndHold() // We handle onPressAndHold, therefore no onClicked QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("clickandhold.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("clickandhold.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QVERIFY(window.rootObject() != nullptr); @@ -812,7 +787,7 @@ void tst_QQuickMouseArea::noOnClickedWithPressAndHold() // We do not handle onPressAndHold, therefore we get onClicked QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("noclickandhold.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("noclickandhold.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QVERIFY(window.rootObject() != nullptr); @@ -835,7 +810,7 @@ void tst_QQuickMouseArea::onMousePressRejected() { QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("rejectEvent.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("rejectEvent.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QVERIFY(window.rootObject() != nullptr); @@ -882,7 +857,7 @@ void tst_QQuickMouseArea::pressedCanceledOnWindowDeactivate() QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("pressedCanceled.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("pressedCanceled.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QVERIFY(window.rootObject() != nullptr); @@ -955,7 +930,7 @@ void tst_QQuickMouseArea::doubleClick() QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("doubleclick.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("doubleclick.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QVERIFY(window.rootObject() != nullptr); @@ -992,7 +967,7 @@ void tst_QQuickMouseArea::clickTwice() QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("clicktwice.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("clicktwice.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QVERIFY(window.rootObject() != nullptr); @@ -1028,7 +1003,7 @@ void tst_QQuickMouseArea::invalidClick() QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("doubleclick.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("doubleclick.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QVERIFY(window.rootObject() != nullptr); @@ -1061,7 +1036,7 @@ void tst_QQuickMouseArea::pressedOrdering() { QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("pressedOrdering.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("pressedOrdering.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QVERIFY(window.rootObject() != nullptr); @@ -1087,7 +1062,7 @@ void tst_QQuickMouseArea::preventStealing() { QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("preventstealing.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("preventstealing.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QVERIFY(window.rootObject() != nullptr); @@ -1161,7 +1136,7 @@ void tst_QQuickMouseArea::clickThrough() //With no handlers defined click, doubleClick and PressAndHold should propagate to those with handlers QScopedPointer window(new QQuickView); QByteArray errorMessage; - QVERIFY2(initView(*window.data(), testFileUrl("clickThrough.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(*window.data(), testFileUrl("clickThrough.qml"), true, &errorMessage), errorMessage.constData()); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window.data())); QVERIFY(window->rootObject() != nullptr); @@ -1195,7 +1170,7 @@ void tst_QQuickMouseArea::clickThrough() window.reset(new QQuickView); //With handlers defined click, doubleClick and PressAndHold should propagate only when explicitly ignored - QVERIFY2(initView(*window.data(), testFileUrl("clickThrough2.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(*window.data(), testFileUrl("clickThrough2.qml"), true, &errorMessage), errorMessage.constData()); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window.data())); QVERIFY(window->rootObject() != nullptr); @@ -1269,7 +1244,7 @@ void tst_QQuickMouseArea::clickThrough() window.reset(new QQuickView); //QTBUG-34368 - Shouldn't propagate to disabled mouse areas - QVERIFY2(initView(*window.data(), testFileUrl("qtbug34368.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(*window.data(), testFileUrl("qtbug34368.qml"), true, &errorMessage), errorMessage.constData()); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window.data())); QVERIFY(window->rootObject() != nullptr); @@ -1291,7 +1266,7 @@ void tst_QQuickMouseArea::clickThrough() window.reset(new QQuickView); //QTBUG-49100 - QVERIFY2(initView(*window.data(), testFileUrl("qtbug49100.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(*window.data(), testFileUrl("qtbug49100.qml"), true, &errorMessage), errorMessage.constData()); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window.data())); QVERIFY(window->rootObject() != nullptr); @@ -1306,7 +1281,7 @@ void tst_QQuickMouseArea::hoverPosition() { QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("hoverPosition.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("hoverPosition.qml"), true, &errorMessage), errorMessage.constData()); QQuickItem *root = window.rootObject(); QVERIFY(root != nullptr); @@ -1325,7 +1300,7 @@ void tst_QQuickMouseArea::hoverPropagation() //QTBUG-18175, to behave like GV did. QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("hoverPropagation.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("hoverPropagation.qml"), true, &errorMessage), errorMessage.constData()); QQuickItem *root = window.rootObject(); QVERIFY(root != nullptr); @@ -1352,7 +1327,7 @@ void tst_QQuickMouseArea::hoverVisible() QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("hoverVisible.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("hoverVisible.qml"), true, &errorMessage), errorMessage.constData()); QQuickItem *root = window.rootObject(); QVERIFY(root != nullptr); @@ -1380,7 +1355,7 @@ void tst_QQuickMouseArea::hoverAfterPress() { QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("hoverAfterPress.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("hoverAfterPress.qml"), true, &errorMessage), errorMessage.constData()); QQuickItem *root = window.rootObject(); QVERIFY(root != nullptr); @@ -1406,7 +1381,7 @@ void tst_QQuickMouseArea::subtreeHoverEnabled() { QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("qtbug54019.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("qtbug54019.qml"), true, &errorMessage), errorMessage.constData()); QQuickItem *root = window.rootObject(); QVERIFY(root != nullptr); @@ -1426,7 +1401,7 @@ void tst_QQuickMouseArea::disableAfterPress() { QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("dragging.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("dragging.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QVERIFY(window.rootObject() != nullptr); @@ -1529,7 +1504,7 @@ void tst_QQuickMouseArea::onWheel() { QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("wheel.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("wheel.qml"), true, &errorMessage), errorMessage.constData()); QQuickItem *root = window.rootObject(); QVERIFY(root != nullptr); @@ -1573,7 +1548,7 @@ void tst_QQuickMouseArea::transformedMouseArea() QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("transformedMouseArea.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("transformedMouseArea.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QVERIFY(window.rootObject() != nullptr); @@ -1681,7 +1656,7 @@ void tst_QQuickMouseArea::pressedMultipleButtons() QQuickView view; QByteArray errorMessage; - QVERIFY2(initView(view, testFileUrl("simple.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(view, testFileUrl("simple.qml"), true, &errorMessage), errorMessage.constData()); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); QVERIFY(view.rootObject() != nullptr); @@ -1712,7 +1687,7 @@ void tst_QQuickMouseArea::changeAxis() { QQuickView view; QByteArray errorMessage; - QVERIFY2(initView(view, testFileUrl("changeAxis.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(view, testFileUrl("changeAxis.qml"), true, &errorMessage), errorMessage.constData()); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); QTRY_VERIFY(view.rootObject() != nullptr); @@ -1805,7 +1780,7 @@ void tst_QQuickMouseArea::moveAndReleaseWithoutPress() { QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("moveAndReleaseWithoutPress.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("moveAndReleaseWithoutPress.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); @@ -1845,7 +1820,7 @@ void tst_QQuickMouseArea::nestedStopAtBounds() QQuickView view; QByteArray errorMessage; - QVERIFY2(initView(view, testFileUrl("nestedStopAtBounds.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(view, testFileUrl("nestedStopAtBounds.qml"), true, &errorMessage), errorMessage.constData()); view.show(); view.requestActivate(); QVERIFY(QTest::qWaitForWindowExposed(&view)); @@ -1898,7 +1873,7 @@ void tst_QQuickMouseArea::nestedFlickableStopAtBounds() { QQuickView view; QByteArray errorMessage; - QVERIFY2(initView(view, testFileUrl("nestedFlickableStopAtBounds.qml"), false, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(view, testFileUrl("nestedFlickableStopAtBounds.qml"), false, &errorMessage), errorMessage.constData()); view.show(); view.requestActivate(); QVERIFY(QTest::qWaitForWindowExposed(&view)); @@ -2000,7 +1975,7 @@ void tst_QQuickMouseArea::containsPress() QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("containsPress.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("containsPress.qml"), true, &errorMessage), errorMessage.constData()); window.show(); window.requestActivate(); QVERIFY(QTest::qWaitForWindowExposed(&window)); @@ -2053,7 +2028,7 @@ void tst_QQuickMouseArea::ignoreBySource() { QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("ignoreBySource.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("ignoreBySource.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QVERIFY(window.rootObject()); @@ -2194,7 +2169,7 @@ void tst_QQuickMouseArea::pressAndHold() QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("pressAndHold.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("pressAndHold.qml"), true, &errorMessage), errorMessage.constData()); window.show(); window.requestActivate(); QVERIFY(QTest::qWaitForWindowExposed(&window)); @@ -2239,7 +2214,7 @@ void tst_QQuickMouseArea::pressOneAndTapAnother() QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("twoMouseAreas.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("twoMouseAreas.qml"), true, &errorMessage), errorMessage.constData()); window.show(); window.requestActivate(); QVERIFY(QTest::qWaitForWindowExposed(&window)); @@ -2292,7 +2267,7 @@ void tst_QQuickMouseArea::mask() { QQuickView window; QByteArray errorMessage; - QVERIFY2(initView(window, testFileUrl("mask.qml"), true, &errorMessage), errorMessage.constData()); + QVERIFY2(QQuickTest::initView(window, testFileUrl("mask.qml"), true, &errorMessage), errorMessage.constData()); window.show(); window.requestActivate(); QVERIFY(QTest::qWaitForWindowExposed(&window)); diff --git a/tests/auto/quick/shared/viewtestutil.cpp b/tests/auto/quick/shared/viewtestutil.cpp index 3bfa23173e..12d54c4bbe 100644 --- a/tests/auto/quick/shared/viewtestutil.cpp +++ b/tests/auto/quick/shared/viewtestutil.cpp @@ -451,3 +451,30 @@ namespace QQuickTouchUtils { } } + +namespace QQuickTest { + // Initialize view, set Url, center in available geometry, move mouse away if desired + bool initView(QQuickView &v, const QUrl &url, bool moveMouseOut, QByteArray *errorMessage) + { + v.setBaseSize(QSize(240,320)); + v.setSource(url); + while (v.status() == QQuickView::Loading) + QTest::qWait(10); + if (v.status() != QQuickView::Ready) { + foreach (const QQmlError &e, v.errors()) + errorMessage->append(e.toString().toLocal8Bit() + '\n'); + return false; + } + const QRect screenGeometry = v.screen()->availableGeometry(); + const QSize size = v.size(); + const QPoint offset = QPoint(size.width() / 2, size.height() / 2); + v.setFramePosition(screenGeometry.center() - offset); + #if QT_CONFIG(cursor) // Get the cursor out of the way. + if (moveMouseOut) + QCursor::setPos(v.geometry().topRight() + QPoint(100, 100)); + #else + Q_UNUSED(moveMouseOut) + #endif + return true; + } +} diff --git a/tests/auto/quick/shared/viewtestutil.h b/tests/auto/quick/shared/viewtestutil.h index 04e1771ef8..3b57a29777 100644 --- a/tests/auto/quick/shared/viewtestutil.h +++ b/tests/auto/quick/shared/viewtestutil.h @@ -185,6 +185,10 @@ namespace QQuickTouchUtils { void flush(QQuickWindow *window); } +namespace QQuickTest { + bool initView(QQuickView &v, const QUrl &url, bool moveMouseOut, QByteArray *errorMessage); +} + Q_DECLARE_METATYPE(QQuickViewTestUtil::QaimModel*) Q_DECLARE_METATYPE(QQuickViewTestUtil::ListChange) Q_DECLARE_METATYPE(QList) -- cgit v1.2.3 From eeedd26f35369a61d66283b8e2994a6e87c314fe Mon Sep 17 00:00:00 2001 From: Eirik Aavitsland Date: Tue, 12 Feb 2019 09:33:03 +0100 Subject: Fix background color of some QML lancelot test scenes Avoid red since that is also used for marking visual diffs in the lancelot results. Replace with a great color. Change-Id: I1da4d1b6dc3cacbd3b5b2574720fcd3176f49a52 Reviewed-by: Paul Olav Tvete --- .../data/borderimages/borderimage_borders_exceed_height.qml | 2 +- .../data/borderimages/borderimage_borders_exceed_width.qml | 2 +- .../data/borderimages/borderimage_rotated_smoothed.qml | 2 +- .../data/borderimages/borderimage_rotated_unsmoothed.qml | 2 +- .../data/borderimages/borderimage_rotated_unsmoothed_border_overlap.qml | 2 +- .../borderimages/borderimage_rotated_unsmoothed_negative_borders.qml | 2 +- .../data/borderimages/borderimage_rotated_unsmoothed_no_borders.qml | 2 +- .../data/borderimages/borderimage_rotated_unsmoothed_no_bottom.qml | 2 +- .../data/borderimages/borderimage_rotated_unsmoothed_no_center.qml | 2 +- .../data/borderimages/borderimage_rotated_unsmoothed_no_left.qml | 2 +- .../data/borderimages/borderimage_rotated_unsmoothed_no_right.qml | 2 +- .../data/borderimages/borderimage_rotated_unsmoothed_no_right_left.qml | 2 +- .../data/borderimages/borderimage_rotated_unsmoothed_no_top.qml | 2 +- .../data/borderimages/borderimage_rotated_unsmoothed_no_top_bottom.qml | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_borders_exceed_height.qml b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_borders_exceed_height.qml index c4321d25bb..1e2885baed 100644 --- a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_borders_exceed_height.qml +++ b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_borders_exceed_height.qml @@ -4,7 +4,7 @@ Rectangle { width: 320 height: 480 - color: "red" + color: "#C0FEFE" Item { x: 80 diff --git a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_borders_exceed_width.qml b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_borders_exceed_width.qml index 328ff40008..437f10dd97 100644 --- a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_borders_exceed_width.qml +++ b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_borders_exceed_width.qml @@ -4,7 +4,7 @@ Rectangle { width: 320 height: 480 - color: "red" + color: "#C0FEFE" Item { x: 80 diff --git a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_smoothed.qml b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_smoothed.qml index 804567cf19..12c600c4af 100644 --- a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_smoothed.qml +++ b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_smoothed.qml @@ -4,7 +4,7 @@ Rectangle { width: 320 height: 480 - color: "red" + color: "#C0FEFE" Item { x: 80 diff --git a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed.qml b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed.qml index b10554ad66..c3d4e5b018 100644 --- a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed.qml +++ b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed.qml @@ -4,7 +4,7 @@ Rectangle { width: 320 height: 480 - color: "red" + color: "#C0FEFE" Item { x: 80 diff --git a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_border_overlap.qml b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_border_overlap.qml index 73cc53ed2b..5d645902ec 100644 --- a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_border_overlap.qml +++ b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_border_overlap.qml @@ -4,7 +4,7 @@ Rectangle { width: 320 height: 480 - color: "red" + color: "#C0FEFE" Item { x: 80 diff --git a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_negative_borders.qml b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_negative_borders.qml index 8356f02614..153b77d642 100644 --- a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_negative_borders.qml +++ b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_negative_borders.qml @@ -4,7 +4,7 @@ Rectangle { width: 320 height: 480 - color: "red" + color: "#C0FEFE" Item { x: 80 diff --git a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_borders.qml b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_borders.qml index 9213589648..687a54ee80 100644 --- a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_borders.qml +++ b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_borders.qml @@ -4,7 +4,7 @@ Rectangle { width: 320 height: 480 - color: "red" + color: "#C0FEFE" Item { x: 80 diff --git a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_bottom.qml b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_bottom.qml index 615abdee20..70bd5c0f14 100644 --- a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_bottom.qml +++ b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_bottom.qml @@ -4,7 +4,7 @@ Rectangle { width: 320 height: 480 - color: "red" + color: "#C0FEFE" Item { x: 80 diff --git a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_center.qml b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_center.qml index 58ed4d44dc..98d2b867b8 100644 --- a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_center.qml +++ b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_center.qml @@ -4,7 +4,7 @@ Rectangle { width: 320 height: 480 - color: "red" + color: "#C0FEFE" Item { x: 80 diff --git a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_left.qml b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_left.qml index 7e0045bf24..64cd9d30cc 100644 --- a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_left.qml +++ b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_left.qml @@ -4,7 +4,7 @@ Rectangle { width: 320 height: 480 - color: "red" + color: "#C0FEFE" Item { x: 80 diff --git a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_right.qml b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_right.qml index 04c2d021f0..6d63475cfd 100644 --- a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_right.qml +++ b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_right.qml @@ -4,7 +4,7 @@ Rectangle { width: 320 height: 480 - color: "red" + color: "#C0FEFE" Item { x: 80 diff --git a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_right_left.qml b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_right_left.qml index 5210bab321..6d2193633a 100644 --- a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_right_left.qml +++ b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_right_left.qml @@ -4,7 +4,7 @@ Rectangle { width: 320 height: 480 - color: "red" + color: "#C0FEFE" Item { x: 80 diff --git a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_top.qml b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_top.qml index 2e89496c92..c3c8dfb7f4 100644 --- a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_top.qml +++ b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_top.qml @@ -4,7 +4,7 @@ Rectangle { width: 320 height: 480 - color: "red" + color: "#C0FEFE" Item { x: 80 diff --git a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_top_bottom.qml b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_top_bottom.qml index 4388601f13..a32827cfc3 100644 --- a/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_top_bottom.qml +++ b/tests/manual/scenegraph_lancelot/data/borderimages/borderimage_rotated_unsmoothed_no_top_bottom.qml @@ -4,7 +4,7 @@ Rectangle { width: 320 height: 480 - color: "red" + color: "#C0FEFE" Item { x: 80 -- cgit v1.2.3 From 91a71bce9c633934540e9f06fb081e3b89259ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Tue, 12 Feb 2019 14:19:19 +0100 Subject: Defer QML testing until event loop has started MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Executing the event loop means we get various things set up properly, such as runloop auto-release pools on macOS. If not, the whole test suite will be run as a result of the setWindowShown call. Change-Id: Ie217d803208134c5be7db0ee04fbfab86702b521 Reviewed-by: Simon Hausmann Reviewed-by: Tor Arne Vestbø --- src/qmltest/quicktest.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/qmltest/quicktest.cpp b/src/qmltest/quicktest.cpp index 8fe9da5e09..1a5cad5d5a 100644 --- a/src/qmltest/quicktest.cpp +++ b/src/qmltest/quicktest.cpp @@ -57,6 +57,7 @@ #include #include #include +#include #include #include #include @@ -577,7 +578,10 @@ int quick_test_main_with_setup(int argc, char **argv, const char *name, const ch << "Test '" << QDir::toNativeSeparators(path) << "' window not active after requestActivate()."; } if (view.isExposed()) { - QTestRootObject::instance()->setWindowShown(true); + // Defer property update until event loop has started + QTimer::singleShot(0, []() { + QTestRootObject::instance()->setWindowShown(true); + }); } else { qWarning().nospace() << "Test '" << QDir::toNativeSeparators(path) << "' window was never exposed! " -- cgit v1.2.3 From 01f9c623ed2dc1645ac022d69062f720e3b50132 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 13 Feb 2019 09:17:10 +0100 Subject: Don't optimize global lookups if fast QML lookups are disabled If fast QML lookups are disabled, we generally want to look up by string. If the name then happens to be a member of the global JavaScript object, we still don't want to directly access that, as the name could have been overridden in a deeper context. Fixes: QTBUG-73750 Change-Id: Id16110969123d91501064ba46bfad4c2a39e4650 Reviewed-by: Simon Hausmann --- src/qml/compiler/qqmlirbuilder_p.h | 1 + src/qml/compiler/qv4codegen.cpp | 2 +- src/qml/compiler/qv4codegen_p.h | 4 +++- .../qqmlconnections/data/override-proxy-type.qml | 13 +++++++++++ .../qml/qqmlconnections/tst_qqmlconnections.cpp | 25 ++++++++++++++++++++++ 5 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 tests/auto/qml/qqmlconnections/data/override-proxy-type.qml diff --git a/src/qml/compiler/qqmlirbuilder_p.h b/src/qml/compiler/qqmlirbuilder_p.h index 3dde929cc4..1a3ca4163e 100644 --- a/src/qml/compiler/qqmlirbuilder_p.h +++ b/src/qml/compiler/qqmlirbuilder_p.h @@ -622,6 +622,7 @@ struct Q_QML_PRIVATE_EXPORT JSCodeGen : public QV4::Compiler::Codegen protected: void beginFunctionBodyHook() override; + bool canAccelerateGlobalLookups() const override { return !_disableAcceleratedLookups; } Reference fallbackNameLookup(const QString &name) override; private: diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index fb3c66b123..04da41430c 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -2404,7 +2404,7 @@ Codegen::Reference Codegen::referenceForName(const QString &name, bool isLhs, co Reference r = Reference::fromName(this, name); r.global = useFastLookups && (resolved.type == Context::ResolvedName::Global); - if (!r.global && m_globalNames.contains(name)) + if (!r.global && canAccelerateGlobalLookups() && m_globalNames.contains(name)) r.global = true; return r; } diff --git a/src/qml/compiler/qv4codegen_p.h b/src/qml/compiler/qv4codegen_p.h index 3f96afc7c2..4d7001fe64 100644 --- a/src/qml/compiler/qv4codegen_p.h +++ b/src/qml/compiler/qv4codegen_p.h @@ -561,8 +561,10 @@ protected: Reference referenceForPropertyName(const Codegen::Reference &object, AST::PropertyName *name); - // Hook provided to implement QML lookup semantics + // Hooks provided to implement QML lookup semantics + virtual bool canAccelerateGlobalLookups() const { return true; } virtual Reference fallbackNameLookup(const QString &name); + virtual void beginFunctionBodyHook() {} void emitReturn(const Reference &expr); diff --git a/tests/auto/qml/qqmlconnections/data/override-proxy-type.qml b/tests/auto/qml/qqmlconnections/data/override-proxy-type.qml new file mode 100644 index 0000000000..80e459966b --- /dev/null +++ b/tests/auto/qml/qqmlconnections/data/override-proxy-type.qml @@ -0,0 +1,13 @@ +import QtQml 2.12 +import test.proxy 1.0 + +Proxy { + property int testEnum: 0; + id: proxy + property Connections connections: Connections { + target: proxy + onSomeSignal: testEnum = Proxy.EnumValue; + } + + Component.onCompleted: someSignal() +} diff --git a/tests/auto/qml/qqmlconnections/tst_qqmlconnections.cpp b/tests/auto/qml/qqmlconnections/tst_qqmlconnections.cpp index 8ef00f8080..dc29363fcf 100644 --- a/tests/auto/qml/qqmlconnections/tst_qqmlconnections.cpp +++ b/tests/auto/qml/qqmlconnections/tst_qqmlconnections.cpp @@ -55,6 +55,7 @@ private slots: void disabledAtStart(); void clearImplicitTarget(); void onWithoutASignal(); + void noAcceleratedGlobalLookup(); private: QQmlEngine engine; @@ -407,6 +408,30 @@ void tst_qqmlconnections::onWithoutASignal() QVERIFY(item == nullptr); // should parse error, and not give us an item (or crash). } +class Proxy : public QObject +{ + Q_OBJECT +public: + enum MyEnum { EnumValue = 20, AnotherEnumValue }; + Q_ENUM(MyEnum) + +signals: + void someSignal(); +}; + +void tst_qqmlconnections::noAcceleratedGlobalLookup() +{ + qRegisterMetaType(); + qmlRegisterType("test.proxy", 1, 0, "Proxy"); + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("override-proxy-type.qml")); + QVERIFY(c.isReady()); + QScopedPointer object(c.create()); + const QVariant val = object->property("testEnum"); + QCOMPARE(val.type(), QMetaType::Int); + QCOMPARE(val.toInt(), int(Proxy::EnumValue)); +} + QTEST_MAIN(tst_qqmlconnections) #include "tst_qqmlconnections.moc" -- cgit v1.2.3 From 48402d5c4c97bae5762668aaaf9670c644120feb Mon Sep 17 00:00:00 2001 From: Nicolas Fella Date: Tue, 29 Jan 2019 18:33:21 +0100 Subject: Support loading images from content:/ URLs on Android Patch 251038 adds support for content:/ URLs on Android, but to be able to load images from them this patch is needed. It copies the existing code for assets:/ URls Change-Id: Ic086da6396576553a9635dbbbb1fa12d45bd3f52 Reviewed-by: BogDan Vatra --- src/qml/qml/qqmlfile.cpp | 17 +++++++++++++++-- src/qml/qml/qqmltypeloader.cpp | 12 +++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/qml/qml/qqmlfile.cpp b/src/qml/qml/qqmlfile.cpp index 99031e1e74..465a342129 100644 --- a/src/qml/qml/qqmlfile.cpp +++ b/src/qml/qml/qqmlfile.cpp @@ -64,6 +64,7 @@ static char file_string[] = "file"; #if defined(Q_OS_ANDROID) static char assets_string[] = "assets"; +static char content_string[] = "content"; #endif class QQmlFilePrivate; @@ -452,6 +453,8 @@ bool QQmlFile::isSynchronous(const QUrl &url) #if defined(Q_OS_ANDROID) } else if (scheme.length() == 6 && 0 == scheme.compare(QLatin1String(assets_string), Qt::CaseInsensitive)) { return true; + } else if (scheme.length() == 7 && 0 == scheme.compare(QLatin1String(content_string), Qt::CaseInsensitive)) { + return true; #endif } else { @@ -492,7 +495,10 @@ bool QQmlFile::isSynchronous(const QString &url) return url.length() >= 8 /* assets:/ */ && url.startsWith(QLatin1String(assets_string), Qt::CaseInsensitive) && url[6] == QLatin1Char(':') && url[7] == QLatin1Char('/'); - + } else if (f == QLatin1Char('c') || f == QLatin1Char('C')) { + return url.length() >= 9 /* content:/ */ && + url.startsWith(QLatin1String(content_string), Qt::CaseInsensitive) && + url[7] == QLatin1Char(':') && url[8] == QLatin1Char('/'); } #endif @@ -556,7 +562,10 @@ bool QQmlFile::isLocalFile(const QString &url) return url.length() >= 8 /* assets:/ */ && url.startsWith(QLatin1String(assets_string), Qt::CaseInsensitive) && url[6] == QLatin1Char(':') && url[7] == QLatin1Char('/'); - + } else if (f == QLatin1Char('c') || f == QLatin1Char('C')) { + return url.length() >= 9 /* content:/ */ && + url.startsWith(QLatin1String(content_string), Qt::CaseInsensitive) && + url[7] == QLatin1Char(':') && url[8] == QLatin1Char('/'); } #endif @@ -580,6 +589,8 @@ QString QQmlFile::urlToLocalFileOrQrc(const QUrl& url) if (url.authority().isEmpty()) return url.toString(); return QString(); + } else if (url.scheme().compare(QLatin1String("content"), Qt::CaseInsensitive) == 0) { + return url.toString(); } #endif @@ -618,6 +629,8 @@ QString QQmlFile::urlToLocalFileOrQrc(const QString& url) #if defined(Q_OS_ANDROID) else if (url.startsWith(QLatin1String("assets:"), Qt::CaseInsensitive)) { return url; + } else if (url.startsWith(QLatin1String("content:"), Qt::CaseInsensitive)) { + return url; } #endif diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp index fc48957bcb..b508a66f84 100644 --- a/src/qml/qml/qqmltypeloader.cpp +++ b/src/qml/qml/qqmltypeloader.cpp @@ -1821,6 +1821,11 @@ QString QQmlTypeLoader::absoluteFilePath(const QString &path) // assets resource url QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path)); return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString(); + } else if (path.count() > 8 && path.at(7) == QLatin1Char(':') && path.at(8) == QLatin1Char('/') && + path.startsWith(QLatin1String("content"), Qt::CaseInsensitive)) { + // content url + QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path)); + return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString(); } #endif @@ -1878,6 +1883,11 @@ bool QQmlTypeLoader::fileExists(const QString &path, const QString &file) // assets resource url QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path + file)); return fileInfo.isFile(); + } else if (path.count() > 8 && path.at(7) == QLatin1Char(':') && path.at(8) == QLatin1Char('/') && + path.startsWith(QLatin1String("content"), Qt::CaseInsensitive)) { + // content url + QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path + file)); + return fileInfo.isFile(); } #endif @@ -1913,7 +1923,7 @@ bool QQmlTypeLoader::directoryExists(const QString &path) bool isResource = path.at(0) == QLatin1Char(':'); #if defined(Q_OS_ANDROID) - isResource = isResource || path.startsWith(QLatin1String("assets:/")); + isResource = isResource || path.startsWith(QLatin1String("assets:/")) || path.startsWith(QLatin1String("content:/")); #endif if (isResource) { -- cgit v1.2.3 From 57604562db870f7b05d4349761d73e054dbe04c6 Mon Sep 17 00:00:00 2001 From: Christian Ehrlicher Date: Sun, 10 Feb 2019 12:49:46 +0100 Subject: QtDeclarative: replace deprecated functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the deprecated functions with it successors: - QFontMetrics::width() -> horizontalAdvance() - QLayout::setMargin() -> setContentsMargins() Change-Id: I2a2557cdb2eaec40e2c9955a0082372c582ec6b8 Reviewed-by: Shawn Rutledge Reviewed-by: Jan Arve Sæther --- src/quick/items/qquicktext.cpp | 2 +- src/quick/items/qquicktextnodeengine.cpp | 4 ++-- src/quick/util/qquickfontmetrics.cpp | 2 +- src/quick/util/qquickstyledtext.cpp | 2 +- src/quick/util/qquicktextmetrics.cpp | 2 +- tests/auto/quick/qquickfontmetrics/tst_quickfontmetrics.cpp | 2 +- tools/qmleasing/mainwindow.cpp | 2 +- tools/qmleasing/segmentproperties.cpp | 2 +- tools/qmleasing/splineeditor.cpp | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp index dd7fbccff5..9e447d40ac 100644 --- a/src/quick/items/qquicktext.cpp +++ b/src/quick/items/qquicktext.cpp @@ -643,7 +643,7 @@ QString QQuickTextPrivate::elidedText(qreal lineWidth, const QTextLine &line, QT // Appending the elide character may push the line over the maximum width // in which case the elided text will need to be elided. QFontMetricsF metrics(layout.font()); - if (metrics.width(elideChar) + line.naturalTextWidth() >= lineWidth) + if (metrics.horizontalAdvance(elideChar) + line.naturalTextWidth() >= lineWidth) elideText = metrics.elidedText(elideText, Qt::TextElideMode(elideMode), lineWidth); } return elideText; diff --git a/src/quick/items/qquicktextnodeengine.cpp b/src/quick/items/qquicktextnodeengine.cpp index 792aa31a88..d84932b8d0 100644 --- a/src/quick/items/qquicktextnodeengine.cpp +++ b/src/quick/items/qquicktextnodeengine.cpp @@ -1007,8 +1007,8 @@ void QQuickTextNodeEngine::addTextBlock(QTextDocument *textDocument, const QText break; }; - QSizeF size(fontMetrics.width(listItemBullet), fontMetrics.height()); - qreal xoff = fontMetrics.width(QLatin1Char(' ')); + QSizeF size(fontMetrics.horizontalAdvance(listItemBullet), fontMetrics.height()); + qreal xoff = fontMetrics.horizontalAdvance(QLatin1Char(' ')); if (block.textDirection() == Qt::LeftToRight) xoff = -xoff - size.width(); setPosition(pos + QPointF(xoff, 0)); diff --git a/src/quick/util/qquickfontmetrics.cpp b/src/quick/util/qquickfontmetrics.cpp index f1278c366f..42b3038c48 100644 --- a/src/quick/util/qquickfontmetrics.cpp +++ b/src/quick/util/qquickfontmetrics.cpp @@ -287,7 +287,7 @@ qreal QQuickFontMetrics::lineWidth() const */ qreal QQuickFontMetrics::advanceWidth(const QString &text) const { - return m_metrics.width(text); + return m_metrics.horizontalAdvance(text); } /*! diff --git a/src/quick/util/qquickstyledtext.cpp b/src/quick/util/qquickstyledtext.cpp index 5e1aaf121e..7d545cdb2f 100644 --- a/src/quick/util/qquickstyledtext.cpp +++ b/src/quick/util/qquickstyledtext.cpp @@ -675,7 +675,7 @@ void QQuickStyledTextPrivate::parseImageAttributes(const QChar *&ch, const QStri { qreal imgWidth = 0.0; QFontMetricsF fm(layout.font()); - const qreal spaceWidth = fm.width(QChar::Nbsp); + const qreal spaceWidth = fm.horizontalAdvance(QChar::Nbsp); const bool trailingSpace = textOut.endsWith(space); if (!updateImagePositions) { diff --git a/src/quick/util/qquicktextmetrics.cpp b/src/quick/util/qquicktextmetrics.cpp index 81088b5cd6..959980b267 100644 --- a/src/quick/util/qquicktextmetrics.cpp +++ b/src/quick/util/qquicktextmetrics.cpp @@ -186,7 +186,7 @@ void QQuickTextMetrics::setElideWidth(qreal elideWidth) */ qreal QQuickTextMetrics::advanceWidth() const { - return m_metrics.width(m_text); + return m_metrics.horizontalAdvance(m_text); } /*! diff --git a/tests/auto/quick/qquickfontmetrics/tst_quickfontmetrics.cpp b/tests/auto/quick/qquickfontmetrics/tst_quickfontmetrics.cpp index 6e516f51e1..ef61c45225 100644 --- a/tests/auto/quick/qquickfontmetrics/tst_quickfontmetrics.cpp +++ b/tests/auto/quick/qquickfontmetrics/tst_quickfontmetrics.cpp @@ -127,7 +127,7 @@ void tst_QuickFontMetrics::functions() QFontMetricsF expected = QFontMetricsF(QFont()); QCOMPARE(metrics.elidedText(text, mode, width, flags), expected.elidedText(text, mode, width, flags)); - QCOMPARE(metrics.advanceWidth(text), expected.width(text)); + QCOMPARE(metrics.advanceWidth(text), expected.horizontalAdvance(text)); QCOMPARE(metrics.boundingRect(text), expected.boundingRect(text)); QCOMPARE(metrics.tightBoundingRect(text), expected.tightBoundingRect(text)); } diff --git a/tools/qmleasing/mainwindow.cpp b/tools/qmleasing/mainwindow.cpp index c1a87642a5..679b4c0b91 100644 --- a/tools/qmleasing/mainwindow.cpp +++ b/tools/qmleasing/mainwindow.cpp @@ -82,7 +82,7 @@ MainWindow::MainWindow(QWidget *parent) : splineEditor->setPreset(ui_properties.comboBox->currentText()); QVBoxLayout *groupBoxLayout = new QVBoxLayout(ui_properties.groupBox); - groupBoxLayout->setMargin(0); + groupBoxLayout->setContentsMargins(QMargins()); ui_properties.groupBox->setLayout(groupBoxLayout); groupBoxLayout->addWidget(splineEditor->pointListWidget()); diff --git a/tools/qmleasing/segmentproperties.cpp b/tools/qmleasing/segmentproperties.cpp index f37527f863..c61feef9a4 100644 --- a/tools/qmleasing/segmentproperties.cpp +++ b/tools/qmleasing/segmentproperties.cpp @@ -33,7 +33,7 @@ SegmentProperties::SegmentProperties(QWidget *parent) : QWidget(parent), m_splineEditor(nullptr), m_blockSignals(false) { QVBoxLayout *layout = new QVBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(QMargins()); layout->setSpacing(2); setLayout(layout); { diff --git a/tools/qmleasing/splineeditor.cpp b/tools/qmleasing/splineeditor.cpp index 2a6081903f..69850dc7a1 100644 --- a/tools/qmleasing/splineeditor.cpp +++ b/tools/qmleasing/splineeditor.cpp @@ -524,7 +524,7 @@ void SplineEditor::setupPointListWidget() m_pointListWidget->setWidget(new QWidget(m_pointListWidget)); QVBoxLayout *layout = new QVBoxLayout(m_pointListWidget->widget()); - layout->setMargin(0); + layout->setContentsMargins(QMargins()); layout->setSpacing(2); m_pointListWidget->widget()->setLayout(layout); -- cgit v1.2.3 From 1e3ed172f35abaa0e0af43ee22259bc3cd188ad8 Mon Sep 17 00:00:00 2001 From: Michal Policht Date: Wed, 6 Feb 2019 14:58:59 +0100 Subject: Connect quit() and exit() signals with queued connections Class QQmlApplicationEngine connects QQmlApplicationEngine::quit() signal to QCoreApplication::quit() and QQmlApplicationEngine::exit() signal to QCoreApplication::exit(), but it does so with AutoConnection. This causes in some circumstances problems, which are described in Qt documentation (see QCoreApplication::exit()). This change modifies type of connections to queued connections. [ChangeLog][QtQml][QQmlApplicationEngine] QQmlApplicationEngine connects quit() and exit() signals with queued connections to avoid problems with AutoConnection, when connecting to QCoreApplication slots. Task-number: QTBUG-73649 Change-Id: Ib27738b5af2f879efee8862b1ca01613a2e8dc4e Reviewed-by: Ulf Hermann --- src/qml/qml/qqmlapplicationengine.cpp | 6 ++- .../qqmlapplicationengine/testapp/delayedExit.qml | 11 ++++++ .../qqmlapplicationengine/testapp/delayedQuit.qml | 11 ++++++ .../testapp/immediateExit.qml | 8 ++++ .../testapp/immediateQuit.qml | 8 ++++ .../qml/qqmlapplicationengine/testapp/main.cpp | 2 +- .../qml/qqmlapplicationengine/testapp/main.qml | 11 ------ .../qml/qqmlapplicationengine/testapp/main.qrc | 5 ++- .../tst_qqmlapplicationengine.cpp | 44 ++++++++++++++++------ 9 files changed, 80 insertions(+), 26 deletions(-) create mode 100644 tests/auto/qml/qqmlapplicationengine/testapp/delayedExit.qml create mode 100644 tests/auto/qml/qqmlapplicationengine/testapp/delayedQuit.qml create mode 100644 tests/auto/qml/qqmlapplicationengine/testapp/immediateExit.qml create mode 100644 tests/auto/qml/qqmlapplicationengine/testapp/immediateQuit.qml delete mode 100644 tests/auto/qml/qqmlapplicationengine/testapp/main.qml diff --git a/src/qml/qml/qqmlapplicationengine.cpp b/src/qml/qml/qqmlapplicationengine.cpp index 9ac2100eab..c519429d48 100644 --- a/src/qml/qml/qqmlapplicationengine.cpp +++ b/src/qml/qml/qqmlapplicationengine.cpp @@ -70,8 +70,10 @@ void QQmlApplicationEnginePrivate::cleanUp() void QQmlApplicationEnginePrivate::init() { Q_Q(QQmlApplicationEngine); - q->connect(q, SIGNAL(quit()), QCoreApplication::instance(), SLOT(quit())); - q->connect(q, &QQmlApplicationEngine::exit, QCoreApplication::instance(), &QCoreApplication::exit); + q->connect(q, &QQmlApplicationEngine::quit, QCoreApplication::instance(), + &QCoreApplication::quit, Qt::QueuedConnection); + q->connect(q, &QQmlApplicationEngine::exit, QCoreApplication::instance(), + &QCoreApplication::exit, Qt::QueuedConnection); #if QT_CONFIG(translation) QTranslator* qtTranslator = new QTranslator; if (qtTranslator->load(QLocale(), QLatin1String("qt"), QLatin1String("_"), QLibraryInfo::location(QLibraryInfo::TranslationsPath))) diff --git a/tests/auto/qml/qqmlapplicationengine/testapp/delayedExit.qml b/tests/auto/qml/qqmlapplicationengine/testapp/delayedExit.qml new file mode 100644 index 0000000000..3d67c958bb --- /dev/null +++ b/tests/auto/qml/qqmlapplicationengine/testapp/delayedExit.qml @@ -0,0 +1,11 @@ +import QtQml 2.0 + +QtObject { + id: root + property Timer t: Timer { interval: 1; running: true; onTriggered: Qt.exit(0); } + property Connections c: Connections { + target: Qt.application + onAboutToQuit: console.log("End"); + } + Component.onCompleted: console.log("Start: " + Qt.application.arguments[1]); +} diff --git a/tests/auto/qml/qqmlapplicationengine/testapp/delayedQuit.qml b/tests/auto/qml/qqmlapplicationengine/testapp/delayedQuit.qml new file mode 100644 index 0000000000..c75485a7f7 --- /dev/null +++ b/tests/auto/qml/qqmlapplicationengine/testapp/delayedQuit.qml @@ -0,0 +1,11 @@ +import QtQml 2.0 + +QtObject { + id: root + property Timer t: Timer { interval: 1; running: true; onTriggered: Qt.quit(); } + property Connections c: Connections { + target: Qt.application + onAboutToQuit: console.log("End"); + } + Component.onCompleted: console.log("Start: " + Qt.application.arguments[1]); +} diff --git a/tests/auto/qml/qqmlapplicationengine/testapp/immediateExit.qml b/tests/auto/qml/qqmlapplicationengine/testapp/immediateExit.qml new file mode 100644 index 0000000000..46634f3f51 --- /dev/null +++ b/tests/auto/qml/qqmlapplicationengine/testapp/immediateExit.qml @@ -0,0 +1,8 @@ +import QtQml 2.0 + +QtObject { + Component.onCompleted: { + console.log("End: " + Qt.application.arguments[1]); + Qt.exit(0) + } +} diff --git a/tests/auto/qml/qqmlapplicationengine/testapp/immediateQuit.qml b/tests/auto/qml/qqmlapplicationengine/testapp/immediateQuit.qml new file mode 100644 index 0000000000..1da9d1201a --- /dev/null +++ b/tests/auto/qml/qqmlapplicationengine/testapp/immediateQuit.qml @@ -0,0 +1,8 @@ +import QtQml 2.0 + +QtObject { + Component.onCompleted: { + console.log("End: " + Qt.application.arguments[1]); + Qt.quit() + } +} diff --git a/tests/auto/qml/qqmlapplicationengine/testapp/main.cpp b/tests/auto/qml/qqmlapplicationengine/testapp/main.cpp index a57889fe86..be0d98a2df 100644 --- a/tests/auto/qml/qqmlapplicationengine/testapp/main.cpp +++ b/tests/auto/qml/qqmlapplicationengine/testapp/main.cpp @@ -32,6 +32,6 @@ int main (int argc, char *argv[]) { QCoreApplication app(argc, argv); - QQmlApplicationEngine e(QUrl("qrc:///main.qml")); + QQmlApplicationEngine e(QUrl(QString("qrc:///") + argv[1])); return app.exec(); } diff --git a/tests/auto/qml/qqmlapplicationengine/testapp/main.qml b/tests/auto/qml/qqmlapplicationengine/testapp/main.qml deleted file mode 100644 index c75485a7f7..0000000000 --- a/tests/auto/qml/qqmlapplicationengine/testapp/main.qml +++ /dev/null @@ -1,11 +0,0 @@ -import QtQml 2.0 - -QtObject { - id: root - property Timer t: Timer { interval: 1; running: true; onTriggered: Qt.quit(); } - property Connections c: Connections { - target: Qt.application - onAboutToQuit: console.log("End"); - } - Component.onCompleted: console.log("Start: " + Qt.application.arguments[1]); -} diff --git a/tests/auto/qml/qqmlapplicationengine/testapp/main.qrc b/tests/auto/qml/qqmlapplicationengine/testapp/main.qrc index 5f6483ac33..82b695bbd8 100644 --- a/tests/auto/qml/qqmlapplicationengine/testapp/main.qrc +++ b/tests/auto/qml/qqmlapplicationengine/testapp/main.qrc @@ -1,5 +1,8 @@ - main.qml + immediateQuit.qml + immediateExit.qml + delayedQuit.qml + delayedExit.qml diff --git a/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp b/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp index daeb9b5455..ce654dc45e 100644 --- a/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp +++ b/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp @@ -46,6 +46,7 @@ private slots: void initTestCase(); void basicLoading(); void testNonResolvedPath(); + void application_data(); void application(); void applicationProperties(); void removeObjectsWhenDestroyed(); @@ -111,35 +112,56 @@ void tst_qqmlapplicationengine::testNonResolvedPath() } } +void tst_qqmlapplicationengine::application_data() +{ + QTest::addColumn("qmlFile"); + QTest::addColumn("expectedStdErr"); + + QTest::newRow("delayed quit") << QByteArray("delayedQuit.qml") + << QByteArray("qml: Start: delayedQuit.qml\nqml: End\n"); + QTest::newRow("delayed exit") << QByteArray("delayedExit.qml") + << QByteArray("qml: Start: delayedExit.qml\nqml: End\n"); + QTest::newRow("immediate quit") << QByteArray("immediateQuit.qml") + << QByteArray("qml: End: immediateQuit.qml\n"); + QTest::newRow("immediate exit") << QByteArray("immediateExit.qml") + << QByteArray("qml: End: immediateExit.qml\n"); +} + void tst_qqmlapplicationengine::application() { /* This test batches together some tests about running an external application written with QQmlApplicationEngine. The application tests the following functionality which is easier to do by watching a separate process: - -Loads relative paths from the working directory - -quits when quit is called - -emits aboutToQuit after quit is called - -has access to application command line arguments + - Loads relative paths from the working directory + - Quits when quit is called + - Exits when exit is called + - Emits aboutToQuit after quit is called + - Has access to application command line arguments Note that checking the output means that on builds with extra debugging, this might fail with a false positive. Also the testapp is automatically built and installed in shadow builds, so it does NOT use testData */ + + QFETCH(QByteArray, qmlFile); + QFETCH(QByteArray, expectedStdErr); + #if QT_CONFIG(process) QDir::setCurrent(buildDir); QProcess *testProcess = new QProcess(this); QStringList args; - args << QLatin1String("testData"); + args << qmlFile; // QML file passed as an argument is going to be run by testapp. testProcess->start(QLatin1String("testapp/testapp"), args); QVERIFY(testProcess->waitForFinished(5000)); QCOMPARE(testProcess->exitCode(), 0); - QByteArray test_stdout = testProcess->readAllStandardOutput(); - QByteArray test_stderr = testProcess->readAllStandardError(); - QByteArray test_stderr_target("qml: Start: testData\nqml: End\n"); + QByteArray testStdOut = testProcess->readAllStandardOutput(); + QByteArray testStdErr = testProcess->readAllStandardError(); #ifdef Q_OS_WIN - test_stderr_target.replace('\n', QByteArray("\r\n")); + expectedStdErr.replace('\n', QByteArray("\r\n")); #endif - QCOMPARE(test_stdout, QByteArray("")); - QVERIFY(QString(test_stderr).endsWith(QString(test_stderr_target))); + QCOMPARE(testStdOut, QByteArray("")); + QVERIFY2(QString(testStdErr).endsWith(QString(expectedStdErr)), + QByteArray("\nExpected ending:\n") + expectedStdErr + + QByteArray("\nActual output:\n") + testStdErr); delete testProcess; QDir::setCurrent(srcDir); #else // process -- cgit v1.2.3 From 68d0377736a541fa29d1aaba6b7aa4a4cf8781ef Mon Sep 17 00:00:00 2001 From: Jan Arve Saether Date: Wed, 13 Feb 2019 12:43:56 +0100 Subject: Make test more robust in case we have interleaved update events This could for instance happen if the window gets exposed under the cursor, and it sends a mouse move event that might interleave the press and release events, causing the eventCount variable to jump to 3. Change-Id: Icce59b2aa1a937a990baa83f503907633003e2bb Reviewed-by: Shawn Rutledge --- .../qquickpointerhandler/tst_qquickpointerhandler.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp b/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp index feb356a7d5..2b6482465c 100644 --- a/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp @@ -188,7 +188,10 @@ public: QQuickPointerHandler::handlePointerEventImpl(event); if (!enabled()) return; - ++eventCount; + if (event->isPressEvent()) + ++pressEventCount; + if (event->isReleaseEvent()) + ++releaseEventCount; EventItem *item = qmlobject_cast(target()); if (!item) { event->point(0)->setGrabberPointerHandler(this); @@ -218,7 +221,8 @@ public: static_cast(point->state()), stateChange, eventPos(point), point->scenePosition())); } - int eventCount = 0; + int pressEventCount = 0; + int releaseEventCount = 0; }; class tst_PointerHandlers : public QQmlDataTest @@ -646,9 +650,9 @@ void tst_PointerHandlers::handlerInWindow() QPoint p1(20, 20); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); - QTRY_COMPARE(handler->eventCount, 1); + QTRY_COMPARE(handler->pressEventCount, 1); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); - QTRY_COMPARE(handler->eventCount, 2); + QTRY_COMPARE(handler->releaseEventCount, 1); } void tst_PointerHandlers::dynamicCreationInWindow() @@ -670,9 +674,9 @@ void tst_PointerHandlers::dynamicCreationInWindow() QPoint p1(20, 20); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); - QTRY_COMPARE(handler->eventCount, 1); + QTRY_COMPARE(handler->pressEventCount, 1); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); - QTRY_COMPARE(handler->eventCount, 2); + QTRY_COMPARE(handler->releaseEventCount, 1); } QTEST_MAIN(tst_PointerHandlers) -- cgit v1.2.3 From 0148243a77a42de1ba6170a1da3977b3026c1927 Mon Sep 17 00:00:00 2001 From: Andy Shaw Date: Wed, 13 Feb 2019 14:08:49 +0100 Subject: Canvas: Handle switching between object and string based colors When switching between the two it should respect whatever the color is regardless of whether it is a color value or a string based color. This also accounts for "invalid" colors as this should set the stroke-style to be #000000 to be inline with the default stroke-style indicated in the specification. Change-Id: I00bee6c9a85787762271882838510b4187798ee0 Fixes: QTBUG-42155 Fixes: QTBUG-52959 Reviewed-by: Mitch Curtis --- src/quick/items/context2d/qquickcontext2d.cpp | 9 ++++-- .../qquickcanvasitem/data/tst_strokeStyle.qml | 35 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/quick/items/context2d/qquickcontext2d.cpp b/src/quick/items/context2d/qquickcontext2d.cpp index 66727e7845..57e697bdd1 100644 --- a/src/quick/items/context2d/qquickcontext2d.cpp +++ b/src/quick/items/context2d/qquickcontext2d.cpp @@ -1548,7 +1548,7 @@ QV4::ReturnedValue QQuickJSContext2D::method_set_strokeStyle(const QV4::Function if (value->as()) { QColor color = scope.engine->toVariant(value, qMetaTypeId()).value(); if (color.isValid()) { - r->d()->context()->state.fillStyle = color; + r->d()->context()->state.strokeStyle = color; r->d()->context()->buffer()->setStrokeStyle(color); r->d()->context()->m_strokeStyle.set(scope.engine, value); } else { @@ -1559,7 +1559,12 @@ QV4::ReturnedValue QQuickJSContext2D::method_set_strokeStyle(const QV4::Function r->d()->context()->m_strokeStyle.set(scope.engine, value); r->d()->context()->state.strokePatternRepeatX = style->d()->patternRepeatX; r->d()->context()->state.strokePatternRepeatY = style->d()->patternRepeatY; - + } else if (!style && r->d()->context()->state.strokeStyle != QBrush(QColor())) { + // If there is no style object, then ensure that the strokeStyle is at least + // QColor in case it was previously set + r->d()->context()->state.strokeStyle = QBrush(QColor()); + r->d()->context()->buffer()->setStrokeStyle(r->d()->context()->state.strokeStyle); + r->d()->context()->m_strokeStyle.set(scope.engine, value); } } } else if (value->isString()) { diff --git a/tests/auto/quick/qquickcanvasitem/data/tst_strokeStyle.qml b/tests/auto/quick/qquickcanvasitem/data/tst_strokeStyle.qml index 22803a19ce..a3f1ab0a9b 100644 --- a/tests/auto/quick/qquickcanvasitem/data/tst_strokeStyle.qml +++ b/tests/auto/quick/qquickcanvasitem/data/tst_strokeStyle.qml @@ -3,6 +3,8 @@ import QtQuick 2.0 CanvasTestCase { id:testCase name: "strokeStyle" + property color anotherColor: "#0000ff" + property color emptyColor function init_data() { return testData("2d"); } function test_default(row) { var canvas = createCanvasObject(row); @@ -46,4 +48,37 @@ CanvasTestCase { comparePixel(ctx,0,0,255,255,255,255); canvas.destroy() } + function test_colorFromObjectToString(row) { + var canvas = createCanvasObject(row); + var ctx = canvas.getContext('2d'); + + ctx.reset(); + ctx.strokeStyle = anotherColor + ctx.strokeStyle = "red"; + compare(ctx.strokeStyle, "#ff0000"); + + ctx.strokeStyle = anotherColor + ctx.strokeStyle = "black"; + compare(ctx.strokeStyle, "#000000"); + + ctx.strokeStyle = "white"; + ctx.strokeStyle = anotherColor + compare(ctx.strokeStyle, "#0000ff"); + canvas.destroy() + } + function test_withInvalidColor(row) { + var canvas = createCanvasObject(row); + var ctx = canvas.getContext('2d'); + + ctx.reset(); + ctx.strokeStyle = emptyColor + compare(ctx.strokeStyle, "#000000"); + ctx.strokeStyle = "red"; + compare(ctx.strokeStyle, "#ff0000"); + ctx.strokeStyle = emptyColor + compare(ctx.strokeStyle, "#000000"); + ctx.strokeStyle = anotherColor; + compare(ctx.strokeStyle, "#0000ff"); + canvas.destroy() + } } -- cgit v1.2.3 From b00efbf0edd9b4bb206570f653f4451f3522961c Mon Sep 17 00:00:00 2001 From: Mitch Curtis Date: Tue, 12 Feb 2019 14:47:15 +0100 Subject: viewtestutil.h: fix broken namespace build Surround the header in QT_BEGIN_NAMESPACE/QT_END_NAMESPACE to avoid ambiguous references to the QQuickTest namespace. Change-Id: Ib2f991bc55e292e020417eca35c99f2bf844ffb6 Reviewed-by: Liang Qi --- tests/auto/quick/shared/viewtestutil.cpp | 3 +++ tests/auto/quick/shared/viewtestutil.h | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/tests/auto/quick/shared/viewtestutil.cpp b/tests/auto/quick/shared/viewtestutil.cpp index 12d54c4bbe..5631ffe047 100644 --- a/tests/auto/quick/shared/viewtestutil.cpp +++ b/tests/auto/quick/shared/viewtestutil.cpp @@ -38,6 +38,7 @@ #include #include +QT_BEGIN_NAMESPACE QQuickView *QQuickViewTestUtil::createView() { @@ -478,3 +479,5 @@ namespace QQuickTest { return true; } } + +QT_END_NAMESPACE diff --git a/tests/auto/quick/shared/viewtestutil.h b/tests/auto/quick/shared/viewtestutil.h index 3b57a29777..5e725fdf11 100644 --- a/tests/auto/quick/shared/viewtestutil.h +++ b/tests/auto/quick/shared/viewtestutil.h @@ -37,6 +37,8 @@ QT_FORWARD_DECLARE_CLASS(QQuickView) QT_FORWARD_DECLARE_CLASS(QQuickItemViewPrivate) QT_FORWARD_DECLARE_CLASS(FxViewItem) +QT_BEGIN_NAMESPACE + namespace QQuickViewTestUtil { QQuickView *createView(); @@ -189,6 +191,8 @@ namespace QQuickTest { bool initView(QQuickView &v, const QUrl &url, bool moveMouseOut, QByteArray *errorMessage); } +QT_END_NAMESPACE + Q_DECLARE_METATYPE(QQuickViewTestUtil::QaimModel*) Q_DECLARE_METATYPE(QQuickViewTestUtil::ListChange) Q_DECLARE_METATYPE(QList) -- cgit v1.2.3 From dbc811e164019b5f9ce371cb7b49d695644f6a90 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 13 Feb 2019 13:31:07 +0100 Subject: DelegateModel: Zero foreign context objects when they are deleted If we keep plain pointers to objects we don't own, we need to zero them when something else deletes them. Fixes: QTBUG-73733 Change-Id: Ib4f3e144f10f70ab6cf44af4ffa62725470d3972 Reviewed-by: Mitch Curtis --- src/qml/types/qqmldelegatemodel.cpp | 17 +++++- src/qml/types/qqmldelegatemodel_p_p.h | 1 + .../data/externalManagedModel.qml | 65 +++++++++++++++++++++ .../tst_qquickvisualdatamodel.cpp | 68 ++++++++++++++++++++++ 4 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 tests/auto/quick/qquickvisualdatamodel/data/externalManagedModel.qml diff --git a/src/qml/types/qqmldelegatemodel.cpp b/src/qml/types/qqmldelegatemodel.cpp index 57bbf7465d..48cc77bc3d 100644 --- a/src/qml/types/qqmldelegatemodel.cpp +++ b/src/qml/types/qqmldelegatemodel.cpp @@ -1060,7 +1060,11 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQ = qobject_cast(cacheItem)) { ctxt = new QQmlContextData; ctxt->setParent(cacheItem->contextData, /*stronglyReferencedByParent*/true); - ctxt->contextObject = proxy->proxiedObject(); + QObject *proxied = proxy->proxiedObject(); + ctxt->contextObject = proxied; + // We don't own the proxied object. We need to clear it if it goes away. + QObject::connect(proxied, &QObject::destroyed, + cacheItem, &QQmlDelegateModelItem::childContextObjectDestroyed); } } @@ -2009,6 +2013,17 @@ QV4::ReturnedValue QQmlDelegateModelItem::get_index(QQmlDelegateModelItem *thisI return QV4::Encode((int)thisItem->groupIndex(Compositor::Group(flag))); } +void QQmlDelegateModelItem::childContextObjectDestroyed(QObject *childContextObject) +{ + if (!contextData) + return; + + for (QQmlContextData *ctxt = contextData->childContexts; ctxt; ctxt = ctxt->nextChild) { + if (ctxt->contextObject == childContextObject) + ctxt->contextObject = nullptr; + } +} + //--------------------------------------------------------------------------- diff --git a/src/qml/types/qqmldelegatemodel_p_p.h b/src/qml/types/qqmldelegatemodel_p_p.h index 2d6fdf228e..5e480f4df6 100644 --- a/src/qml/types/qqmldelegatemodel_p_p.h +++ b/src/qml/types/qqmldelegatemodel_p_p.h @@ -106,6 +106,7 @@ public: void referenceObject() { ++objectRef; } bool releaseObject() { return --objectRef == 0 && !(groups & Compositor::PersistedFlag); } bool isObjectReferenced() const { return objectRef != 0 || (groups & Compositor::PersistedFlag); } + void childContextObjectDestroyed(QObject *childContextObject); bool isReferenced() const { return scriptRef diff --git a/tests/auto/quick/qquickvisualdatamodel/data/externalManagedModel.qml b/tests/auto/quick/qquickvisualdatamodel/data/externalManagedModel.qml new file mode 100644 index 0000000000..44c157b824 --- /dev/null +++ b/tests/auto/quick/qquickvisualdatamodel/data/externalManagedModel.qml @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tests of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick.Window 2.2 +import QtQuick 2.6 +import QtQml.Models 2.11 +import example 1.0 + +Window { + visible: true + property bool running: rebuildTimer.running + ListView { + anchors.fill: parent + model: delegateModel + } + + DelegateModel { + id: delegateModel + model: objectsProvider.objects + delegate: Item {} + } + + Timer { + id: rebuildTimer + running: true + repeat: true + interval: 1 + + property int count: 0 + onTriggered: { + objectsProvider.rebuild(); + if (++count === 10) + running = false; + } + } + + ObjectsProvider { + id: objectsProvider + } +} diff --git a/tests/auto/quick/qquickvisualdatamodel/tst_qquickvisualdatamodel.cpp b/tests/auto/quick/qquickvisualdatamodel/tst_qquickvisualdatamodel.cpp index 1b8654ecdd..fac8283e2c 100644 --- a/tests/auto/quick/qquickvisualdatamodel/tst_qquickvisualdatamodel.cpp +++ b/tests/auto/quick/qquickvisualdatamodel/tst_qquickvisualdatamodel.cpp @@ -431,6 +431,7 @@ private slots: void asynchronousMove_data(); void asynchronousCancel(); void invalidContext(); + void externalManagedModel(); private: template void groups_verify( @@ -4234,6 +4235,73 @@ void tst_qquickvisualdatamodel::invalidContext() QVERIFY(!item); } +class ObjectsProvider : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQmlListProperty objects READ objects NOTIFY objectsChanged) + +public: + explicit ObjectsProvider(QObject *parent = nullptr) : QObject(parent) {} + + Q_INVOKABLE void rebuild() + { + for (auto old: m_objects) + old->deleteLater(); + + m_objects.clear(); + emit objectsChanged(); + + const int size = std::rand() & 0xff; + for (int i = 0; i < size; ++i) { + auto newElement = new QObject(this); + QQmlEngine::setObjectOwnership(newElement, QQmlEngine::CppOwnership); + m_objects.push_back(newElement); + } + emit objectsChanged(); + } + + Q_INVOKABLE QQmlListProperty objects() + { + return QQmlListProperty(this, nullptr, &ObjectsProvider::listLength, + &ObjectsProvider::listAt); + } + + static int listLength(QQmlListProperty *property) + { + auto objectsProvider = qobject_cast(property->object); + return objectsProvider ? objectsProvider->m_objects.length() : 0; + } + + static QObject* listAt(QQmlListProperty *property, int index) + { + auto objectsProvider = qobject_cast(property->object); + return objectsProvider ? objectsProvider->m_objects.at(index) : nullptr; + } + +signals: + void objectsChanged(); + +private: + QList m_objects; +}; + +void tst_qquickvisualdatamodel::externalManagedModel() +{ + qmlRegisterType("example", 1, 0, "ObjectsProvider"); + + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("externalManagedModel.qml")); + QVERIFY(component.isReady()); + + QScopedPointer object(component.create()); + QVERIFY(!object.isNull()); + + QVERIFY(object->property("running").toBool()); + + // Make sure it runs to completion without crashing. + QTRY_VERIFY(!object->property("running").toBool()); +} + QTEST_MAIN(tst_qquickvisualdatamodel) #include "tst_qquickvisualdatamodel.moc" -- cgit v1.2.3 From 9343fbc478e42c7aec3247486b25b34f1908e93b Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Tue, 12 Feb 2019 15:22:19 +0100 Subject: PropertCache: Don't pass Q_GADGET value types as integers We need to check for the IsGadget flag there. Fixes: QTBUG-73734 Change-Id: Ic4afd4215e6ed346bc40794d85397f0f262715e2 Reviewed-by: Simon Hausmann --- src/qml/qml/qqmlpropertycache.cpp | 4 +-- .../qml/qqmlpropertycache/data/passQGadget.qml | 12 +++++++ .../qqmlpropertycache/tst_qqmlpropertycache.cpp | 39 ++++++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 tests/auto/qml/qqmlpropertycache/data/passQGadget.qml diff --git a/src/qml/qml/qqmlpropertycache.cpp b/src/qml/qml/qqmlpropertycache.cpp index f91ba78932..95bdcf13e8 100644 --- a/src/qml/qml/qqmlpropertycache.cpp +++ b/src/qml/qml/qqmlpropertycache.cpp @@ -929,8 +929,8 @@ static bool passTypeAsInt(int type) if (type < int(QMetaType::User)) return false; - // Pointers to QObjects and QGadgets can be handled as they are. - if (flags & (QMetaType::PointerToQObject | QMetaType::PointerToGadget)) + // Pointers to QObjects and QGadgets, and QGadgets themselves can be handled as they are. + if (flags & (QMetaType::PointerToQObject | QMetaType::PointerToGadget | QMetaType::IsGadget)) return false; // If it wasn't declared as metatype, better don't touch it. diff --git a/tests/auto/qml/qqmlpropertycache/data/passQGadget.qml b/tests/auto/qml/qqmlpropertycache/data/passQGadget.qml new file mode 100644 index 0000000000..86fdd920ed --- /dev/null +++ b/tests/auto/qml/qqmlpropertycache/data/passQGadget.qml @@ -0,0 +1,12 @@ +import QtQml 2.2 + +QtObject { + property var result; + + property Connections connections: Connections { + target: emitter + onEmitGadget: function(gadget) { + result = gadget.someProperty; + } + } +} diff --git a/tests/auto/qml/qqmlpropertycache/tst_qqmlpropertycache.cpp b/tests/auto/qml/qqmlpropertycache/tst_qqmlpropertycache.cpp index 1c8e3c50ab..f577a091eb 100644 --- a/tests/auto/qml/qqmlpropertycache/tst_qqmlpropertycache.cpp +++ b/tests/auto/qml/qqmlpropertycache/tst_qqmlpropertycache.cpp @@ -50,6 +50,7 @@ private slots: void signalHandlers(); void signalHandlersDerived(); void passForeignEnums(); + void passQGadget(); void metaObjectSize_data(); void metaObjectSize(); void metaObjectChecksum(); @@ -338,6 +339,44 @@ void tst_qqmlpropertycache::passForeignEnums() Q_DECLARE_METATYPE(MyEnum::Option1) +QT_BEGIN_NAMESPACE +class SimpleGadget +{ + Q_GADGET + Q_PROPERTY(bool someProperty READ someProperty) +public: + bool someProperty() const { return true; } +}; + +// Avoids NeedsCreation and NeedsDestruction flags +Q_DECLARE_TYPEINFO(SimpleGadget, Q_PRIMITIVE_TYPE); +QT_END_NAMESPACE + +class GadgetEmitter : public QObject +{ + Q_OBJECT +signals: + void emitGadget(SimpleGadget); +}; + +void tst_qqmlpropertycache::passQGadget() +{ + qRegisterMetaType(); + + GadgetEmitter emitter; + engine.rootContext()->setContextProperty("emitter", &emitter); + QQmlComponent component(&engine, testFile("passQGadget.qml")); + QVERIFY(component.isReady()); + + QScopedPointer obj(component.create(engine.rootContext())); + QVariant before = obj->property("result"); + QVERIFY(before.isNull()); + emit emitter.emitGadget(SimpleGadget()); + QVariant after = obj->property("result"); + QCOMPARE(QMetaType::Type(after.type()), QMetaType::Bool); + QVERIFY(after.toBool()); +} + class TestClass : public QObject { Q_OBJECT -- cgit v1.2.3 From 8df1afa89558ddb608ff0df792d2015dd5e2e6ac Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Thu, 14 Feb 2019 11:06:35 +0100 Subject: V4: Fix JS tail call crashes on win32/linux32 For platforms where arguments are passed on the stack, we would do an invalid (off-by-one) calcultion to see where we should put arguments for a tail call, thereby overwriting other values. As we don't write to these memory locations anywhere, and the arguments are exactly the same as calls to jitted code (which is done by design), we could just as well re-use them. Change-Id: If4118b2023da6dc301252a1579a36df0e0cbc3a5 Reviewed-by: Simon Hausmann --- src/qml/jit/qv4assemblercommon.cpp | 16 ++++++---------- src/qml/jit/qv4assemblercommon_p.h | 1 - 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/qml/jit/qv4assemblercommon.cpp b/src/qml/jit/qv4assemblercommon.cpp index b302ac6403..831496a628 100644 --- a/src/qml/jit/qv4assemblercommon.cpp +++ b/src/qml/jit/qv4assemblercommon.cpp @@ -212,13 +212,6 @@ PlatformAssemblerCommon::Address PlatformAssemblerCommon::argStackAddress(int ar return Address(StackPointerRegister, offset * PointerSize); } -JSC::MacroAssemblerBase::Address PlatformAssemblerCommon::inArgStackAddress(int arg) -{ - int offset = arg - ArgInRegCount; - Q_ASSERT(offset >= 0); - return Address(FramePointerRegister, -(offset + 1) * PointerSize); -} - void PlatformAssemblerCommon::passAccumulatorAsArg(int arg) { #ifndef QT_NO_DEBUG @@ -354,10 +347,13 @@ void PlatformAssemblerCommon::tailCallRuntime(const char *functionName, const vo void PlatformAssemblerCommon::setTailCallArg(RegisterID src, int arg) { - if (arg < ArgInRegCount) + if (arg < ArgInRegCount) { move(src, registerForArg(arg)); - else - storePtr(src, inArgStackAddress(arg)); + } else { + // We never write to the incoming arguments space on the stack, and the tail call runtime + // method has the same signature as the jitted function, so it is safe for us to just reuse + // the arguments that we got in. + } } JSC::MacroAssemblerBase::Address PlatformAssemblerCommon::jsAlloca(int slotCount) diff --git a/src/qml/jit/qv4assemblercommon_p.h b/src/qml/jit/qv4assemblercommon_p.h index c17fdd3a23..729d0fc53d 100644 --- a/src/qml/jit/qv4assemblercommon_p.h +++ b/src/qml/jit/qv4assemblercommon_p.h @@ -709,7 +709,6 @@ public: private: void passAccumulatorAsArg_internal(int arg, bool doPush); static Address argStackAddress(int arg); - static Address inArgStackAddress(int arg); private: const Value* constantTable; -- cgit v1.2.3 From df867d86285c9c9115313c90d7d72ff9a245bce4 Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Wed, 6 Feb 2019 10:47:49 +0100 Subject: QQuickTableView: clear cache before querying column widths/row heights Otherwise, if we're unlucky, it's the stored column in the cache that changes visibility. And unless we clear the cache, we'll not detect it. Change-Id: I0933a7d6cd3056d7d3883bd032ea5f8fa048402e Reviewed-by: Mitch Curtis --- src/quick/items/qquicktableview.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index b3305ccaeb..75cafbc1a0 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -706,6 +706,7 @@ void QQuickTableViewPrivate::syncLoadedTableRectFromLoadedTable() void QQuickTableViewPrivate::forceLayout() { columnRowPositionsInvalid = true; + clearEdgeSizeCache(); RebuildOptions rebuildOptions = RebuildOption::None; // Go through all columns from first to last, find the columns that used -- cgit v1.2.3 From 3dcc9dde65c780fb87ff9feef60dfb16d6748eb0 Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Thu, 7 Feb 2019 15:09:37 +0100 Subject: QQuickTableView: ensure we end up caching the correct value After the call to the application, we check the return value and adjust it if it's e.g NaN. And we need to cache the adjusted value, not the raw return value, otherwise the getColumnWidth()/getRowHeight() functions might return different values on subsequent calls. Change-Id: I7f3134f599b9863641132811ab7d5883cc02857b Reviewed-by: Mitch Curtis --- src/quick/items/qquicktableview.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 75cafbc1a0..28871e8068 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -1112,8 +1112,6 @@ qreal QQuickTableViewPrivate::getColumnWidth(int column) if (columnWidthProvider.isCallable()) { auto const columnAsArgument = QJSValueList() << QJSValue(column); columnWidth = columnWidthProvider.call(columnAsArgument).toNumber(); - cachedColumnWidth.startIndex = column; - cachedColumnWidth.size = columnWidth; if (qIsNaN(columnWidth) || columnWidth < 0) columnWidth = noExplicitColumnWidth; } else { @@ -1124,6 +1122,8 @@ qreal QQuickTableViewPrivate::getColumnWidth(int column) columnWidth = noExplicitColumnWidth; } + cachedColumnWidth.startIndex = column; + cachedColumnWidth.size = columnWidth; return columnWidth; } @@ -1146,8 +1146,6 @@ qreal QQuickTableViewPrivate::getRowHeight(int row) if (rowHeightProvider.isCallable()) { auto const rowAsArgument = QJSValueList() << QJSValue(row); rowHeight = rowHeightProvider.call(rowAsArgument).toNumber(); - cachedRowHeight.startIndex = row; - cachedRowHeight.size = rowHeight; if (qIsNaN(rowHeight) || rowHeight < 0) rowHeight = noExplicitRowHeight; } else { @@ -1158,6 +1156,8 @@ qreal QQuickTableViewPrivate::getRowHeight(int row) rowHeight = noExplicitRowHeight; } + cachedRowHeight.startIndex = row; + cachedRowHeight.size = rowHeight; return rowHeight; } -- cgit v1.2.3 From d96a700cc3611480ff76023287cb06f455a37b02 Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Thu, 14 Feb 2019 12:07:45 +0100 Subject: V4: Fix failing assert on all 32bit platforms The failing assert is in qv4mm.cpp:170, which is correct. The failure stemms from the fact that on 32bit platforms, quint64 does not have the same size as quintptr. Who would have thought? Fixes: QTBUG-73821 Change-Id: I9abe6bc9e2bfcdb6700ab7997c078076a9883cf2 Reviewed-by: Simon Hausmann --- src/qml/memory/qv4mm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qml/memory/qv4mm.cpp b/src/qml/memory/qv4mm.cpp index 3cf22d82e5..203f1f424f 100644 --- a/src/qml/memory/qv4mm.cpp +++ b/src/qml/memory/qv4mm.cpp @@ -201,7 +201,7 @@ Chunk *MemorySegment::allocate(size_t size) // chunk allocated for one huge allocation Q_ASSERT(availableBytes >= size); pageReservation.commit(base, size); - allocatedMap = ~static_cast(0); + allocatedMap = ~static_cast(0); return base; } size_t requiredChunks = (size + sizeof(Chunk) - 1)/sizeof(Chunk); -- cgit v1.2.3 From 1d0f808c7dc708f5ec8093f09f332615b94f4318 Mon Sep 17 00:00:00 2001 From: Nils Jeisecke Date: Wed, 13 Feb 2019 15:28:11 +0100 Subject: TextEdit: Fix persistentSelection for readonly controls TextEdit items with readOnly:true do not clear the selection on losing focus which is expected with persistentSelection:false. The reason is that a readonly TextEdit does never show a blinking cursor and thus the selection clearing within setCursorVisible never happens. This change adapts the implementation from TextInput. Fixes: QTBUG-50587 Change-Id: Ie66baaa0ccbc006359473862d8e9dbecd46a59f6 Reviewed-by: Shawn Rutledge Reviewed-by: Mitch Curtis --- src/quick/items/qquicktextedit.cpp | 6 +++++ .../quick/qquicktextedit/tst_qquicktextedit.cpp | 28 ++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp index 3a12ad6ba5..6b4b118eb7 100644 --- a/src/quick/items/qquicktextedit.cpp +++ b/src/quick/items/qquicktextedit.cpp @@ -2689,6 +2689,12 @@ void QQuickTextEditPrivate::handleFocusEvent(QFocusEvent *event) q->disconnect(QGuiApplication::inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)), q, SLOT(q_updateAlignment())); #endif + if (event->reason() != Qt::ActiveWindowFocusReason + && event->reason() != Qt::PopupFocusReason + && control->textCursor().hasSelection() + && !persistentSelection) + q->deselect(); + emit q->editingFinished(); } } diff --git a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp index f32da44daa..ce2a8eb257 100644 --- a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp +++ b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp @@ -1262,6 +1262,34 @@ void tst_qquicktextedit::persistentSelection() edit->setFocus(true); QCOMPARE(edit->property("selected").toString(), QLatin1String("ell")); + // QTBUG-50587 (persistentSelection with readOnly) + edit->setReadOnly(true); + + edit->setPersistentSelection(false); + QCOMPARE(edit->persistentSelection(), false); + QCOMPARE(spy.count(), 2); + + edit->select(1, 4); + QCOMPARE(edit->property("selected").toString(), QLatin1String("ell")); + + edit->setFocus(false); + QCOMPARE(edit->property("selected").toString(), QString()); + + edit->setFocus(true); + QCOMPARE(edit->property("selected").toString(), QString()); + + edit->setPersistentSelection(true); + QCOMPARE(edit->persistentSelection(), true); + QCOMPARE(spy.count(), 3); + + edit->select(1, 4); + QCOMPARE(edit->property("selected").toString(), QLatin1String("ell")); + + edit->setFocus(false); + QCOMPARE(edit->property("selected").toString(), QLatin1String("ell")); + + edit->setFocus(true); + QCOMPARE(edit->property("selected").toString(), QLatin1String("ell")); } void tst_qquicktextedit::selectionOnFocusOut() -- cgit v1.2.3 From fee0fcfef08a05ed4ba9369d2352c876b514d69c Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Wed, 13 Feb 2019 19:18:47 +0100 Subject: Fix qml block in doc comment for Window::transientParent Change-Id: I95c7557dd552dd86bfcaa7eebf43f00516ef7db6 Reviewed-by: Shawn Rutledge --- src/quick/items/qquickwindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index f517b5b3e9..485c0dfea7 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -4630,7 +4630,7 @@ void QQuickWindow::resetOpenGLState() Item or Window within which it was declared, you can remove that relationship by setting \c transientParent to \c null: - \l qml + \qml import QtQuick.Window 2.13 Window { @@ -4640,7 +4640,7 @@ void QQuickWindow::resetOpenGLState() visible: true } } - \qml + \endqml In order to cause the window to be centered above its transient parent by default, depending on the window manager, it may also be necessary to set -- cgit v1.2.3