summaryrefslogtreecommitdiffstats
path: root/src/quick/items
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2019-05-28 17:59:34 +0200
committerLaszlo Agocs <laszlo.agocs@qt.io>2019-05-28 17:59:40 +0200
commit1f3c157ec6b8be5de1eb89295713c2980fb1a7aa (patch)
treeb85888d73518f6abc7bbde74ab71a27887682f9d /src/quick/items
parent44ca7e31ee9365a72cd17ecd335ec4d0161420a9 (diff)
parent0f5c34f2c6b64bae3429706a6c4211334c689092 (diff)
Merge "Merge remote-tracking branch 'origin/dev' into wip/scenegraphng"wip/scenegraphng
Diffstat (limited to 'src/quick/items')
-rw-r--r--src/quick/items/context2d/qquickcontext2d.cpp14
-rw-r--r--src/quick/items/qquickaccessibleattached.cpp43
-rw-r--r--src/quick/items/qquickaccessibleattached_p.h32
-rw-r--r--src/quick/items/qquickopenglshadereffect.cpp7
-rw-r--r--src/quick/items/qquickopenglshadereffect_p.h2
-rw-r--r--src/quick/items/qquickopenglshadereffectnode.cpp8
-rw-r--r--src/quick/items/qquickopenglshadereffectnode_p.h4
-rw-r--r--src/quick/items/qquickshadereffect.cpp3
-rw-r--r--src/quick/items/qquicktableview.cpp367
-rw-r--r--src/quick/items/qquicktableview_p.h5
-rw-r--r--src/quick/items/qquicktableview_p_p.h7
-rw-r--r--src/quick/items/qquicktext.cpp16
-rw-r--r--src/quick/items/qquicktext_p.h1
-rw-r--r--src/quick/items/qquicktext_p_p.h1
-rw-r--r--src/quick/items/qquicktextcontrol.cpp3
-rw-r--r--src/quick/items/qquicktextdocument.cpp8
-rw-r--r--src/quick/items/qquicktextdocument_p.h3
-rw-r--r--src/quick/items/qquicktextedit.cpp19
-rw-r--r--src/quick/items/qquicktextedit_p.h1
-rw-r--r--src/quick/items/qquicktextedit_p_p.h1
-rw-r--r--src/quick/items/qquicktextnodeengine.cpp11
-rw-r--r--src/quick/items/qquickwindow.cpp42
-rw-r--r--src/quick/items/qquickwindow_p.h3
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 &current
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