diff options
-rw-r--r-- | src/widgets/widgets/qwidgettextcontrol.cpp | 45 | ||||
-rw-r--r-- | src/widgets/widgets/qwidgettextcontrol_p_p.h | 1 | ||||
-rw-r--r-- | tests/auto/widgets/widgets/qtextedit/tst_qtextedit.cpp | 94 |
3 files changed, 139 insertions, 1 deletions
diff --git a/src/widgets/widgets/qwidgettextcontrol.cpp b/src/widgets/widgets/qwidgettextcontrol.cpp index 76029714d5..8c849ddbae 100644 --- a/src/widgets/widgets/qwidgettextcontrol.cpp +++ b/src/widgets/widgets/qwidgettextcontrol.cpp @@ -1299,7 +1299,7 @@ void QWidgetTextControlPrivate::keyPressEvent(QKeyEvent *e) } #ifndef QT_NO_SHORTCUT else if (e == QKeySequence::InsertParagraphSeparator) { - cursor.insertBlock(); + insertParagraphSeparator(); e->accept(); goto accept; } else if (e == QKeySequence::InsertLineSeparator) { @@ -3198,6 +3198,49 @@ QString QWidgetTextControl::toMarkdown(QTextDocument::MarkdownFeatures features) } #endif +void QWidgetTextControlPrivate::insertParagraphSeparator() +{ + // clear blockFormat properties that the user is unlikely to want duplicated: + // - don't insert <hr/> automatically + // - the next paragraph after a heading should be a normal paragraph + // - remove the bottom margin from the last list item before appending + // - the next checklist item after a checked item should be unchecked + auto blockFmt = cursor.blockFormat(); + auto charFmt = cursor.charFormat(); + blockFmt.clearProperty(QTextFormat::BlockTrailingHorizontalRulerWidth); + if (blockFmt.hasProperty(QTextFormat::HeadingLevel)) { + blockFmt.clearProperty(QTextFormat::HeadingLevel); + charFmt = QTextCharFormat(); + } + if (cursor.currentList()) { + auto existingFmt = cursor.blockFormat(); + existingFmt.clearProperty(QTextBlockFormat::BlockBottomMargin); + cursor.setBlockFormat(existingFmt); + if (blockFmt.marker() == QTextBlockFormat::MarkerType::Checked) + blockFmt.setMarker(QTextBlockFormat::MarkerType::Unchecked); + } + + // After a blank line, reset block and char formats. I.e. you can end a list, + // block quote, etc. by hitting enter twice, and get back to normal paragraph style. + if (cursor.block().text().isEmpty() && + !cursor.blockFormat().hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth) && + !cursor.blockFormat().hasProperty(QTextFormat::BlockCodeLanguage)) { + blockFmt = QTextBlockFormat(); + const bool blockFmtChanged = (cursor.blockFormat() != blockFmt); + charFmt = QTextCharFormat(); + cursor.setBlockFormat(blockFmt); + cursor.setCharFormat(charFmt); + // If the user hit enter twice just to get back to default format, + // don't actually insert a new block. But if the user then hits enter + // yet again, the block format will not change, so we will insert a block. + // This is what many word processors do. + if (blockFmtChanged) + return; + } + + cursor.insertBlock(blockFmt, charFmt); +} + void QWidgetTextControlPrivate::append(const QString &text, Qt::TextFormat format) { QTextCursor tmp(doc); diff --git a/src/widgets/widgets/qwidgettextcontrol_p_p.h b/src/widgets/widgets/qwidgettextcontrol_p_p.h index a7a19e748d..c94f859e6f 100644 --- a/src/widgets/widgets/qwidgettextcontrol_p_p.h +++ b/src/widgets/widgets/qwidgettextcontrol_p_p.h @@ -179,6 +179,7 @@ public: bool isPreediting() const; void commitPreedit(); + void insertParagraphSeparator(); void append(const QString &text, Qt::TextFormat format = Qt::AutoText); QTextDocument *doc; diff --git a/tests/auto/widgets/widgets/qtextedit/tst_qtextedit.cpp b/tests/auto/widgets/widgets/qtextedit/tst_qtextedit.cpp index 8d5716c129..9126b87472 100644 --- a/tests/auto/widgets/widgets/qtextedit/tst_qtextedit.cpp +++ b/tests/auto/widgets/widgets/qtextedit/tst_qtextedit.cpp @@ -58,6 +58,8 @@ #include "../../../shared/platforminputcontext.h" #include <private/qinputmethod_p.h> +Q_LOGGING_CATEGORY(lcTests, "qt.widgets.tests") + //Used in copyAvailable typedef QPair<Qt::Key, Qt::KeyboardModifier> keyPairType; typedef QList<keyPairType> pairListType; @@ -209,6 +211,9 @@ private slots: void preeditCharFormat_data(); void preeditCharFormat(); + void nextFormatAfterEnterPressed_data(); + void nextFormatAfterEnterPressed(); + private: void createSelection(); int blockCount() const; @@ -2895,5 +2900,94 @@ void tst_QTextEdit::preeditCharFormat() delete w; } +void tst_QTextEdit::nextFormatAfterEnterPressed_data() +{ + typedef QMap<int, QVariant> pmap; + QTest::addColumn<QString>("html"); + QTest::addColumn<int>("enterKeyCount"); + QTest::addColumn<pmap>("expectedPrevBlockProps"); + QTest::addColumn<pmap>("expectedPrevCharProps"); + QTest::addColumn<pmap>("expectedNewBlockProps"); + QTest::addColumn<pmap>("expectedNewCharProps"); + + // the BlockBottomMargin on "two" will be removed: property() returns invalid QVariant + QTest::newRow("bottom margin after ordered list") << "<ol><li>one</li><li>two</li></ol>" << 1 + << pmap{{QTextFormat::BlockBottomMargin, {}}} << pmap{} + << pmap{{QTextFormat::BlockBottomMargin, 12}} << pmap{}; + QTest::newRow("double enter after list: default format") << "<ol><li>one</li><li>two</li></ol>" << 2 + << pmap{{QTextFormat::BlockBottomMargin, {}}} << pmap{} + << pmap{} << pmap{}; + QTest::newRow("continue block quote") << "<blockquote>I'll be back</blockquote>" << 1 + << pmap{{QTextFormat::BlockLeftMargin, 40}} << pmap{} + << pmap{{QTextFormat::BlockLeftMargin, 40}} << pmap{}; + QTest::newRow("double enter after block quote") << "<blockquote>I'll be back</blockquote>" << 2 + << pmap{{QTextFormat::BlockLeftMargin, 40}} << pmap{} + << pmap{{QTextFormat::BlockLeftMargin, {}}} << pmap{}; + QTest::newRow("bottom margin after bullet list") << "<ul><li>one</li><li>two</li></ul>" << 1 + << pmap{{QTextFormat::BlockBottomMargin, {}}} << pmap{} + << pmap{{QTextFormat::BlockBottomMargin, 12}} << pmap{}; + QTest::newRow("paragraph after heading") << "<h1>so big!</h1>" << 1 + << pmap{{QTextFormat::HeadingLevel, 1}} << pmap{} + << pmap{{QTextFormat::HeadingLevel, {}}} << pmap{}; + QTest::newRow("paragraph after hrule") << "<p style='font-size:18px;'>blah blah<hr/></p>" << 1 + << pmap{} << pmap{} + << pmap{{QTextFormat::BlockTrailingHorizontalRulerWidth, {}}} << pmap{}; +} + +void tst_QTextEdit::nextFormatAfterEnterPressed() +{ + typedef QMap<int, QVariant> pmap; + QFETCH(QString, html); + QFETCH(int, enterKeyCount); + QFETCH(pmap, expectedPrevBlockProps); + QFETCH(pmap, expectedPrevCharProps); + QFETCH(pmap, expectedNewBlockProps); + QFETCH(pmap, expectedNewCharProps); + + ed->setHtml(html); + QTextCursor cursor = ed->textCursor(); + cursor.movePosition(QTextCursor::End); + ed->setTextCursor(cursor); + + if (lcTests().isDebugEnabled()) { + ed->show(); + QTest::qWait(500); + } + + for (int i = 0; i < enterKeyCount; ++i) + QTest::keyClick(ed, Qt::Key_Enter); + QTest::keyClicks(ed, "foo"); + + if (lcTests().isDebugEnabled()) { + // visually see what happened when debug is enabled + QTest::qWait(500); + qCDebug(lcTests) << "new block" << Qt::hex << ed->textCursor().blockFormat().properties(); + qCDebug(lcTests) << "new char" << Qt::hex << ed->textCursor().charFormat().properties(); + } + + // if expectedNewBlockProps is empty, we expect the current block format to be the default format + if (expectedNewBlockProps.isEmpty()) + QCOMPARE(ed->textCursor().blockFormat(), QTextBlockFormat()); + // otherwise we expect to find certain property values in the current block format + else for (auto it = expectedNewBlockProps.constBegin(); it != expectedNewBlockProps.constEnd(); ++it) + QCOMPARE(ed->textCursor().blockFormat().property(it.key()), it.value()); + + // if expectedNewCharProps is empty, we expect the current char format to be the default format + if (expectedNewCharProps.isEmpty()) + QCOMPARE(ed->textCursor().charFormat(), QTextCharFormat()); + // otherwise we expect to find certain property values in the current char format + else for (auto it = expectedNewCharProps.constBegin(); it != expectedNewCharProps.constEnd(); ++it) + QCOMPARE(ed->textCursor().charFormat().property(it.key()), it.value()); + + // check the cases where QWidgetTextControlPrivate::insertParagraphSeparator() should modify + // the previous block's block format and/or char format + auto prevBlockCursor = ed->textCursor(); + prevBlockCursor.movePosition(QTextCursor::PreviousBlock); + for (auto it = expectedPrevBlockProps.constBegin(); it != expectedPrevBlockProps.constEnd(); ++it) + QCOMPARE(prevBlockCursor.blockFormat().property(it.key()), it.value()); + for (auto it = expectedPrevCharProps.constBegin(); it != expectedPrevCharProps.constEnd(); ++it) + QCOMPARE(prevBlockCursor.charFormat().property(it.key()), it.value()); +} + QTEST_MAIN(tst_QTextEdit) #include "tst_qtextedit.moc" |