From 56f0ebfe860e440dcbba8997f44836debc901119 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Fri, 20 May 2022 09:23:28 +0200 Subject: 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. Pick-to: 6.4 Task-number: QTBUG-76105 Task-number: QTBUG-103484 Change-Id: Ie6ca4dbb450dbc36b3d09fd0df1ae5909aaebca7 Reviewed-by: Qt CI Bot Reviewed-by: Allan Sandfeld Jensen --- src/widgets/widgets/qwidgettextcontrol.cpp | 23 ++++-- .../corelib/kernel/qmimedata/tst_qmimedata.cpp | 11 +++ .../widgets/widgets/qtextedit/tst_qtextedit.cpp | 94 ++++++++++++++++++++++ 3 files changed, 123 insertions(+), 5 deletions(-) diff --git a/src/widgets/widgets/qwidgettextcontrol.cpp b/src/widgets/widgets/qwidgettextcontrol.cpp index b97d0fa3c2..cec18dbe69 100644 --- a/src/widgets/widgets/qwidgettextcontrol.cpp +++ b/src/widgets/widgets/qwidgettextcontrol.cpp @@ -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; diff --git a/tests/auto/corelib/kernel/qmimedata/tst_qmimedata.cpp b/tests/auto/corelib/kernel/qmimedata/tst_qmimedata.cpp index 16d5c5308e..03b07c41ec 100644 --- a/tests/auto/corelib/kernel/qmimedata/tst_qmimedata.cpp +++ b/tests/auto/corelib/kernel/qmimedata/tst_qmimedata.cpp @@ -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()); diff --git a/tests/auto/widgets/widgets/qtextedit/tst_qtextedit.cpp b/tests/auto/widgets/widgets/qtextedit/tst_qtextedit.cpp index dbcc4c32e3..ff5d3ab083 100644 --- a/tests/auto/widgets/widgets/qtextedit/tst_qtextedit.cpp +++ b/tests/auto/widgets/widgets/qtextedit/tst_qtextedit.cpp @@ -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("Hello World"); + 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")); //
  • 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(); @@ -2199,6 +2275,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(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; -- cgit v1.2.3