Use CSS classes on html list items for checkbox support

If we replace the bullet character with a UC checkbox character, it
looks ok in a browser, and the HTML parser can recover the BlockMarker
attribute from the css class.

[ChangeLog][QtGui][Text] Checkbox list items can now be read and written
in both HTML and Markdown, including conversions.

Task-number: QTBUG-103714
Change-Id: Ic6b74512075cd4ac16d6f80fdf55b221447491a9
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
(cherry picked from commit eee9d252028c4b3b743c77a406cd6939eda3962f)
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Shawn Rutledge 2022-05-20 23:49:04 +02:00
parent 91cb059e3d
commit e58a29bdae
4 changed files with 53 additions and 15 deletions

View File

@ -2372,6 +2372,8 @@ QString QTextHtmlExporter::toHtml(ExportMode mode)
html += "<style type=\"text/css\">\n"_L1;
html += "p, li { white-space: pre-wrap; }\n"_L1;
html += "hr { height: 1px; border-width: 0; }\n"_L1;
html += "li.unchecked::marker { content: \"\\2610\"; }\n"_L1;
html += "li.checked::marker { content: \"\\2612\"; }\n"_L1;
html += "</style>"_L1;
html += "</head><body"_L1;
@ -3038,7 +3040,7 @@ void QTextHtmlExporter::emitBlock(const QTextBlock &block)
html += " style=\""_L1;
html += styleString;
html += "\">"_L1;
html += "\">\n"_L1;
}
html += "<li"_L1;
@ -3051,6 +3053,18 @@ void QTextHtmlExporter::emitBlock(const QTextBlock &block)
defaultCharFormat.merge(block.charFormat());
}
if (block.blockFormat().hasProperty(QTextFormat::BlockMarker)) {
switch (block.blockFormat().marker()) {
case QTextBlockFormat::MarkerType::Checked:
html += " class=\"checked\""_L1;
break;
case QTextBlockFormat::MarkerType::Unchecked:
html += " class=\"unchecked\""_L1;
break;
case QTextBlockFormat::MarkerType::NoMarker:
break;
}
}
}
const QTextBlockFormat blockFormat = block.blockFormat();

View File

@ -1636,6 +1636,14 @@ void QTextHtmlParser::applyAttributes(const QStringList &attributes)
}
}
break;
case Html_li:
if (key == "class"_L1) {
if (value == "unchecked"_L1)
node->blockFormat.setMarker(QTextBlockFormat::MarkerType::Unchecked);
else if (value == "checked"_L1)
node->blockFormat.setMarker(QTextBlockFormat::MarkerType::Checked);
}
break;
case Html_a:
if (key == "href"_L1)
node->charFormat.setAnchorHref(value);

View File

