Support copy-pasting foreground images within same document

When using a texture for foreground color in text and copy-pasting
the text inside the same QTextEdit, the formatting would disappear.

Fixing this in a general way would require implementing some
other carrier format in the mime data than HTML, such as e.g ODF,
but it can quite easily be fixed for the case where the data
is pasted in the same document, or even different documents
as long as they have a reference to the image in the formats.

[ChangeLog][QtWidgets][QTextEdit] Added support for copy-pasting
foreground brushes with textures within same document.

Task-number: QTBUG-75931
Change-Id: I8b39dce289c64eea39e25cb8eb207e2534bcd2eb
Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
This commit is contained in:
Eskil Abrahamsen Blomfeldt 2019-06-18 16:08:06 +02:00
parent cefaa2ff4c
commit 6492541df3
7 changed files with 90 additions and 3 deletions

View File

@ -67,6 +67,7 @@ struct QCssKnownValue
static const QCssKnownValue properties[NumProperties - 1] = {
{ "-qt-background-role", QtBackgroundRole },
{ "-qt-block-indent", QtBlockIndent },
{ "-qt-fg-texture-cachekey", QtForegroundTextureCacheKey },
{ "-qt-line-height-type", QtLineHeightType },
{ "-qt-list-indent", QtListIndent },
{ "-qt-list-number-prefix", QtListNumberPrefix },

View File

@ -196,6 +196,7 @@ enum Property {
LineHeight,
QtLineHeightType,
FontKerning,
QtForegroundTextureCacheKey,
NumProperties
};

View File

@ -2470,9 +2470,19 @@ bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
if (format.foreground() != defaultCharFormat.foreground()
&& format.foreground().style() != Qt::NoBrush) {
html += QLatin1String(" color:");
html += colorValue(format.foreground().color());
html += QLatin1Char(';');
QBrush brush = format.foreground();
if (brush.style() == Qt::TexturePattern) {
const bool isPixmap = qHasPixmapTexture(brush);
const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey();
html += QLatin1String(" -qt-fg-texture-cachekey:");
html += QString::number(cacheKey);
html += QLatin1String(";");
} else {
html += QLatin1String(" color:");
html += colorValue(brush.color());
html += QLatin1Char(';');
}
attributesEmitted = true;
}

View File

@ -357,6 +357,7 @@ public:
void mergeCachedResources(const QTextDocumentPrivate *priv);
friend struct QTextHtmlParserNode;
friend class QTextHtmlExporter;
friend class QTextCursor;
};

View File

@ -1335,6 +1335,17 @@ void QTextHtmlParserNode::applyCssDeclarations(const QVector<QCss::Declaration>
default: break;
}
break;
case QCss::QtForegroundTextureCacheKey:
{
if (resourceProvider != nullptr && resourceProvider->docHandle() != nullptr) {
bool ok;
qint64 searchKey = decl.d->values.first().variant.toLongLong(&ok);
if (ok)
applyForegroundImage(searchKey, resourceProvider);
}
break;
}
default: break;
}
}
@ -1367,6 +1378,37 @@ void QTextHtmlParserNode::applyCssDeclarations(const QVector<QCss::Declaration>
#endif // QT_NO_CSSPARSER
void QTextHtmlParserNode::applyForegroundImage(qint64 searchKey, const QTextDocument *resourceProvider)
{
QTextDocumentPrivate *priv = resourceProvider->docHandle();
for (int i = 0; i < priv->formats.numFormats(); ++i) {
QTextCharFormat format = priv->formats.charFormat(i);
if (format.isValid()) {
QBrush brush = format.foreground();
if (brush.style() == Qt::TexturePattern) {
const bool isPixmap = qHasPixmapTexture(brush);
if (isPixmap && QCoreApplication::instance()->thread() != QThread::currentThread()) {
qWarning("Can't apply QPixmap outside of GUI thread");
return;
}
const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey();
if (cacheKey == searchKey) {
QBrush b;
if (isPixmap)
b.setTexture(brush.texture());
else
b.setTextureImage(brush.textureImage());
b.setStyle(Qt::TexturePattern);
charFormat.setForeground(b);
}
}
}
}
}
void QTextHtmlParserNode::applyBackgroundImage(const QString &url, const QTextDocument *resourceProvider)
{
if (!url.isEmpty() && resourceProvider) {

View File

@ -251,6 +251,7 @@ struct QTextHtmlParserNode {
void setListStyle(const QVector<QCss::Value> &cssValues);
#endif
void applyForegroundImage(qint64 cacheKey, const QTextDocument *resourceProvider);
void applyBackgroundImage(const QString &url, const QTextDocument *resourceProvider);
bool hasOnlyWhitespace() const;

View File

@ -160,6 +160,7 @@ private slots:
void selectionChanged();
#ifndef QT_NO_CLIPBOARD
void copyPasteBackgroundImage();
void copyPasteForegroundImage();
#endif
void setText();
void cursorRect();
@ -1904,6 +1905,36 @@ void tst_QTextEdit::copyPasteBackgroundImage()
ba.texture().cacheKey() == bb.texture().cacheKey());
QFile::remove(QLatin1String("foo.png"));
}
void tst_QTextEdit::copyPasteForegroundImage()
{
ed->clear();
QPixmap pix(20, 20);
pix.fill(Qt::blue);
QTextCharFormat fmt;
{
QBrush textureBrush;
{
textureBrush.setTexture(pix);
}
textureBrush.setStyle(Qt::TexturePattern);
fmt.setForeground(textureBrush);
}
ed->textCursor().insertText("Foobar", fmt);
ed->moveCursor(QTextCursor::Start);
ed->moveCursor(QTextCursor::End, QTextCursor::KeepAnchor);
ed->copy();
ed->clear();
ed->paste();
QBrush brush = ed->textCursor().charFormat().foreground();
QCOMPARE(brush.style(), Qt::TexturePattern);
QCOMPARE(brush.texture().cacheKey(), pix.cacheKey());
}
#endif
void tst_QTextEdit::setText()