180 lines
5.1 KiB
C++
180 lines
5.1 KiB
C++
#include "richtexteditor.h"
|
|
#include "richtexteditortoolbar.h"
|
|
|
|
#include <QXmlStreamReader>
|
|
#include <QXmlStreamWriter>
|
|
#include <QStringView>
|
|
#include <QStringLiteral>
|
|
|
|
#include <algorithm>
|
|
|
|
const bool simplifyRichTextDefault = true;
|
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
// Richtext simplification filter helpers: Elements to be discarded
|
|
static inline bool filterElement(QStringView name)
|
|
{
|
|
return name != "meta"_L1 && name != "style"_L1;
|
|
}
|
|
|
|
// Richtext simplification filter helpers: Filter attributes of elements
|
|
static inline void filterAttributes(QStringView name,
|
|
QXmlStreamAttributes *atts,
|
|
bool *paragraphAlignmentFound)
|
|
{
|
|
if (atts->isEmpty())
|
|
return;
|
|
|
|
// No style attributes for <body>
|
|
if (name == "body"_L1) {
|
|
atts->clear();
|
|
return;
|
|
}
|
|
|
|
// Clean out everything except 'align' for 'p'
|
|
if (name == "p"_L1) {
|
|
for (auto it = atts->begin(); it != atts->end(); ) {
|
|
if (it->name() == "align"_L1) {
|
|
++it;
|
|
*paragraphAlignmentFound = true;
|
|
} else {
|
|
it = atts->erase(it);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Richtext simplification filter helpers: Check for blank QStringView.
|
|
static inline bool isWhiteSpace(QStringView in)
|
|
{
|
|
return std::all_of(in.cbegin(), in.cend(),
|
|
[](QChar c) { return c.isSpace(); });
|
|
}
|
|
|
|
// Richtext simplification filter: Remove hard-coded font settings,
|
|
// <style> elements, <p> attributes other than 'align' and
|
|
// and unnecessary meta-information.
|
|
QString simplifyRichTextFilter(const QString &in, bool *isPlainTextPtr = nullptr)
|
|
{
|
|
unsigned elementCount = 0;
|
|
bool paragraphAlignmentFound = false;
|
|
QString out;
|
|
QXmlStreamReader reader(in);
|
|
QXmlStreamWriter writer(&out);
|
|
writer.setAutoFormatting(false);
|
|
writer.setAutoFormattingIndent(0);
|
|
|
|
while (!reader.atEnd()) {
|
|
switch (reader.readNext()) {
|
|
case QXmlStreamReader::StartElement:
|
|
elementCount++;
|
|
if (filterElement(reader.name())) {
|
|
const auto name = reader.name();
|
|
QXmlStreamAttributes attributes = reader.attributes();
|
|
filterAttributes(name, &attributes, ¶graphAlignmentFound);
|
|
writer.writeStartElement(name.toString());
|
|
if (!attributes.isEmpty())
|
|
writer.writeAttributes(attributes);
|
|
} else {
|
|
reader.readElementText(); // Skip away all nested elements and characters.
|
|
}
|
|
break;
|
|
case QXmlStreamReader::Characters:
|
|
if (!isWhiteSpace(reader.text()))
|
|
writer.writeCharacters(reader.text().toString());
|
|
break;
|
|
case QXmlStreamReader::EndElement:
|
|
writer.writeEndElement();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
// Check for plain text (no spans, just <html><head><body><p>)
|
|
if (isPlainTextPtr)
|
|
*isPlainTextPtr = !paragraphAlignmentFound && elementCount == 4u; //
|
|
return out;
|
|
}
|
|
|
|
RichTextEditor::RichTextEditor(QWidget *parent)
|
|
: QTextEdit(parent), m_simplifyRichText(simplifyRichTextDefault)
|
|
{
|
|
connect(this, &RichTextEditor::currentCharFormatChanged,
|
|
this, &RichTextEditor::stateChanged);
|
|
connect(this, &RichTextEditor::cursorPositionChanged,
|
|
this, &RichTextEditor::stateChanged);
|
|
}
|
|
|
|
QToolBar *RichTextEditor::createToolBar(QWidget *parent)
|
|
{
|
|
return new RichTextEditorToolBar(this, parent);
|
|
}
|
|
|
|
void RichTextEditor::setFontBold(bool b)
|
|
{
|
|
if (b)
|
|
setFontWeight(QFont::Bold);
|
|
else
|
|
setFontWeight(QFont::Normal);
|
|
}
|
|
|
|
void RichTextEditor::setFontPointSize(double d)
|
|
{
|
|
QTextEdit::setFontPointSize(qreal(d));
|
|
}
|
|
|
|
void RichTextEditor::setText(const QString &text)
|
|
{
|
|
|
|
if (Qt::mightBeRichText(text))
|
|
setHtml(text);
|
|
else
|
|
setPlainText(text);
|
|
}
|
|
|
|
void RichTextEditor::setSimplifyRichText(bool v)
|
|
{
|
|
if (v != m_simplifyRichText) {
|
|
m_simplifyRichText = v;
|
|
emit simplifyRichTextChanged(v);
|
|
}
|
|
}
|
|
|
|
void RichTextEditor::setDefaultFont(QFont font)
|
|
{
|
|
// Some default fonts on Windows have a default size of 7.8,
|
|
// which results in complicated rich text generated by toHtml().
|
|
// Use an integer value.
|
|
const int pointSize = qRound(font.pointSizeF());
|
|
if (pointSize > 0 && !qFuzzyCompare(qreal(pointSize), font.pointSizeF())) {
|
|
font.setPointSize(pointSize);
|
|
}
|
|
|
|
document()->setDefaultFont(font);
|
|
if (font.pointSize() > 0)
|
|
setFontPointSize(font.pointSize());
|
|
else
|
|
setFontPointSize(QFontInfo(font).pointSize());
|
|
emit textChanged();
|
|
}
|
|
|
|
QString RichTextEditor::text(Qt::TextFormat format) const
|
|
{
|
|
switch (format) {
|
|
case Qt::PlainText:
|
|
return toPlainText();
|
|
case Qt::RichText:
|
|
return m_simplifyRichText ? simplifyRichTextFilter(toHtml()) : toHtml();
|
|
default:
|
|
break;
|
|
}
|
|
const QString html = toHtml();
|
|
bool isPlainText;
|
|
const QString simplifiedHtml = simplifyRichTextFilter(html, &isPlainText);
|
|
if (isPlainText)
|
|
return toPlainText();
|
|
return m_simplifyRichText ? simplifiedHtml : html;
|
|
}
|