@ -274,6 +274,8 @@ void tst_QTextDocument::init()
"<html><head><meta name=\"qrichtext\" content=\"1\" /><meta charset=\"utf-8\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"hr { height: 1px; border-width: 0; }\n"
"li.unchecked::marker { content: \"\\2610\"; }\n"
"li.checked::marker { content: \"\\2612\"; }\n"
"</style></head>"
"<body style=\" font-family:'%1'; font-size:%2; font-weight:%3; font-style:%4;\">\n");
htmlHead = htmlHead.arg(defaultFont.family())
@ -1371,7 +1373,7 @@ void tst_QTextDocument::toHtml_data()
QTest::newRow("lists") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li DEFAULTBLOCKSTYLE>Blubb</li>\n<li DEFAULTBLOCKSTYLE>Blah</li></ul>");
QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\">\n<li DEFAULTBLOCKSTYLE>Blubb</li>\n<li DEFAULTBLOCKSTYLE>Blah</li></ul>");
}
{
@ -1394,7 +1396,7 @@ void tst_QTextDocument::toHtml_data()
QTest::newRow("charfmt-for-list-item") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li DEFAULTBLOCKSTYLE>Blubb</li>\n<li style=\" color:#0000ff;\" DEFAULTBLOCKSTYLE><span style=\" color:#ff0000;\">Blah</span></li></ul>");
QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\">\n<li DEFAULTBLOCKSTYLE>Blubb</li>\n<li style=\" color:#0000ff;\" DEFAULTBLOCKSTYLE><span style=\" color:#ff0000;\">Blah</span></li></ul>");
}
{
@ -1424,7 +1426,7 @@ void tst_QTextDocument::toHtml_data()
QTest::newRow("list-indent") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 4;\"><li DEFAULTBLOCKSTYLE>Blah</li></ul>");
QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 4;\">\n<li DEFAULTBLOCKSTYLE>Blah</li></ul>");
}
{
@ -1712,7 +1714,7 @@ void tst_QTextDocument::toHtml_data()
QTest::newRow("list-ul-margin") << QTextDocumentFragment(&doc)
<< QString("EMPTYBLOCK") +
QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li DEFAULTBLOCKSTYLE>Blah</li></ul>");
QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\">\n<li DEFAULTBLOCKSTYLE>Blah</li></ul>");
}
{
CREATE_DOC_AND_CURSOR();
@ -1722,12 +1724,12 @@ void tst_QTextDocument::toHtml_data()
cursor.insertHtml(listHtml);
QTest::newRow("nested-lists-one") << QTextDocumentFragment(&doc)
<< QString("<ul DEFAULTULSTYLE 1;\"><li style=\" margin-top:12px; margin-bottom:0px; "
<< QString("<ul DEFAULTULSTYLE 1;\">\n<li style=\" margin-top:12px; margin-bottom:0px; "
"margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">"
"item-1</li>\n<li DEFAULTBLOCKSTYLE>item-2\n<ul DEFAULTULSTYLE 2;\"><li "
"item-1</li>\n<li DEFAULTBLOCKSTYLE>item-2\n<ul DEFAULTULSTYLE 2;\">\n<li "
"DEFAULTBLOCKSTYLE>item-2.1</li>\n<li DEFAULTBLOCKSTYLE>item-2.2\n<ul "
"DEFAULTULSTYLE 3;\"><li DEFAULTBLOCKSTYLE>item-2.2.1</li></ul></li>\n"
"<li DEFAULTBLOCKSTYLE>item-2.3\n<ul DEFAULTULSTYLE 3;\"><li DEFAULTBLOCKSTYLE>"
"DEFAULTULSTYLE 3;\">\n<li DEFAULTBLOCKSTYLE>item-2.2.1</li></ul></li>\n"
"<li DEFAULTBLOCKSTYLE>item-2.3\n<ul DEFAULTULSTYLE 3;\">\n<li DEFAULTBLOCKSTYLE>"
"item-2.3.1</li></ul></li></ul></li>\n<li DEFAULTLASTLISTYLE>item-3</li></ul>");
}
{
@ -1736,9 +1738,9 @@ void tst_QTextDocument::toHtml_data()
cursor.insertHtml(listHtml);
QTest::newRow("nested-lists-two") << QTextDocumentFragment(&doc)
<< QString("<ul DEFAULTULSTYLE 1;\"><li style=\" margin-top:12px; margin-bottom:0px; "
<< QString("<ul DEFAULTULSTYLE 1;\">\n<li style=\" margin-top:12px; margin-bottom:0px; "
"margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">"
"item-1</li>\n<li DEFAULTLASTLISTYLE>item-2\n<ul DEFAULTULSTYLE 2;\"><li "
"item-1</li>\n<li DEFAULTLASTLISTYLE>item-2\n<ul DEFAULTULSTYLE 2;\">\n<li "
"DEFAULTBLOCKSTYLE>item-2.1</li></ul></li></ul>");
}
{
@ -1748,9 +1750,9 @@ void tst_QTextDocument::toHtml_data()
cursor.insertHtml(listHtml);
QTest::newRow("nested-lists-three") << QTextDocumentFragment(&doc)
<< QString("<ul DEFAULTULSTYLE 1;\"><li style=\" margin-top:12px; margin-bottom:0px; "
<< QString("<ul DEFAULTULSTYLE 1;\">\n<li style=\" margin-top:12px; margin-bottom:0px; "
"margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">"
"item-1</li>\n<li DEFAULTLASTLISTYLE>item-2\n<ul DEFAULTULSTYLE 2;\"><li "
"item-1</li>\n<li DEFAULTLASTLISTYLE>item-2\n<ul DEFAULTULSTYLE 2;\">\n<li "
"DEFAULTBLOCKSTYLE>item-2.1</li>\n<li DEFAULTBLOCKSTYLE>item-2.2</li></ul>"
"</li></ul>");
}
@ -1761,12 +1763,23 @@ void tst_QTextDocument::toHtml_data()
cursor.insertHtml(listHtml);
QTest::newRow("not-nested-list") << QTextDocumentFragment(&doc)
<< QString("<ul DEFAULTULSTYLE 1;\"><li style=\" margin-top:12px; margin-bottom:0px; "
<< QString("<ul DEFAULTULSTYLE 1;\">\n<li style=\" margin-top:12px; margin-bottom:0px; "
"margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">"
"item-1.1</li>\n<li DEFAULTBLOCKSTYLE>item-1.2</li></ul>\n<ul DEFAULTULSTYLE 1;\">"
"item-1.1</li>\n<li DEFAULTBLOCKSTYLE>item-1.2</li></ul>\n<ul DEFAULTULSTYLE 1;\">\n"
"<li style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; "
"margin-right:0px; -qt-block-indent:0; text-indent:0px;\">item-2.1</li></ul>");
}
{
CREATE_DOC_AND_CURSOR();
const QString listHtml = "<ul><li>bullet</li><li class=\"unchecked\">unchecked item</li><li class=\"checked\">checked item</li></ul>";
cursor.insertHtml(listHtml);
QTest::newRow("list with and without checkboxes") << QTextDocumentFragment(&doc)
<< QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\">\n"
"<li style=\" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">bullet</li>\n"
"<li class=\"unchecked\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">unchecked item</li>\n"
"<li class=\"checked\" style=\" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">checked item</li></ul>");
}
}
void tst_QTextDocument::toHtml()

View File

@ -502,6 +502,9 @@ void tst_QTextMarkdownWriter::fromHtml_data()
QTest::newRow("preformats with embedded backticks") <<
"<pre>none `one` ``two``</pre>plain<pre>```three``` ````four````</pre>plain" <<
"```\nnone `one` ``two``\n\n```\nplain\n\n```\n```three``` ````four````\n\n```\nplain\n\n";
QTest::newRow("list items with and without checkboxes") <<
"<ul><li>bullet</li><li class=\"unchecked\">unchecked item</li><li class=\"checked\">checked item</li></ul>" <<
"- bullet\n- [ ] unchecked item\n- [x] checked item\n";
}
void tst_QTextMarkdownWriter::fromHtml()