initial upload
This commit is contained in:
179
src/richtexteditor.cpp
Normal file
179
src/richtexteditor.cpp
Normal file
@@ -0,0 +1,179 @@
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user