diff options
author | Laszlo Agocs <laszlo.agocs@qt.io> | 2019-05-28 17:59:34 +0200 |
---|---|---|
committer | Laszlo Agocs <laszlo.agocs@qt.io> | 2019-05-28 17:59:40 +0200 |
commit | 1f3c157ec6b8be5de1eb89295713c2980fb1a7aa (patch) | |
tree | b85888d73518f6abc7bbde74ab71a27887682f9d /src/quick/items | |
parent | 44ca7e31ee9365a72cd17ecd335ec4d0161420a9 (diff) | |
parent | 0f5c34f2c6b64bae3429706a6c4211334c689092 (diff) |
Merge "Merge remote-tracking branch 'origin/dev' into wip/scenegraphng"wip/scenegraphng
Diffstat (limited to 'src/quick/items')
23 files changed, 437 insertions, 164 deletions
diff --git a/src/quick/items/context2d/qquickcontext2d.cpp b/src/quick/items/context2d/qquickcontext2d.cpp index 58bc12a221..0f104a122f 100644 --- a/src/quick/items/context2d/qquickcontext2d.cpp +++ b/src/quick/items/context2d/qquickcontext2d.cpp @@ -473,7 +473,7 @@ static QFont qt_font_from_string(const QString& fontString, const QFont ¤t return newFont; } -class QQuickContext2DEngineData : public QV8Engine::Deletable +class QQuickContext2DEngineData : public QV4::ExecutionEngine::Deletable { public: QQuickContext2DEngineData(QV4::ExecutionEngine *engine); @@ -2000,7 +2000,7 @@ QV4::ReturnedValue QQuickJSContext2D::method_set_miterLimit(const QV4::FunctionO \since QtQuick 2.11 Returns an array of qreals representing the dash pattern of the line. - \sa setLineDash() + \sa setLineDash(), lineDashOffset */ QV4::ReturnedValue QQuickJSContext2DPrototype::method_getLineDash(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) { @@ -2022,7 +2022,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_getLineDash(const QV4::Fun /*! \qmlmethod QtQuick::Context2D::setLineDash(array pattern) \since QtQuick 2.11 - Sets the dash pattern to the given pattern + Sets the dash pattern to the given pattern. \a pattern a list of numbers that specifies distances to alternately draw a line and a gap. @@ -2041,7 +2041,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_getLineDash(const QV4::Fun \endcode \endtable - \sa setLineDash + \sa getLineDash(), lineDashOffset */ QV4::ReturnedValue QQuickJSContext2DPrototype::method_setLineDash(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc) { @@ -2083,8 +2083,10 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_setLineDash(const QV4::Fun \qmlproperty real QtQuick::Context2D::lineDashOffset \since QtQuick 2.11 - Holds the current line dash offset - The default line dash ofset value is 0 + Holds the current line dash offset. + The default line dash offset value is \c 0. + + \sa getLineDash(), setLineDash() */ QV4::ReturnedValue QQuickJSContext2D::method_get_lineDashOffset(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) { diff --git a/src/quick/items/qquickaccessibleattached.cpp b/src/quick/items/qquickaccessibleattached.cpp index 0168c3160c..c150e4efa2 100644 --- a/src/quick/items/qquickaccessibleattached.cpp +++ b/src/quick/items/qquickaccessibleattached.cpp @@ -390,6 +390,49 @@ QQuickAccessibleAttached::~QQuickAccessibleAttached() { } +void QQuickAccessibleAttached::setRole(QAccessible::Role role) +{ + if (role != m_role) { + m_role = role; + Q_EMIT roleChanged(); + // There is no way to signify role changes at the moment. + // QAccessible::updateAccessibility(parent(), 0, QAccessible::); + + switch (role) { + case QAccessible::CheckBox: + case QAccessible::RadioButton: + if (!m_stateExplicitlySet.focusable) + m_state.focusable = true; + if (!m_stateExplicitlySet.checkable) + m_state.checkable = true; + break; + case QAccessible::Button: + case QAccessible::MenuItem: + case QAccessible::PageTab: + case QAccessible::SpinBox: + case QAccessible::ComboBox: + case QAccessible::Terminal: + case QAccessible::ScrollBar: + if (!m_stateExplicitlySet.focusable) + m_state.focusable = true; + break; + case QAccessible::EditableText: + if (!m_stateExplicitlySet.editable) + m_state.editable = true; + if (!m_stateExplicitlySet.focusable) + m_state.focusable = true; + break; + case QAccessible::StaticText: + if (!m_stateExplicitlySet.readOnly) { + m_state.readOnly = true; + } + break; + default: + break; + } + } +} + QQuickAccessibleAttached *QQuickAccessibleAttached::qmlAttachedProperties(QObject *obj) { return new QQuickAccessibleAttached(obj); diff --git a/src/quick/items/qquickaccessibleattached_p.h b/src/quick/items/qquickaccessibleattached_p.h index 215a1e5db6..f4194ef13d 100644 --- a/src/quick/items/qquickaccessibleattached_p.h +++ b/src/quick/items/qquickaccessibleattached_p.h @@ -69,6 +69,7 @@ QT_BEGIN_NAMESPACE bool P() const { return m_state.P ; } \ void set_ ## P(bool arg) \ { \ + m_stateExplicitlySet.P = true; \ if (m_state.P == arg) \ return; \ m_state.P = arg; \ @@ -111,35 +112,7 @@ public: ~QQuickAccessibleAttached(); QAccessible::Role role() const { return m_role; } - void setRole(QAccessible::Role role) - { - if (role != m_role) { - m_role = role; - Q_EMIT roleChanged(); - // There is no way to signify role changes at the moment. - // QAccessible::updateAccessibility(parent(), 0, QAccessible::); - - switch (role) { - case QAccessible::CheckBox: - case QAccessible::RadioButton: - m_state.focusable = true; - m_state.checkable = true; - break; - case QAccessible::Button: - case QAccessible::MenuItem: - case QAccessible::PageTab: - case QAccessible::EditableText: - case QAccessible::SpinBox: - case QAccessible::ComboBox: - case QAccessible::Terminal: - case QAccessible::ScrollBar: - m_state.focusable = true; - break; - default: - break; - } - } - } + void setRole(QAccessible::Role role); QString name() const { if (m_state.passwordEdit) return QString(); @@ -241,6 +214,7 @@ private: QAccessible::Role m_role; QAccessible::State m_state; + QAccessible::State m_stateExplicitlySet; QString m_name; QString m_description; diff --git a/src/quick/items/qquickopenglshadereffect.cpp b/src/quick/items/qquickopenglshadereffect.cpp index bc1f787b81..0cbabaa170 100644 --- a/src/quick/items/qquickopenglshadereffect.cpp +++ b/src/quick/items/qquickopenglshadereffect.cpp @@ -221,7 +221,7 @@ QQuickOpenGLShaderEffectCommon::~QQuickOpenGLShaderEffectCommon() clearSignalMappers(shaderType); } -void QQuickOpenGLShaderEffectCommon::disconnectPropertySignals(QQuickItem *item, Key::ShaderType shaderType) +void QQuickOpenGLShaderEffectCommon::disconnectPropertySignals(QObject *obj, Key::ShaderType shaderType) { for (int i = 0; i < uniformData[shaderType].size(); ++i) { if (signalMappers[shaderType].at(i) == 0) @@ -229,12 +229,11 @@ void QQuickOpenGLShaderEffectCommon::disconnectPropertySignals(QQuickItem *item, const UniformData &d = uniformData[shaderType].at(i); auto mapper = signalMappers[shaderType].at(i); void *a = mapper; - QObjectPrivate::disconnect(item, mapper->signalIndex(), &a); + QObjectPrivate::disconnect(obj, mapper->signalIndex(), &a); if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) { QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value)); if (source) { - if (item->window()) - QQuickItemPrivate::get(source)->derefWindow(); + QQuickItemPrivate::get(source)->derefWindow(); QObject::disconnect(source, SIGNAL(destroyed(QObject*)), host, SLOT(sourceDestroyed(QObject*))); } } diff --git a/src/quick/items/qquickopenglshadereffect_p.h b/src/quick/items/qquickopenglshadereffect_p.h index 0c2adadc62..3087c1eb0b 100644 --- a/src/quick/items/qquickopenglshadereffect_p.h +++ b/src/quick/items/qquickopenglshadereffect_p.h @@ -89,7 +89,7 @@ struct Q_QUICK_PRIVATE_EXPORT QQuickOpenGLShaderEffectCommon ~QQuickOpenGLShaderEffectCommon(); - void disconnectPropertySignals(QQuickItem *item, Key::ShaderType shaderType); + void disconnectPropertySignals(QObject *item, Key::ShaderType shaderType); void connectPropertySignals(QQuickItem *item, const QMetaObject *itemMetaObject, Key::ShaderType shaderType); void updateParseLog(bool ignoreAttributes); void lookThroughShaderCode(QQuickItem *item, const QMetaObject *itemMetaObject, Key::ShaderType shaderType, const QByteArray &code); diff --git a/src/quick/items/qquickopenglshadereffectnode.cpp b/src/quick/items/qquickopenglshadereffectnode.cpp index f32b32491b..f96ebebcd6 100644 --- a/src/quick/items/qquickopenglshadereffectnode.cpp +++ b/src/quick/items/qquickopenglshadereffectnode.cpp @@ -477,11 +477,11 @@ void QQuickOpenGLShaderEffectMaterial::updateTextures() const } } -void QQuickOpenGLShaderEffectMaterial::invalidateTextureProvider(QSGTextureProvider *provider) +void QQuickOpenGLShaderEffectMaterial::invalidateTextureProvider(const QObject *provider) { for (int i = 0; i < textureProviders.size(); ++i) { if (provider == textureProviders.at(i)) - textureProviders[i] = 0; + textureProviders[i] = nullptr; } } @@ -505,10 +505,10 @@ void QQuickOpenGLShaderEffectNode::markDirtyTexture() Q_EMIT dirtyTexture(); } -void QQuickOpenGLShaderEffectNode::textureProviderDestroyed(QObject *object) +void QQuickOpenGLShaderEffectNode::textureProviderDestroyed(const QObject *object) { Q_ASSERT(material()); - static_cast<QQuickOpenGLShaderEffectMaterial *>(material())->invalidateTextureProvider(static_cast<QSGTextureProvider *>(object)); + static_cast<QQuickOpenGLShaderEffectMaterial *>(material())->invalidateTextureProvider(object); } void QQuickOpenGLShaderEffectNode::preprocess() diff --git a/src/quick/items/qquickopenglshadereffectnode_p.h b/src/quick/items/qquickopenglshadereffectnode_p.h index 7c75bb3126..6d68ba87b9 100644 --- a/src/quick/items/qquickopenglshadereffectnode_p.h +++ b/src/quick/items/qquickopenglshadereffectnode_p.h @@ -122,7 +122,7 @@ public: void setProgramSource(const QQuickOpenGLShaderEffectMaterialKey &source); void updateTextures() const; - void invalidateTextureProvider(QSGTextureProvider *provider); + void invalidateTextureProvider(const QObject *provider); static void cleanupMaterialCache(); @@ -159,7 +159,7 @@ Q_SIGNALS: private Q_SLOTS: void markDirtyTexture(); - void textureProviderDestroyed(QObject *object); + void textureProviderDestroyed(const QObject *object); }; QT_END_NAMESPACE diff --git a/src/quick/items/qquickshadereffect.cpp b/src/quick/items/qquickshadereffect.cpp index ab79b69c8c..05d9e5e36d 100644 --- a/src/quick/items/qquickshadereffect.cpp +++ b/src/quick/items/qquickshadereffect.cpp @@ -795,7 +795,8 @@ bool QQuickShaderEffect::event(QEvent *e) return QQuickItem::event(e); } #endif - m_impl->handleEvent(e); + if (m_impl) + m_impl->handleEvent(e); return QQuickItem::event(e); } diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 8f5130fc17..9583ef4231 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -305,9 +305,7 @@ \l view, which means that the table's width could be larger or smaller than the viewport width. As a TableView cannot always know the exact width of the table without loading all columns in the model, the \c contentWidth is - usually an estimate based on the columns it has seen so far. This estimate - is recalculated whenever new columns are flicked into view, which means - that the content width can change dynamically. + usually an estimate based on the initially loaded table. If you know what the width of the table will be, assign a value to \c contentWidth, to avoid unnecessary calculations and updates to the @@ -324,9 +322,7 @@ \c view, which means that the table's height could be larger or smaller than the viewport height. As a TableView cannot always know the exact height of the table without loading all rows in the model, the \c contentHeight is - usually an estimate based on the rows it has seen so far. This estimate is - recalculated whenever new rows are flicked into view, which means that - the content height can change dynamically. + usually an estimate based on the initially loaded table. If you know what the height of the table will be, assign a value to \c contentHeight, to avoid unnecessary calculations and updates to @@ -616,6 +612,7 @@ void QQuickTableViewPrivate::updateContentWidth() Q_Q(QQuickTableView); if (syncHorizontally) { + QBoolBlocker fixupGuard(inUpdateContentSize, true); q->QQuickFlickable::setContentWidth(syncView->contentWidth()); return; } @@ -632,6 +629,8 @@ void QQuickTableViewPrivate::updateContentWidth() const qreal remainingSpacing = columnsRemaining * cellSpacing.width(); const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing; const qreal estimatedWidth = loadedTableOuterRect.right() + estimatedRemainingWidth; + + QBoolBlocker fixupGuard(inUpdateContentSize, true); q->QQuickFlickable::setContentWidth(estimatedWidth); } @@ -640,6 +639,7 @@ void QQuickTableViewPrivate::updateContentHeight() Q_Q(QQuickTableView); if (syncVertically) { + QBoolBlocker fixupGuard(inUpdateContentSize, true); q->QQuickFlickable::setContentHeight(syncView->contentHeight()); return; } @@ -656,64 +656,204 @@ void QQuickTableViewPrivate::updateContentHeight() const qreal remainingSpacing = rowsRemaining * cellSpacing.height(); const qreal estimatedRemainingHeight = remainingRowHeights + remainingSpacing; const qreal estimatedHeight = loadedTableOuterRect.bottom() + estimatedRemainingHeight; + + QBoolBlocker fixupGuard(inUpdateContentSize, true); q->QQuickFlickable::setContentHeight(estimatedHeight); } -void QQuickTableViewPrivate::enforceTableAtOrigin() +void QQuickTableViewPrivate::updateExtents() { - // Gaps before the first row/column can happen if rows/columns - // changes size while flicking e.g because of spacing changes or - // changes to a column maxWidth/row maxHeight. Check for this, and - // move the whole table rect accordingly. - bool layoutNeeded = false; - const qreal flickMargin = 50; + // When rows or columns outside the viewport are removed or added, or a rebuild + // forces us to guesstimate a new top-left, the edges of the table might end up + // out of sync with the edges of the content view. We detect this situation here, and + // move the origin to ensure that there will never be gaps at the end of the table. + // Normally we detect that the size of the whole table is not going to be equal to the + // size of the content view already when we load the last row/column, and especially + // before it's flicked completely inside the viewport. For those cases we simply adjust + // the origin/endExtent, to give a smooth flicking experience. + // But if flicking fast (e.g with a scrollbar), it can happen that the viewport ends up + // outside the end of the table in just one viewport update. To avoid a "blink" in the + // viewport when that happens, we "move" the loaded table into the viewport to cover it. + Q_Q(QQuickTableView); - const bool noMoreColumns = nextVisibleEdgeIndexAroundLoadedTable(Qt::LeftEdge) == kEdgeIndexAtEnd; - const bool noMoreRows = nextVisibleEdgeIndexAroundLoadedTable(Qt::TopEdge) == kEdgeIndexAtEnd; + bool tableMovedHorizontally = false; + bool tableMovedVertically = false; - 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; + const int nextLeftColumn = nextVisibleEdgeIndexAroundLoadedTable(Qt::LeftEdge); + const int nextRightColumn = nextVisibleEdgeIndexAroundLoadedTable(Qt::RightEdge); + const int nextTopRow = nextVisibleEdgeIndexAroundLoadedTable(Qt::TopEdge); + const int nextBottomRow = nextVisibleEdgeIndexAroundLoadedTable(Qt::BottomEdge); + + if (syncHorizontally) { + const auto syncView_d = syncView->d_func(); + origin.rx() = syncView_d->origin.x(); + endExtent.rwidth() = syncView_d->endExtent.width(); + hData.markExtentsDirty(); + } else if (nextLeftColumn == kEdgeIndexAtEnd) { + // There are no more columns to load on the left side of the table. + // In that case, we ensure that the origin match the beginning of the table. + if (loadedTableOuterRect.left() > viewportRect.left()) { + // We have a blank area at the left end of the viewport. In that case we don't have time to + // wait for the viewport to move (after changing origin), since that will take an extra + // update cycle, which will be visible as a blink. Instead, unless the blank spot is just + // us overshooting, we brute force the loaded table inside the already existing viewport. + if (loadedTableOuterRect.left() > origin.x()) { + const qreal diff = loadedTableOuterRect.left() - origin.x(); + loadedTableOuterRect.moveLeft(loadedTableOuterRect.left() - diff); + loadedTableInnerRect.moveLeft(loadedTableInnerRect.left() - diff); + tableMovedHorizontally = 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; + origin.rx() = loadedTableOuterRect.left(); + hData.markExtentsDirty(); + } else if (loadedTableOuterRect.left() <= origin.x() + cellSpacing.width()) { + // The table rect is at the origin, or outside, but we still have more + // visible columns to the left. So we try to guesstimate how much space + // the rest of the columns will occupy, and move the origin accordingly. + const int columnsRemaining = nextLeftColumn + 1; + const qreal remainingColumnWidths = columnsRemaining * averageEdgeSize.width(); + const qreal remainingSpacing = columnsRemaining * cellSpacing.width(); + const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing; + origin.rx() = loadedTableOuterRect.left() - estimatedRemainingWidth; + hData.markExtentsDirty(); + } else if (nextRightColumn == kEdgeIndexAtEnd) { + // There are no more columns to load on the right side of the table. + // In that case, we ensure that the end of the content view match the end of the table. + if (loadedTableOuterRect.right() < viewportRect.right()) { + // We have a blank area at the right end of the viewport. In that case we don't have time to + // wait for the viewport to move (after changing endExtent), since that will take an extra + // update cycle, which will be visible as a blink. Instead, unless the blank spot is just + // us overshooting, we brute force the loaded table inside the already existing viewport. + const qreal w = qMin(viewportRect.right(), q->contentWidth() + endExtent.width()); + if (loadedTableOuterRect.right() < w) { + const qreal diff = loadedTableOuterRect.right() - w; + loadedTableOuterRect.moveRight(loadedTableOuterRect.right() - diff); + loadedTableInnerRect.moveRight(loadedTableInnerRect.right() - diff); + tableMovedHorizontally = true; + } } + endExtent.rwidth() = loadedTableOuterRect.right() - q->contentWidth(); + hData.markExtentsDirty(); + } else if (loadedTableOuterRect.right() >= q->contentWidth() + endExtent.width() - cellSpacing.width()) { + // The right-most column is outside the end of the content view, and we + // still have more visible columns in the model. This can happen if the application + // has set a fixed content width. + const int columnsRemaining = tableSize.width() - nextRightColumn; + const qreal remainingColumnWidths = columnsRemaining * averageEdgeSize.width(); + const qreal remainingSpacing = columnsRemaining * cellSpacing.width(); + const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing; + const qreal pixelsOutsideContentWidth = loadedTableOuterRect.right() - q->contentWidth(); + endExtent.rwidth() = pixelsOutsideContentWidth + estimatedRemainingWidth; + hData.markExtentsDirty(); } - if (noMoreRows) { - if (!qFuzzyIsNull(loadedTableOuterRect.top())) { - loadedTableOuterRect.moveTop(0); - layoutNeeded = true; + if (syncVertically) { + const auto syncView_d = syncView->d_func(); + origin.ry() = syncView_d->origin.y(); + endExtent.rheight() = syncView_d->endExtent.height(); + vData.markExtentsDirty(); + } else if (nextTopRow == kEdgeIndexAtEnd) { + // There are no more rows to load on the top side of the table. + // In that case, we ensure that the origin match the beginning of the table. + if (loadedTableOuterRect.top() > viewportRect.top()) { + // We have a blank area at the top of the viewport. In that case we don't have time to + // wait for the viewport to move (after changing origin), since that will take an extra + // update cycle, which will be visible as a blink. Instead, unless the blank spot is just + // us overshooting, we brute force the loaded table inside the already existing viewport. + if (loadedTableOuterRect.top() > origin.y()) { + const qreal diff = loadedTableOuterRect.top() - origin.y(); + loadedTableOuterRect.moveTop(loadedTableOuterRect.top() - diff); + loadedTableInnerRect.moveTop(loadedTableInnerRect.top() - diff); + tableMovedVertically = true; + } } - } else { - if (loadedTableOuterRect.top() <= 0) { - loadedTableOuterRect.moveTop(flickMargin); - layoutNeeded = true; + origin.ry() = loadedTableOuterRect.top(); + vData.markExtentsDirty(); + } else if (loadedTableOuterRect.top() <= origin.y() + cellSpacing.height()) { + // The table rect is at the origin, or outside, but we still have more + // visible rows at the top. So we try to guesstimate how much space + // the rest of the rows will occupy, and move the origin accordingly. + const int rowsRemaining = nextTopRow + 1; + const qreal remainingRowHeights = rowsRemaining * averageEdgeSize.height(); + const qreal remainingSpacing = rowsRemaining * cellSpacing.height(); + const qreal estimatedRemainingHeight = remainingRowHeights + remainingSpacing; + origin.ry() = loadedTableOuterRect.top() - estimatedRemainingHeight; + vData.markExtentsDirty(); + } else if (nextBottomRow == kEdgeIndexAtEnd) { + // There are no more rows to load on the bottom side of the table. + // In that case, we ensure that the end of the content view match the end of the table. + if (loadedTableOuterRect.bottom() < viewportRect.bottom()) { + // We have a blank area at the bottom of the viewport. In that case we don't have time to + // wait for the viewport to move (after changing endExtent), since that will take an extra + // update cycle, which will be visible as a blink. Instead, unless the blank spot is just + // us overshooting, we brute force the loaded table inside the already existing viewport. + const qreal h = qMin(viewportRect.bottom(), q->contentHeight() + endExtent.height()); + if (loadedTableOuterRect.bottom() < h) { + const qreal diff = loadedTableOuterRect.bottom() - h; + loadedTableOuterRect.moveBottom(loadedTableOuterRect.bottom() - diff); + loadedTableInnerRect.moveBottom(loadedTableInnerRect.bottom() - diff); + tableMovedVertically = true; + } + } + endExtent.rheight() = loadedTableOuterRect.bottom() - q->contentHeight(); + vData.markExtentsDirty(); + } else if (loadedTableOuterRect.bottom() >= q->contentHeight() + endExtent.height() - cellSpacing.height()) { + // The bottom-most row is outside the end of the content view, and we + // still have more visible rows in the model. This can happen if the application + // has set a fixed content height. + const int rowsRemaining = tableSize.height() - nextBottomRow; + const qreal remainingRowHeigts = rowsRemaining * averageEdgeSize.height(); + const qreal remainingSpacing = rowsRemaining * cellSpacing.height(); + const qreal estimatedRemainingHeight = remainingRowHeigts + remainingSpacing; + const qreal pixelsOutsideContentHeight = loadedTableOuterRect.bottom() - q->contentHeight(); + endExtent.rheight() = pixelsOutsideContentHeight + estimatedRemainingHeight; + vData.markExtentsDirty(); + } + + if (tableMovedHorizontally || tableMovedVertically) { + qCDebug(lcTableViewDelegateLifecycle) << "move table to" << loadedTableOuterRect; + + // relayoutTableItems() will take care of moving the existing + // delegate items into the new loadedTableOuterRect. + relayoutTableItems(); + + // Inform the sync children that they need to rebuild to stay in sync + for (auto syncChild : qAsConst(syncChildren)) { + auto syncChild_d = syncChild->d_func(); + syncChild_d->scheduledRebuildOptions |= RebuildOption::ViewportOnly; + if (tableMovedHorizontally) + syncChild_d->scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftColumn; + if (tableMovedVertically) + syncChild_d->scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftRow; } } - if (layoutNeeded) { - qCDebug(lcTableViewDelegateLifecycle); - relayoutTableItems(); + if (hData.minExtentDirty || vData.minExtentDirty) { + qCDebug(lcTableViewDelegateLifecycle) << "move origin and endExtent to:" << origin << endExtent; + // updateBeginningEnd() will let the new extents take effect. This will also change the + // visualArea of the flickable, which again will cause any attached scrollbars to adjust + // the position of the handle. Note the latter will cause the viewport to move once more. + updateBeginningEnd(); } } void QQuickTableViewPrivate::updateAverageEdgeSize() { - 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); + if (explicitContentWidth.isValid()) { + const qreal accColumnSpacing = (tableSize.width() - 1) * cellSpacing.width(); + averageEdgeSize.setWidth((explicitContentWidth - accColumnSpacing) / tableSize.width()); + } else { + const qreal accColumnSpacing = (loadedColumns.count() - 1) * cellSpacing.width(); + averageEdgeSize.setWidth((loadedTableOuterRect.width() - accColumnSpacing) / loadedColumns.count()); + } + + if (explicitContentHeight.isValid()) { + const qreal accRowSpacing = (tableSize.height() - 1) * cellSpacing.height(); + averageEdgeSize.setHeight((explicitContentHeight - accRowSpacing) / tableSize.height()); + } else { + const qreal accRowSpacing = (loadedRows.count() - 1) * cellSpacing.height(); + averageEdgeSize.setHeight((loadedTableOuterRect.height() - accRowSpacing) / loadedRows.count()); + } } void QQuickTableViewPrivate::syncLoadedTableRectFromLoadedTable() @@ -770,13 +910,12 @@ void QQuickTableViewPrivate::forceLayout() scheduleRebuildTable(rebuildOptions); - if (polishing) { + auto rootView = rootSyncView(); + const bool updated = rootView->d_func()->updateTableRecursive(); + if (!updated) { qWarning() << "TableView::forceLayout(): Cannot do an immediate re-layout during an ongoing layout!"; - q_func()->polish(); - return; + rootView->polish(); } - - updatePolish(); } void QQuickTableViewPrivate::syncLoadedTableFromLoadRequest() @@ -1212,19 +1351,6 @@ bool QQuickTableViewPrivate::isRowHidden(int row) return qFuzzyIsNull(getRowHeight(row)); } -void QQuickTableViewPrivate::relayoutTable() -{ - clearEdgeSizeCache(); - relayoutTableItems(); - syncLoadedTableRectFromLoadedTable(); - enforceTableAtOrigin(); - updateContentWidth(); - updateContentHeight(); - // Return back to updatePolish to loadAndUnloadVisibleEdges() - // since the re-layout might have caused some edges to be pushed - // out, while others might have been pushed in. -} - void QQuickTableViewPrivate::relayoutTableItems() { qCDebug(lcTableViewDelegateLifecycle); @@ -1409,20 +1535,7 @@ void QQuickTableViewPrivate::processLoadRequest() if (rebuildState == RebuildState::Done) { // 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; - } + updateExtents(); drainReusePoolAfterLoadRequest(); } @@ -1637,16 +1750,29 @@ void QQuickTableViewPrivate::beginRebuildTable() else if (rebuildOptions & RebuildOption::ViewportOnly) releaseLoadedItems(reusableFlag); + if (rebuildOptions & RebuildOption::All) { + origin = QPointF(0, 0); + endExtent = QSizeF(0, 0); + hData.markExtentsDirty(); + vData.markExtentsDirty(); + updateBeginningEnd(); + } + loadedColumns.clear(); loadedRows.clear(); loadedTableOuterRect = QRect(); loadedTableInnerRect = QRect(); clearEdgeSizeCache(); - if (syncHorizontally) + if (syncHorizontally) { setLocalViewportX(syncView->contentX()); - if (syncVertically) + viewportRect.moveLeft(syncView->d_func()->viewportRect.left()); + } + + if (syncVertically) { setLocalViewportY(syncView->contentY()); + viewportRect.moveTop(syncView->d_func()->viewportRect.top()); + } if (!model) { qCDebug(lcTableViewDelegateLifecycle()) << "no model found, leaving table empty"; @@ -1690,12 +1816,29 @@ void QQuickTableViewPrivate::layoutAfterLoadingInitialTable() // columns, since during the process, we didn't have all the items // available yet for the calculation. So we do it now. The exception // is if we specifically only requested a relayout. - relayoutTable(); + clearEdgeSizeCache(); + relayoutTableItems(); + syncLoadedTableRectFromLoadedTable(); + } + + if (syncView || rebuildOptions.testFlag(RebuildOption::All)) { + // We try to limit how often we update the content size. The main reason is that is has a + // tendency to cause flicker in the viewport if it happens while flicking. But another just + // as valid reason is that we actually never really know what the size of the full table will + // ever be. Even if e.g spacing changes, and we normally would assume that the size of the table + // would increase accordingly, the model might also at some point have removed/hidden/resized + // rows/columns outside the viewport. This would also affect the size, but since we don't load + // rows or columns outside the viewport, this information is ignored. And even if we did, we + // might also have been fast-flicked to a new location at some point, and started a new rebuild + // there based on a new guesstimated top-left cell. Either way, changing the content size + // based on the currently visible row/columns/spacing can be really off. So instead of pretending + // that we know what the actual size of the table is, we just keep the first guesstimate. + updateAverageEdgeSize(); + updateContentWidth(); + updateContentHeight(); } - updateAverageEdgeSize(); - updateContentWidth(); - updateContentHeight(); + updateExtents(); } void QQuickTableViewPrivate::unloadEdge(Qt::Edge edge) @@ -1710,8 +1853,6 @@ void QQuickTableViewPrivate::unloadEdge(Qt::Edge edge) unloadItem(QPoint(column, r.key())); loadedColumns.remove(column); syncLoadedTableRectFromLoadedTable(); - updateAverageEdgeSize(); - updateContentWidth(); break; } case Qt::TopEdge: case Qt::BottomEdge: { @@ -1720,8 +1861,6 @@ void QQuickTableViewPrivate::unloadEdge(Qt::Edge edge) unloadItem(QPoint(c.key(), row)); loadedRows.remove(row); syncLoadedTableRectFromLoadedTable(); - updateAverageEdgeSize(); - updateContentHeight(); break; } } @@ -1925,8 +2064,17 @@ bool QQuickTableViewPrivate::updateTable() void QQuickTableViewPrivate::fixup(QQuickFlickablePrivate::AxisData &data, qreal minExtent, qreal maxExtent) { - if (scheduledRebuildOptions || rebuildState != RebuildState::Done) + if (inUpdateContentSize) { + // We update the content size dynamically as we load and unload edges. + // Unfortunately, this also triggers a call to this function. The base + // implementation will do things like start a momentum animation or move + // the content view somewhere else, which causes glitches. This can + // especially happen if flicking on one of the syncView children, which triggers + // an update to our content size. In that case, the base implementation don't know + // that the view is being indirectly dragged, and will therefore do strange things as + // it tries to 'fixup' the geometry. So we use a guard to prevent this from happening. return; + } QQuickFlickablePrivate::fixup(data, minExtent, maxExtent); } @@ -2011,13 +2159,11 @@ void QQuickTableViewPrivate::syncWithPendingChanges() Q_Q(QQuickTableView); viewportRect = QRectF(q->contentX(), q->contentY(), q->width(), q->height()); - // Sync rebuild options first, in case we schedule a rebuild from one of the - // other sync calls above. If so, we need to start a new rebuild from the top. - syncRebuildOptions(); - syncModel(); syncDelegate(); syncSyncView(); + + syncRebuildOptions(); } void QQuickTableViewPrivate::syncRebuildOptions() @@ -2107,7 +2253,6 @@ void QQuickTableViewPrivate::syncSyncView() assignedSyncView->d_func()->syncChildren.append(q); scheduledRebuildOptions |= RebuildOption::ViewportOnly; - q->polish(); } syncView = assignedSyncView; @@ -2120,6 +2265,21 @@ void QQuickTableViewPrivate::syncSyncView() q->setColumnSpacing(syncView->columnSpacing()); if (syncVertically) q->setRowSpacing(syncView->rowSpacing()); + + if (syncView && loadedItems.isEmpty() && !tableSize.isEmpty()) { + // When we have a syncView, we can sometimes temporarily end up with no loaded items. + // This can happen if the syncView has a model with more rows or columns than us, in + // which case the viewport can end up in a place where we have no rows or columns to + // show. In that case, check now if the viewport has been flicked back again, and + // that we can rebuild the table with a visible top-left cell. + const auto syncView_d = syncView->d_func(); + if (!syncView_d->loadedItems.isEmpty()) { + if (syncHorizontally && syncView_d->leftColumn() <= tableSize.width() - 1) + scheduledRebuildOptions |= QQuickTableViewPrivate::RebuildOption::ViewportOnly; + else if (syncVertically && syncView_d->topRow() <= tableSize.height() - 1) + scheduledRebuildOptions |= QQuickTableViewPrivate::RebuildOption::ViewportOnly; + } + } } void QQuickTableViewPrivate::connectToModel() @@ -2254,7 +2414,6 @@ void QQuickTableViewPrivate::modelResetCallback() void QQuickTableViewPrivate::scheduleRebuildIfFastFlick() { Q_Q(QQuickTableView); - // If the viewport has moved more than one page vertically or horizontally, we switch // strategy from refilling edges around the current table to instead rebuild the table // from scratch inside the new viewport. This will greatly improve performance when flicking @@ -2345,6 +2504,26 @@ QQuickTableView::QQuickTableView(QQuickTableViewPrivate &dd, QQuickItem *parent) setFlag(QQuickItem::ItemIsFocusScope); } +qreal QQuickTableView::minXExtent() const +{ + return QQuickFlickable::minXExtent() - d_func()->origin.x(); +} + +qreal QQuickTableView::maxXExtent() const +{ + return QQuickFlickable::maxXExtent() - d_func()->endExtent.width(); +} + +qreal QQuickTableView::minYExtent() const +{ + return QQuickFlickable::minYExtent() - d_func()->origin.y(); +} + +qreal QQuickTableView::maxYExtent() const +{ + return QQuickFlickable::maxYExtent() - d_func()->endExtent.height(); +} + int QQuickTableView::rows() const { return d_func()->tableSize.height(); diff --git a/src/quick/items/qquicktableview_p.h b/src/quick/items/qquicktableview_p.h index 3d46221574..3b113efa4f 100644 --- a/src/quick/items/qquicktableview_p.h +++ b/src/quick/items/qquicktableview_p.h @@ -147,6 +147,11 @@ private: Q_DISABLE_COPY(QQuickTableView) Q_DECLARE_PRIVATE(QQuickTableView) + qreal minXExtent() const override; + qreal maxXExtent() const override; + qreal minYExtent() const override; + qreal maxYExtent() const override; + Q_PRIVATE_SLOT(d_func(), void _q_componentFinalized()) }; diff --git a/src/quick/items/qquicktableview_p_p.h b/src/quick/items/qquicktableview_p_p.h index 7f2aee9105..b66ac66dec 100644 --- a/src/quick/items/qquicktableview_p_p.h +++ b/src/quick/items/qquicktableview_p_p.h @@ -251,6 +251,9 @@ public: QRectF loadedTableOuterRect; QRectF loadedTableInnerRect; + QPointF origin = QPointF(0, 0); + QSizeF endExtent = QSizeF(0, 0); + QRectF viewportRect = QRectF(0, 0, -1, -1); QSize tableSize; @@ -272,6 +275,7 @@ public: bool syncHorizontally = false; bool inSetLocalViewportPos = false; bool inSyncViewportPosRecursive = false; + bool inUpdateContentSize = false; QJSValue rowHeightProvider; QJSValue columnWidthProvider; @@ -337,7 +341,6 @@ public: bool updateTableRecursive(); bool updateTable(); - void relayoutTable(); void relayoutTableItems(); void layoutVerticalEdge(Qt::Edge tableEdge); @@ -350,7 +353,7 @@ public: void updateAverageEdgeSize(); void forceLayout(); - void enforceTableAtOrigin(); + void updateExtents(); void syncLoadedTableRectFromLoadedTable(); void syncLoadedTableFromLoadRequest(); diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp index 6d343a91ce..ae849aeb4b 100644 --- a/src/quick/items/qquicktext.cpp +++ b/src/quick/items/qquicktext.cpp @@ -1653,13 +1653,17 @@ void QQuickText::setText(const QString &n) if (d->text == n) return; - d->richText = d->format == RichText; + d->markdownText = d->format == MarkdownText; + d->richText = d->format == RichText || d->markdownText; d->styledText = d->format == StyledText || (d->format == AutoText && Qt::mightBeRichText(n)); d->text = n; if (isComponentComplete()) { if (d->richText) { d->ensureDoc(); - d->extra->doc->setText(n); + if (d->markdownText) + d->extra->doc->setMarkdownText(n); + else + d->extra->doc->setText(n); d->rightToLeftText = d->extra->doc->toPlainText().isRightToLeft(); } else { d->clearFormats(); @@ -2146,7 +2150,8 @@ void QQuickText::setTextFormat(TextFormat format) return; d->format = format; bool wasRich = d->richText; - d->richText = format == RichText; + d->markdownText = format == MarkdownText; + d->richText = format == RichText || d->markdownText; d->styledText = format == StyledText || (format == AutoText && Qt::mightBeRichText(d->text)); if (isComponentComplete()) { @@ -2687,7 +2692,10 @@ void QQuickText::componentComplete() if (d->updateOnComponentComplete) { if (d->richText) { d->ensureDoc(); - d->extra->doc->setText(d->text); + if (d->markdownText) + d->extra->doc->setMarkdownText(d->text); + else + d->extra->doc->setText(d->text); d->rightToLeftText = d->extra->doc->toPlainText().isRightToLeft(); } else { d->rightToLeftText = d->text.isRightToLeft(); diff --git a/src/quick/items/qquicktext_p.h b/src/quick/items/qquicktext_p.h index 1af60051fb..45f387cb12 100644 --- a/src/quick/items/qquicktext_p.h +++ b/src/quick/items/qquicktext_p.h @@ -121,6 +121,7 @@ public: Q_ENUM(TextStyle) enum TextFormat { PlainText = Qt::PlainText, RichText = Qt::RichText, + MarkdownText = Qt::MarkdownText, AutoText = Qt::AutoText, StyledText = 4 }; Q_ENUM(TextFormat) diff --git a/src/quick/items/qquicktext_p_p.h b/src/quick/items/qquicktext_p_p.h index efa45e0958..c01998b100 100644 --- a/src/quick/items/qquicktext_p_p.h +++ b/src/quick/items/qquicktext_p_p.h @@ -161,6 +161,7 @@ public: bool updateOnComponentComplete:1; bool richText:1; bool styledText:1; + bool markdownText:1; bool widthExceeded:1; bool heightExceeded:1; bool internalWidthUpdate:1; diff --git a/src/quick/items/qquicktextcontrol.cpp b/src/quick/items/qquicktextcontrol.cpp index a7a90c9134..caef24293a 100644 --- a/src/quick/items/qquicktextcontrol.cpp +++ b/src/quick/items/qquicktextcontrol.cpp @@ -1297,6 +1297,7 @@ void QQuickTextControlPrivate::inputMethodEvent(QInputMethodEvent *e) || e->preeditString() != cursor.block().layout()->preeditAreaText() || e->replacementLength() > 0; bool forceSelectionChanged = false; + int oldCursorPos = cursor.position(); cursor.beginEditBlock(); if (isGettingInput) { @@ -1365,6 +1366,8 @@ void QQuickTextControlPrivate::inputMethodEvent(QInputMethodEvent *e) QTextCursorPrivate *cursor_d = QTextCursorPrivate::getPrivate(&cursor); if (cursor_d) cursor_d->setX(); + if (cursor.position() != oldCursorPos) + emit q->cursorPositionChanged(); q->updateCursorRectangle(oldPreeditCursor != preeditCursor || forceSelectionChanged || isGettingInput); selectionChanged(forceSelectionChanged); } diff --git a/src/quick/items/qquicktextdocument.cpp b/src/quick/items/qquicktextdocument.cpp index 06ac5804c4..021bbca0f6 100644 --- a/src/quick/items/qquicktextdocument.cpp +++ b/src/quick/items/qquicktextdocument.cpp @@ -237,6 +237,14 @@ void QQuickTextDocumentWithImageResources::setText(const QString &text) #endif } +#if QT_CONFIG(textmarkdownreader) +void QQuickTextDocumentWithImageResources::setMarkdownText(const QString &text) +{ + clearResources(); + QTextDocument::setMarkdown(text); +} +#endif + QSet<QUrl> QQuickTextDocumentWithImageResources::errors; QT_END_NAMESPACE diff --git a/src/quick/items/qquicktextdocument_p.h b/src/quick/items/qquicktextdocument_p.h index 1218b12b89..9c5152d442 100644 --- a/src/quick/items/qquicktextdocument_p.h +++ b/src/quick/items/qquicktextdocument_p.h @@ -73,6 +73,9 @@ public: virtual ~QQuickTextDocumentWithImageResources(); void setText(const QString &); +#if QT_CONFIG(textmarkdownreader) + void setMarkdownText(const QString &); +#endif int resourcesLoading() const { return outstanding; } QSizeF intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format) override; diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp index 6b4b118eb7..b299e7c92f 100644 --- a/src/quick/items/qquicktextedit.cpp +++ b/src/quick/items/qquicktextedit.cpp @@ -2298,7 +2298,6 @@ void QQuickTextEditPrivate::init() qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SIGNAL(cursorPositionChanged())); qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorRectangleChanged()), q, QQuickTextEdit, SLOT(moveCursorDelegate())); qmlobject_connect(control, QQuickTextControl, SIGNAL(linkActivated(QString)), q, QQuickTextEdit, SIGNAL(linkActivated(QString))); - qmlobject_connect(control, QQuickTextControl, SIGNAL(linkHovered(QString)), q, QQuickTextEdit, SIGNAL(linkHovered(QString))); qmlobject_connect(control, QQuickTextControl, SIGNAL(overwriteModeChanged(bool)), q, QQuickTextEdit, SIGNAL(overwriteModeChanged(bool))); qmlobject_connect(control, QQuickTextControl, SIGNAL(textChanged()), q, QQuickTextEdit, SLOT(q_textChanged())); qmlobject_connect(control, QQuickTextControl, SIGNAL(preeditTextChanged()), q, QQuickTextEdit, SIGNAL(preeditTextChanged())); @@ -2310,6 +2309,7 @@ void QQuickTextEditPrivate::init() qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(imagesLoaded()), q, QQuickTextEdit, SLOT(updateSize())); QObject::connect(document, &QQuickTextDocumentWithImageResources::contentsChange, q, &QQuickTextEdit::q_contentsChange); QObject::connect(document->documentLayout(), &QAbstractTextDocumentLayout::updateBlock, q, &QQuickTextEdit::invalidateBlock); + QObject::connect(control, &QQuickTextControl::linkHovered, q, &QQuickTextEdit::q_linkHovered); document->setDefaultFont(font); document->setDocumentMargin(textMargin); @@ -2317,6 +2317,9 @@ void QQuickTextEditPrivate::init() document->setUndoRedoEnabled(true); updateDefaultTextOption(); q->updateSize(); +#if QT_CONFIG(cursor) + q->setCursor(Qt::IBeamCursor); +#endif } void QQuickTextEditPrivate::resetInputMethod() @@ -2584,6 +2587,20 @@ void QQuickTextEdit::updateCursor() } } +void QQuickTextEdit::q_linkHovered(const QString &link) +{ + Q_D(QQuickTextEdit); + emit linkHovered(link); +#if QT_CONFIG(cursor) + if (link.isEmpty()) { + setCursor(d->cursorToRestoreAfterHover); + } else if (cursor().shape() != Qt::PointingHandCursor) { + d->cursorToRestoreAfterHover = cursor().shape(); + setCursor(Qt::PointingHandCursor); + } +#endif +} + void QQuickTextEdit::q_updateAlignment() { Q_D(QQuickTextEdit); diff --git a/src/quick/items/qquicktextedit_p.h b/src/quick/items/qquicktextedit_p.h index 7a847ffeae..259a614d6b 100644 --- a/src/quick/items/qquicktextedit_p.h +++ b/src/quick/items/qquicktextedit_p.h @@ -374,6 +374,7 @@ private Q_SLOTS: void updateWholeDocument(); void invalidateBlock(const QTextBlock &block); void updateCursor(); + void q_linkHovered(const QString &link); void q_updateAlignment(); void updateSize(); void triggerPreprocess(); diff --git a/src/quick/items/qquicktextedit_p_p.h b/src/quick/items/qquicktextedit_p_p.h index 46d3d5ff6b..389ce3175c 100644 --- a/src/quick/items/qquicktextedit_p_p.h +++ b/src/quick/items/qquicktextedit_p_p.h @@ -207,6 +207,7 @@ public: Qt::InputMethodHints inputMethodHints; #endif UpdateType updateType; + Qt::CursorShape cursorToRestoreAfterHover = Qt::IBeamCursor; bool dirty : 1; bool richText : 1; diff --git a/src/quick/items/qquicktextnodeengine.cpp b/src/quick/items/qquicktextnodeengine.cpp index a1b5eb1faf..32478ba375 100644 --- a/src/quick/items/qquicktextnodeengine.cpp +++ b/src/quick/items/qquicktextnodeengine.cpp @@ -1011,6 +1011,17 @@ void QQuickTextNodeEngine::addTextBlock(QTextDocument *textDocument, const QText break; }; + switch (block.blockFormat().marker()) { + case QTextBlockFormat::Checked: + listItemBullet = QChar(0x2612); // Checked checkbox + break; + case QTextBlockFormat::Unchecked: + listItemBullet = QChar(0x2610); // Unchecked checkbox + break; + case QTextBlockFormat::NoMarker: + break; + } + QSizeF size(fontMetrics.horizontalAdvance(listItemBullet), fontMetrics.height()); qreal xoff = fontMetrics.horizontalAdvance(QLatin1Char(' ')); if (block.textDirection() == Qt::LeftToRight) diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index f3a2b07620..5dcd101462 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -64,6 +64,7 @@ #include <QtGui/qpainter.h> #include <QtGui/qevent.h> #include <QtGui/qmatrix4x4.h> +#include <QtGui/qpa/qplatformtheme.h> #include <QtCore/qvarlengtharray.h> #include <QtCore/qabstractanimation.h> #include <QtCore/QLibraryInfo> @@ -632,25 +633,28 @@ static QMouseEvent *touchToMouseEvent(QEvent::Type type, const QTouchEvent::Touc return me; } -bool QQuickWindowPrivate::checkIfDoubleClicked(ulong newPressEventTimestamp) +bool QQuickWindowPrivate::checkIfDoubleTapped(ulong newPressEventTimestamp, QPoint newPressPos) { - bool doubleClicked; + bool doubleClicked = false; + + if (touchMousePressTimestamp > 0) { + QPoint distanceBetweenPresses = newPressPos - touchMousePressPos; + const int doubleTapDistance = QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::TouchDoubleTapDistance).toInt(); + doubleClicked = (qAbs(distanceBetweenPresses.x()) <= doubleTapDistance) && (qAbs(distanceBetweenPresses.y()) <= doubleTapDistance); - if (touchMousePressTimestamp == 0) { - // just initialize the variable - touchMousePressTimestamp = newPressEventTimestamp; - doubleClicked = false; - } else { - ulong timeBetweenPresses = newPressEventTimestamp - touchMousePressTimestamp; - ulong doubleClickInterval = static_cast<ulong>(QGuiApplication::styleHints()-> - mouseDoubleClickInterval()); - doubleClicked = timeBetweenPresses < doubleClickInterval; if (doubleClicked) { - touchMousePressTimestamp = 0; - } else { - touchMousePressTimestamp = newPressEventTimestamp; + ulong timeBetweenPresses = newPressEventTimestamp - touchMousePressTimestamp; + ulong doubleClickInterval = static_cast<ulong>(QGuiApplication::styleHints()-> + mouseDoubleClickInterval()); + doubleClicked = timeBetweenPresses < doubleClickInterval; } } + if (doubleClicked) { + touchMousePressTimestamp = 0; + } else { + touchMousePressTimestamp = newPressEventTimestamp; + touchMousePressPos = newPressPos; + } return doubleClicked; } @@ -707,7 +711,9 @@ bool QQuickWindowPrivate::deliverTouchAsMouse(QQuickItem *item, QQuickPointerEve if (auto pointerEventPoint = pointerEvent->pointById(p.id())) pointerEventPoint->setGrabberItem(item); - if (checkIfDoubleClicked(event->timestamp())) { + if (checkIfDoubleTapped(event->timestamp(), p.screenPos().toPoint())) { + // since we synth the mouse event from from touch, we respect the + // QPlatformTheme::TouchDoubleTapDistance instead of QPlatformTheme::MouseDoubleClickDistance QScopedPointer<QMouseEvent> mouseDoubleClick(touchToMouseEvent(QEvent::MouseButtonDblClick, p, event.data(), item, false)); QCoreApplication::sendEvent(item, mouseDoubleClick.data()); event->setAccepted(mouseDoubleClick->isAccepted()); @@ -722,6 +728,12 @@ bool QQuickWindowPrivate::deliverTouchAsMouse(QQuickItem *item, QQuickPointerEve // Touch point was there before and moved } else if (touchMouseDevice == device && p.id() == touchMouseId) { if (p.state() & Qt::TouchPointMoved) { + if (touchMousePressTimestamp != 0) { + const int doubleTapDistance = QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::TouchDoubleTapDistance).toInt(); + const QPoint moveDelta = p.screenPos().toPoint() - touchMousePressPos; + if (moveDelta.x() >= doubleTapDistance || moveDelta.y() >= doubleTapDistance) + touchMousePressTimestamp = 0; // Got dragged too far, dismiss the double tap + } if (QQuickItem *mouseGrabberItem = q->mouseGrabberItem()) { QScopedPointer<QMouseEvent> me(touchToMouseEvent(QEvent::MouseMove, p, event.data(), mouseGrabberItem, false)); QCoreApplication::sendEvent(item, me.data()); diff --git a/src/quick/items/qquickwindow_p.h b/src/quick/items/qquickwindow_p.h index 5a3807b24f..63760a3b68 100644 --- a/src/quick/items/qquickwindow_p.h +++ b/src/quick/items/qquickwindow_p.h @@ -135,8 +135,9 @@ public: #endif int touchMouseId; QQuickPointerDevice *touchMouseDevice; - bool checkIfDoubleClicked(ulong newPressEventTimestamp); + bool checkIfDoubleTapped(ulong newPressEventTimestamp, QPoint newPressPos); ulong touchMousePressTimestamp; + QPoint touchMousePressPos; // in screen coordiantes void cancelTouchMouseSynthesis(); // Mouse positions are saved in widget coordinates |