Support markdown in QTextEditMimeData; fix pasting trailing newlines

- Since 4edcea762d9ce334c4c1a78234c90c118b81da87 the dropsite example
  shows markdown if available; and now it shows that when we do DnD
  of a selection from the richtext example, text/markdown is available.
- If we artificially make html unavailable, copying and pasting between
  widget-based rich text editors uses markdown.

In case markdown writer output contains unnecessary backticks due to
monospace fonts getting used, the workaround from
1ad456c908467212bc30223a69eb7524b64b86e1 is applied.

Task-number: QTBUG-76105
Task-number: QTBUG-103484
Change-Id: Ie6ca4dbb450dbc36b3d09fd0df1ae5909aaebca7
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
(cherry picked from commit 56f0ebfe860e440dcbba8997f44836debc901119)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Shawn Rutledge 2022-05-20 09:23:28 +02:00 committed by Qt Cherry-pick Bot
parent 52d3a42a32
commit 62a7360809
3 changed files with 123 additions and 5 deletions

View File

@ -24,6 +24,7 @@
#endif
#include "qtextdocument.h"
#include "private/qtextdocument_p.h"
#include "private/qtextdocumentfragment_p.h"
#include "qtextlist.h"
#include "private/qwidgettextcontrol_p.h"
#if QT_CONFIG(style_stylesheet)
@ -2697,6 +2698,13 @@ void QWidgetTextControl::insertFromMimeData(const QMimeData *source)
bool hasData = false;
QTextDocumentFragment fragment;
#if QT_CONFIG(textmarkdownreader)
if (source->formats().first() == "text/markdown"_L1) {
auto s = QString::fromUtf8(source->data("text/markdown"_L1));
fragment = QTextDocumentFragment::fromMarkdown(s);
hasData = true;
} else
#endif
#ifndef QT_NO_TEXTHTMLPARSER
if (source->hasFormat("application/x-qrichtext"_L1) && d->acceptRichText) {
// x-qrichtext is always UTF-8 (taken from Qt3 since we don't use it anymore).
@ -2707,16 +2715,15 @@ void QWidgetTextControl::insertFromMimeData(const QMimeData *source)
} else if (source->hasHtml() && d->acceptRichText) {
fragment = QTextDocumentFragment::fromHtml(source->html(), d->doc);
hasData = true;
} else {
QString text = source->text();
}
#endif // QT_NO_TEXTHTMLPARSER
if (!hasData) {
const QString text = source->text();
if (!text.isNull()) {
fragment = QTextDocumentFragment::fromPlainText(text);
hasData = true;
}
}
#else
fragment = QTextDocumentFragment::fromPlainText(source->text());
#endif // QT_NO_TEXTHTMLPARSER
if (hasData)
d->cursor.insertFragment(fragment);
@ -3434,6 +3441,9 @@ QStringList QTextEditMimeData::formats() const
{
if (!fragment.isEmpty())
return QStringList() << u"text/plain"_s << u"text/html"_s
#if QT_CONFIG(textmarkdownwriter)
<< u"text/markdown"_s
#endif
#ifndef QT_NO_TEXTODFWRITER
<< u"application/vnd.oasis.opendocument.text"_s
#endif
@ -3455,6 +3465,9 @@ void QTextEditMimeData::setup() const
#ifndef QT_NO_TEXTHTMLPARSER
that->setData("text/html"_L1, fragment.toHtml().toUtf8());
#endif
#if QT_CONFIG(textmarkdownwriter)
that->setData("text/markdown"_L1, fragment.toMarkdown().toUtf8());
#endif
#ifndef QT_NO_TEXTODFWRITER
{
QBuffer buffer;

View File

@ -72,12 +72,19 @@ void tst_QMimeData::data() const
mimeData.setData("text/plain", "pirates");
QCOMPARE(mimeData.data("text/plain"), QByteArray("pirates"));
QCOMPARE(mimeData.data("text/html").length(), 0);
QCOMPARE(mimeData.data("text/markdown").length(), 0);
// html time
mimeData.setData("text/html", "ninjas");
QCOMPARE(mimeData.data("text/html"), QByteArray("ninjas"));
QCOMPARE(mimeData.data("text/plain"), QByteArray("pirates")); // make sure text not damaged
QCOMPARE(mimeData.data("text/html"), mimeData.html().toLatin1());
// markdown time
mimeData.setData("text/markdown", "vikings");
QCOMPARE(mimeData.data("text/markdown"), QByteArray("vikings"));
QCOMPARE(mimeData.data("text/html"), QByteArray("ninjas"));
QCOMPARE(mimeData.data("text/plain"), QByteArray("pirates"));
}
void tst_QMimeData::formats() const
@ -92,6 +99,10 @@ void tst_QMimeData::formats() const
mimeData.setData("text/html", "ninjas");
QCOMPARE(mimeData.formats(), QStringList() << "text/plain" << "text/html");
// set markdown, verify
mimeData.setData("text/markdown", "vikings");
QCOMPARE(mimeData.formats(), QStringList() << "text/plain" << "text/html" << "text/markdown");
// clear, verify
mimeData.clear();
QCOMPARE(mimeData.formats(), QStringList());

View File

@ -114,7 +114,13 @@ private slots:
void moveCursor();
#ifndef QT_NO_CLIPBOARD
void mimeDataReimplementations();
#ifndef QT_NO_TEXTHTMLPARSER
void mimeTypesAvailableFromRichText();
#endif
#if QT_CONFIG(textmarkdownreader)
void mimeTypesAvailableFromMarkdown();
#endif
#endif // QT_NO_CLIPBOARD
void ctrlEnterShouldInsertLineSeparator_NOT();
void shiftEnterShouldInsertLineSeparator();
void selectWordsFromStringsContainingSeparators_data();
@ -150,6 +156,7 @@ private slots:
void setDocumentPreservesPalette();
#endif
void pasteFromQt3RichText();
void pasteFromMarkdown();
void noWrapBackgrounds();
void preserveCharFormatAfterUnchangingSetPosition();
void twoSameInputMethodEvents();
@ -193,6 +200,7 @@ private:
void createSelection();
int blockCount() const;
void compareWidgetAndImage(QTextEdit &widget, const QString &imageFileName);
bool isMainFontFixed();
QTextEdit *ed;
qreal rootFrameMargin;
@ -1480,7 +1488,63 @@ void tst_QTextEdit::mimeDataReimplementations()
QCOMPARE(ed.insertCallCount, 1);
#endif
}
#ifndef QT_NO_TEXTHTMLPARSER
void tst_QTextEdit::mimeTypesAvailableFromRichText()
{
MyTextEdit ed;
ed.setHtml("<i>Hello <b>World</b></i>");
ed.selectAll();
ed.copy();
const auto *mimeData = QApplication::clipboard()->mimeData();
qCDebug(lcTests) << "available mime types" << mimeData->formats();
QVERIFY(mimeData->formats().contains("text/plain"));
#if QT_CONFIG(textmarkdownwriter)
QVERIFY(mimeData->formats().contains("text/markdown"));
const QByteArray expectedMarkdown = "*Hello **World***\n\n";
if (mimeData->data("text/markdown") != expectedMarkdown && isMainFontFixed())
QEXPECT_FAIL("", "fixed-pitch main font (QTBUG-103484)", Continue);
QCOMPARE(mimeData->data("text/markdown"), expectedMarkdown);
#endif
#ifndef QT_NO_TEXTHTMLPARSER
QVERIFY(mimeData->formats().contains("text/html"));
QVERIFY(mimeData->hasHtml());
#endif
#ifndef QT_NO_TEXTODFWRITER
QVERIFY(mimeData->formats().contains("application/vnd.oasis.opendocument.text"));
#endif
}
#endif // QT_NO_TEXTHTMLPARSER
#if QT_CONFIG(textmarkdownreader)
void tst_QTextEdit::mimeTypesAvailableFromMarkdown()
{
MyTextEdit ed;
const QString md("# TODO\n\n- [x] Fix bugs\n- [ ] Have a beer\n");
ed.setMarkdown(md);
ed.selectAll();
ed.copy();
const auto *mimeData = QApplication::clipboard()->mimeData();
qCDebug(lcTests) << "available mime types" << mimeData->formats();
QVERIFY(mimeData->formats().contains("text/plain"));
#if QT_CONFIG(textmarkdownwriter)
QVERIFY(mimeData->formats().contains("text/markdown"));
if (mimeData->data("text/markdown") != md && isMainFontFixed())
QEXPECT_FAIL("", "fixed-pitch main font (QTBUG-103484)", Continue);
QCOMPARE(mimeData->data("text/markdown"), md);
#endif
#ifndef QT_NO_TEXTHTMLPARSER
QVERIFY(mimeData->formats().contains("text/html"));
QVERIFY(mimeData->hasHtml());
QVERIFY(mimeData->html().contains("checked")); // <li class=\"checked\" ...
#endif
#ifndef QT_NO_TEXTODFWRITER
QVERIFY(mimeData->formats().contains("application/vnd.oasis.opendocument.text"));
#endif
}
#endif // textmarkdownreader
#endif // QT_NO_CLIPBOARD
void tst_QTextEdit::ctrlEnterShouldInsertLineSeparator_NOT()
{
@ -2133,6 +2197,18 @@ void tst_QTextEdit::compareWidgetAndImage(QTextEdit &widget, const QString &imag
}
}
bool tst_QTextEdit::isMainFontFixed()
{
bool ret = QFontInfo(QGuiApplication::font()).fixedPitch();
if (ret) {
qCWarning(lcTests) << "QFontDatabase::GeneralFont is monospaced: markdown writing is likely to use too many backticks";
qCWarning(lcTests) << "system fonts: fixed" << QFontDatabase::systemFont(QFontDatabase::FixedFont)
<< "fixed?" << QFontInfo(QFontDatabase::systemFont(QFontDatabase::FixedFont)).fixedPitch()
<< "general" << QFontDatabase::systemFont(QFontDatabase::GeneralFont);
}
return ret;
}
void tst_QTextEdit::cursorRect()
{
ed->show();
@ -2200,6 +2276,24 @@ void tst_QTextEdit::pasteFromQt3RichText()
QCOMPARE(ed->toPlainText(), QString::fromLatin1(" QTextEdit is an "));
}
void tst_QTextEdit::pasteFromMarkdown()
{
QByteArray richtext("*This* text is **rich**");
QMimeData mimeData;
mimeData.setData("text/markdown", richtext);
static_cast<PublicTextEdit *>(ed)->publicInsertFromMimeData(&mimeData);
QCOMPARE(ed->toPlainText(), "This text is rich");
#if QT_CONFIG(textmarkdownwriter)
const auto expectedMarkdown = QString::fromLatin1(richtext + "\n\n");
if (ed->toMarkdown() != expectedMarkdown && isMainFontFixed())
QEXPECT_FAIL("", "fixed-pitch main font (QTBUG-103484)", Continue);
QCOMPARE(ed->toMarkdown(), expectedMarkdown);
#endif
}
void tst_QTextEdit::noWrapBackgrounds()
{
QWidget topLevel;