From 99dad98ce02a147bb6b01a5cbd76576230876d09 Mon Sep 17 00:00:00 2001 From: vicente Date: Wed, 15 Nov 2023 20:54:49 +0100 Subject: [PATCH] initial upload --- iconos.qrc | 17 ++ images/insertimage.png | Bin 0 -> 885 bytes images/righttoleft.png | Bin 0 -> 131 bytes images/simplifyrichtext.png | Bin 0 -> 1933 bytes images/textanchor.png | Bin 0 -> 1581 bytes images/textbold.png | Bin 0 -> 1134 bytes images/textcenter.png | Bin 0 -> 627 bytes images/textitalic.png | Bin 0 -> 829 bytes images/textjustify.png | Bin 0 -> 695 bytes images/textleft.png | Bin 0 -> 673 bytes images/textright.png | Bin 0 -> 677 bytes images/textsubscript.png | Bin 0 -> 897 bytes images/textsuperscript.png | Bin 0 -> 864 bytes images/textunder.png | Bin 0 -> 971 bytes include/addlinkdialog.h | 27 +++ include/coloraction.h | 26 +++ include/htmlhighlighter.h | 43 +++++ include/htmltextedit.h | 23 +++ include/richtexteditor.h | 34 ++++ include/richtexteditordialog.h | 37 ++++ include/richtexteditortoolbar.h | 57 ++++++ src/addlinkdialog.cpp | 45 +++++ src/coloraction.cpp | 36 ++++ src/htmlhighlighter.cpp | 142 ++++++++++++++ src/htmltextedit.cpp | 40 ++++ src/richtexteditor.cpp | 179 ++++++++++++++++++ src/richtexteditordialog.cpp | 143 +++++++++++++++ src/richtexteditortoolbar.cpp | 316 ++++++++++++++++++++++++++++++++ uis/addlinkdialog.ui | 112 +++++++++++ 29 files changed, 1277 insertions(+) create mode 100644 iconos.qrc create mode 100644 images/insertimage.png create mode 100644 images/righttoleft.png create mode 100644 images/simplifyrichtext.png create mode 100644 images/textanchor.png create mode 100644 images/textbold.png create mode 100644 images/textcenter.png create mode 100644 images/textitalic.png create mode 100644 images/textjustify.png create mode 100644 images/textleft.png create mode 100644 images/textright.png create mode 100644 images/textsubscript.png create mode 100644 images/textsuperscript.png create mode 100644 images/textunder.png create mode 100644 include/addlinkdialog.h create mode 100644 include/coloraction.h create mode 100644 include/htmlhighlighter.h create mode 100644 include/htmltextedit.h create mode 100644 include/richtexteditor.h create mode 100644 include/richtexteditordialog.h create mode 100644 include/richtexteditortoolbar.h create mode 100644 src/addlinkdialog.cpp create mode 100644 src/coloraction.cpp create mode 100644 src/htmlhighlighter.cpp create mode 100644 src/htmltextedit.cpp create mode 100644 src/richtexteditor.cpp create mode 100644 src/richtexteditordialog.cpp create mode 100644 src/richtexteditortoolbar.cpp create mode 100644 uis/addlinkdialog.ui diff --git a/iconos.qrc b/iconos.qrc new file mode 100644 index 0000000..fb8ab4f --- /dev/null +++ b/iconos.qrc @@ -0,0 +1,17 @@ + + + images/insertimage.png + images/simplifyrichtext.png + images/textanchor.png + images/textbold.png + images/textcenter.png + images/textitalic.png + images/textjustify.png + images/textleft.png + images/textright.png + images/textsubscript.png + images/textsuperscript.png + images/textunder.png + images/righttoleft.png + + diff --git a/images/insertimage.png b/images/insertimage.png new file mode 100644 index 0000000000000000000000000000000000000000..cfab6375f7554b991d50a0c30bc4c431ed7c77ab GIT binary patch literal 885 zcmV-*1B(2KP)z2#D*<4OA{-$~GePHrD`W)^&HA}hI6wn3nP8)Jnunn=y2Qjh(SU1A z;A^lJl(3(uwES;`gXqJ9dkuJT5%L0B5iAX0Fa)DvCj}7@_SF>!HA)U%_&|bZI`ID6 z&Oiz5X7J5#@Y)He2k_%fICBAn2OF8~3WMWqc;XlcaAymaR^ZzkaB&GjAI7`zSX^zu zX2puuHsS2oZXIdi)|8bn6}llo-Ki2V9{3&v1^|)_@)D9!MS|D|t=*7NgBYm!Ah?GH zH@IDc{xm#!3?c(b?k@N1ZMc57!k|%uC75L?QT0x;LaGz(hU}Y;WljSg7zvBv>V^i z>3>GOxk>1a$!vitx)jNCBwMeNrZ16~fCZ5OLWh;10dYNmnyD}t?R-kPYB<^G@WbV+ z=&684cZDLo!d&kQlx^T?NQ#0e_9;|?PM$)x-b1HvqEx_ktzt#FohF{1V|jU%^qscie}rOO`Y7{LV~jd{yAzz7t9paK7aoN=dXXvX#C%y2%rSn z1`$C3QGil-BGiqd8w&HGVrjGFoxb4|7b||#UL9|-g@~X4s{{o1nm}MY=Cq&`gbK6> zp$G5IT0ZfwaYB8?pI5&GKD_^m;^_!~HpXI!#nMtjO~81Hzz9zVps{p9%L3P!kw2J| zWoFXLNWP1dDQn5_!3pMT8q*5!u{4#XAHckyaNN7gqWXni`Z*@M33-Z?|5Sz)PAOea zCF8>e*gX&t-fC_zGdNF6hJ@uU;`}GP{5IH(QJHhk=E$DPo>FGuzW*(3hT78!taj>B zeDw0`90i9Hl&g#mnLigAkX`tEGvJ3UkxkWXmhir@bR4|iz&)-}r300000 LNkvXXu0mjfgHV~P literal 0 HcmV?d00001 diff --git a/images/righttoleft.png b/images/righttoleft.png new file mode 100644 index 0000000000000000000000000000000000000000..759066479456e1f10ad5a04e091c21a1dbcf624e GIT binary patch literal 131 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~)3?z59=Y9ZEoB=)|t_%$6K<5Ad|IeP;yA#M_ zED7=pW^j0RBMr#W@N{tu;fPL7P!P{>c&RGTmC4oejH~g&oP`bojSCVV8I&5-78GkR ZGhDY|d#&+xpE^(vgQu&X%Q~loCIFm3CZhlV literal 0 HcmV?d00001 diff --git a/images/simplifyrichtext.png b/images/simplifyrichtext.png new file mode 100644 index 0000000000000000000000000000000000000000..e251cf7b9c248861f128d3508e2c1793679d935f GIT binary patch literal 1933 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 Hu0mjfNaS)2 literal 0 HcmV?d00001 diff --git a/images/textjustify.png b/images/textjustify.png new file mode 100644 index 0000000000000000000000000000000000000000..9de0c8808502f5c30ed02ff038e0325464ee0fa1 GIT binary patch literal 695 zcmV;o0!aOdP)uVEB-sve*Bfa?nb*cY@aIQoyPBadhbG&yHQtlji7QW5+a5@S{Z1`Lt zd6$H3g%3Hs!ap7p^L%5UPJq*qwRet>()m{8bwqAVHHt%p>N$+BP#}rR{If`08fg=| zmo{Hwd^YfL+FzJlWg>Z-le#MR@6GOr+`b~&h-ZMe-<<-LG0#HP#x)j)dH)4#a)979 z4zFJT5q>Yq@?w06NL8a@>XkV!tv}myy^hGEzd-Xsq0PN{m)iQNc#RI@)>NT0)2p0d zQr{%rok^{_?O~1h{?(mt^59yVqHM+ZQJ@TP2%3n=5qYk d53cV?Uzt_*yVa0;;G-w82-&`E=vsl|NRlZ${171%fCqpl2f#iX=F~$xN>PZ3QcU5RbeQX5i% z549YY#Kj(rFZdBhL@?JzG|y2XzK9kAjUG8-r<3&pv6kpNG0ip|(#;5enq@!S(Ci=Y11a`K{Eaaa-Xj#pX>&ERT(G_ z8sL76@ST$o?vT9+`110pt_(DqlLdn|Pr$zfnk24~uyIb2kw%pwb_ zs7;>t4+7ThYw{AWt+lZ3{VTv{9c{jtXP;Tq3l3T*!dfFmTpKI<0;fJ;pSrvZ`BsDl zmwIszB+$Fk@W%?BiAHxK0?Jq+Y18+|j8CE^(t!$d0`K6)3krJ1sdL=L%f!=K;_?y3NX0-MRS=m(- z7(H{K?%2LYf$J^gJ6}RR%hFg_>YQR@X)G+{bxkD}_o2ABA4$;h--3m)5d0Uxg9HzP zb|Lm4-Be0pdtfO!)tIH%xL;>mHS-9KZccUUAK_QjqWRkl;nk zj?|qu5(|CE*Vz!1?Sd}?Tow?#lREz_Egd0&ZH7)uGo2P$40tJxWt>s8Ad_PzP!p2i34I^rsoFUNt_=x- zlpU~m*7LgDpfvkI(KH0g)?V0W+CyqFWfMh0k%l6HoF*S+ogaiuQvfpRZ$ie&>yX)S z8TMZ^5l{V_WII*en4oh-`zmVB0U!frM0`T?)bG-lT+LA%0 zDehxrZY^E50PqaJXVI3-1@ji4URBgSoqA-5apu-X%NN*!jex)Y*J94%8`YOyWlBeA zhVq&=FH;bBOV+mMWEBLoML^|~tU3*bKij{+VgODw)9 z4i#yQLJ&>jg(+=uH{f%;Jw2Q6?qnVq>Ma qIQfrFiof2=7gjvSlEt2}-@gEyTTAG3qw2o^0000DSQm;n zp_z(OeuYzK>U6erIyI+F4ayn9d-1~QdGwv>+}w82;0N!{`JeMY&vVXs4vP@N`k$Q`y5XP3&t*}^WrsI(U_ z$o^jh1#Q^ZB2{U|U@=u6Wj}GeUsQU9gR4(8X!mW-7dX26p7)O6^w>Ii6heCt;IJuzjbQkFDvFjatQk1E zo#KPo7TBSy4d>*0qGo6w4A??3XhHzXEt{-s&Aw_+VBAy8bsFBQ>Y0xo6(P9-L=|bZMsOg?H)1XyJ(Va7dq?urVn$I(4 z8Z;{?x^)^1a!oU6|8Ay1(-=j!OcIi6f&pqiO_^!X01-4Q3E`SxfSP8dc?M;bT`J9o z2}16d)G0muFFn|J!F03u$-+(^gVrDJqJWVm+7F2f5HcSyxRMV&SImdkk0}mL` zJ}+w-|AqP!A)FG>xtU^M4L7vbf7WN`Bt5kOOO12Pgs*_Ij&CGmRr>q&)P;tEC zL2DcPcrdUtYJepnEGah;CLXEmU08VsCeNxR^HALSwZ!s@rPzjjHw7ZOXK)g(-fLr- z&}#s{oxpBjPe9zg+rjaV>V=|?x$^dz|9R61Vv|J_>JN0JlQ&&ykW11lp5&gGm|@E^)&dRp);^6G%=?T@;?Om z9)UD{1>~U#k4FP9XDR$I+@0`^PMh?NN?pY-nx}-5Ga8yG7=aQmjYuvS00i3^SfFqc zUnoGaWIEb0_}~~d=!P;a2HIxUXlH2K1~D_nn1S9D!AtdRE7%Tz4)?@piVSQrZk!9? tiWdlQXE+KR0i2nq5l@d5E>L%q{{n6TIQ-)vN!tJb002ovPDHLkV1h)(w5 + +class AddLinkDialog : public QDialog +{ + Q_OBJECT + +public: + AddLinkDialog(RichTextEditor *editor, QWidget *parent = nullptr); + ~AddLinkDialog() override; + + int showDialog(); + +public Q_SLOTS: + void accept() override; + +private: + RichTextEditor *m_editor; + QT_PREPEND_NAMESPACE(Ui)::AddLinkDialog *m_ui; +}; + +#endif // ADDLINKDIALOG_H diff --git a/include/coloraction.h b/include/coloraction.h new file mode 100644 index 0000000..0ff9d1e --- /dev/null +++ b/include/coloraction.h @@ -0,0 +1,26 @@ +#ifndef COLORACTION_H +#define COLORACTION_H + +#include +#include + +class ColorAction : public QAction +{ + Q_OBJECT + +public: + ColorAction(QObject *parent); + + const QColor& color() const { return m_color; } + void setColor(const QColor &color); + +Q_SIGNALS: + void colorChanged(const QColor &color); + +private Q_SLOTS: + void chooseColor(); + +private: + QColor m_color; +}; +#endif // COLORACTION_H diff --git a/include/htmlhighlighter.h b/include/htmlhighlighter.h new file mode 100644 index 0000000..1601ab1 --- /dev/null +++ b/include/htmlhighlighter.h @@ -0,0 +1,43 @@ +#ifndef HTMLHIGHLIGHTER_H +#define HTMLHIGHLIGHTER_H + +#include +#include +#include + +/* HTML syntax highlighter based on Qt Quarterly example */ +class HtmlHighlighter : public QSyntaxHighlighter +{ + Q_OBJECT + +public: + enum Construct { + Entity, + Tag, + Comment, + Attribute, + Value, + LastConstruct = Value + }; + + HtmlHighlighter(QTextEdit *textEdit); + + void setFormatFor(Construct construct, const QTextCharFormat &format); + + QTextCharFormat formatFor(Construct construct) const + { return m_formats[construct]; } + +protected: + enum State { + NormalState = -1, + InComment, + InTag + }; + + void highlightBlock(const QString &text) override; + +private: + QTextCharFormat m_formats[LastConstruct + 1]; +}; + +#endif // HTMLHIGHLIGHTER_H diff --git a/include/htmltextedit.h b/include/htmltextedit.h new file mode 100644 index 0000000..b12f3e6 --- /dev/null +++ b/include/htmltextedit.h @@ -0,0 +1,23 @@ +#ifndef HTMLTEXTEDIT_H +#define HTMLTEXTEDIT_H + +#include +#include +#include + +class HtmlTextEdit : public QTextEdit +{ + Q_OBJECT + +public: + HtmlTextEdit(QWidget *parent = nullptr) + : QTextEdit(parent) + {} + + void contextMenuEvent(QContextMenuEvent *event) override; + +private slots: + void actionTriggered(QAction *action); +}; + +#endif // HTMLTEXTEDIT_H diff --git a/include/richtexteditor.h b/include/richtexteditor.h new file mode 100644 index 0000000..a9b5d16 --- /dev/null +++ b/include/richtexteditor.h @@ -0,0 +1,34 @@ +#ifndef RICHTEXTEDITOR_H +#define RICHTEXTEDITOR_H + +#include +#include + +class RichTextEditor : public QTextEdit +{ + Q_OBJECT +public: + explicit RichTextEditor(QWidget *parent = nullptr); + void setDefaultFont(QFont font); + + QToolBar *createToolBar(QWidget *parent = nullptr); + + QString text(Qt::TextFormat format) const; + + bool simplifyRichText() const { return m_simplifyRichText; } + +public Q_SLOTS: + void setFontBold(bool b); + void setFontPointSize(double); + void setText(const QString &text); + void setSimplifyRichText(bool v); + +Q_SIGNALS: + void stateChanged(); + void simplifyRichTextChanged(bool); + +private: + bool m_simplifyRichText; +}; + +#endif // RICHTEXTEDITOR_H diff --git a/include/richtexteditordialog.h b/include/richtexteditordialog.h new file mode 100644 index 0000000..1748d06 --- /dev/null +++ b/include/richtexteditordialog.h @@ -0,0 +1,37 @@ +#ifndef RICHTEXTEDITORDIALOG_H +#define RICHTEXTEDITORDIALOG_H + +#include "richtexteditor.h" + +#include +#include +#include + +class RichTextEditorDialog : public QDialog +{ + Q_OBJECT +public: + explicit RichTextEditorDialog(QWidget *parent = nullptr); + ~RichTextEditorDialog(); + + int showDialog(); + void setDefaultFont(const QFont &font); + void setText(const QString &text); + QString text(Qt::TextFormat format = Qt::AutoText) const; + +private Q_SLOTS: + void tabIndexChanged(int newIndex); + void richTextChanged(); + void sourceChanged(); + +private: + enum TabIndex { RichTextIndex, SourceIndex }; + enum State { Clean, RichTextChanged, SourceChanged }; + RichTextEditor *m_editor; + QTextEdit *m_text_edit; + QTabWidget *m_tab_widget; + State m_state; + int m_initialTab; +}; + +#endif // RICHTEXTEDITORDIALOG_H diff --git a/include/richtexteditortoolbar.h b/include/richtexteditortoolbar.h new file mode 100644 index 0000000..0480c83 --- /dev/null +++ b/include/richtexteditortoolbar.h @@ -0,0 +1,57 @@ +#ifndef RICHTEXTEDITORTOOLBAR_H +#define RICHTEXTEDITORTOOLBAR_H + +#include "richtexteditor.h" +#include "coloraction.h" + +#include +#include +#include +#include +#include + +QIcon createIconSet(const QString &name); + +class RichTextEditorToolBar : public QToolBar +{ + Q_OBJECT +public: + RichTextEditorToolBar(RichTextEditor *editor, + QWidget *parent = nullptr); + +public Q_SLOTS: + void updateActions(); + +private Q_SLOTS: + void alignmentActionTriggered(QAction *action); + void sizeInputActivated(const QString &size); + void fontFamilyActivated(const QString &font); + void colorChanged(const QColor &color); + void setVAlignSuper(bool super); + void setVAlignSub(bool sub); + void insertLink(); + void insertImage(); + void layoutDirectionChanged(); + +private: + QAction *m_bold_action; + QAction *m_italic_action; + QAction *m_underline_action; + QAction *m_valign_sup_action; + QAction *m_valign_sub_action; + QAction *m_align_left_action; + QAction *m_align_center_action; + QAction *m_align_right_action; + QAction *m_align_justify_action; + QAction *m_layoutDirectionAction; + QAction *m_link_action; + QAction *m_image_action; + QAction *m_simplify_richtext_action; + ColorAction *m_color_action; + QComboBox *m_font_size_input; + QFontComboBox *m_font_family_input; + + QPointer m_editor; +}; + +#endif // RICHTEXTEDITORTOOLBAR_H diff --git a/src/addlinkdialog.cpp b/src/addlinkdialog.cpp new file mode 100644 index 0000000..ee21c9c --- /dev/null +++ b/src/addlinkdialog.cpp @@ -0,0 +1,45 @@ +#include "addlinkdialog.h" + +AddLinkDialog::AddLinkDialog(RichTextEditor *editor, QWidget *parent) : + QDialog(parent), + m_ui(new QT_PREPEND_NAMESPACE(Ui)::AddLinkDialog) +{ + m_ui->setupUi(this); + + m_editor = editor; +} + +AddLinkDialog::~AddLinkDialog() +{ + delete m_ui; +} + +int AddLinkDialog::showDialog() +{ + // Set initial focus + const QTextCursor cursor = m_editor->textCursor(); + if (cursor.hasSelection()) { + m_ui->titleInput->setText(cursor.selectedText()); + m_ui->urlInput->setFocus(); + } else { + m_ui->titleInput->setFocus(); + } + + return exec(); +} + +void AddLinkDialog::accept() +{ + const QString title = m_ui->titleInput->text(); + const QString url = m_ui->urlInput->text(); + + if (!title.isEmpty()) { + const QString html = "" + title + ""; + m_editor->insertHtml(html); + } + + m_ui->titleInput->clear(); + m_ui->urlInput->clear(); + + QDialog::accept(); +} diff --git a/src/coloraction.cpp b/src/coloraction.cpp new file mode 100644 index 0000000..af9ed7b --- /dev/null +++ b/src/coloraction.cpp @@ -0,0 +1,36 @@ +#include "coloraction.h" + +#include +#include +#include + +ColorAction::ColorAction(QObject *parent): + QAction(parent) +{ + setText(tr("Text Color")); + setColor(Qt::black); + connect(this, &QAction::triggered, this, &ColorAction::chooseColor); +} + +void ColorAction::setColor(const QColor &color) +{ + if (color == m_color) + return; + m_color = color; + QPixmap pix(24, 24); + QPainter painter(&pix); + painter.setRenderHint(QPainter::Antialiasing, false); + painter.fillRect(pix.rect(), m_color); + painter.setPen(m_color.darker()); + painter.drawRect(pix.rect().adjusted(0, 0, -1, -1)); + setIcon(pix); +} + +void ColorAction::chooseColor() +{ + const QColor col = QColorDialog::getColor(m_color, nullptr); + if (col.isValid() && col != m_color) { + setColor(col); + emit colorChanged(m_color); + } +} diff --git a/src/htmlhighlighter.cpp b/src/htmlhighlighter.cpp new file mode 100644 index 0000000..bbdef3b --- /dev/null +++ b/src/htmlhighlighter.cpp @@ -0,0 +1,142 @@ +#include "htmlhighlighter.h" + +#include + +using namespace Qt::StringLiterals; + +HtmlHighlighter::HtmlHighlighter(QTextEdit *textEdit) + : QSyntaxHighlighter(textEdit->document()) +{ + QTextCharFormat entityFormat; + entityFormat.setForeground(Qt::red); + setFormatFor(Entity, entityFormat); + + QTextCharFormat tagFormat; + tagFormat.setForeground(Qt::darkMagenta); + tagFormat.setFontWeight(QFont::Bold); + setFormatFor(Tag, tagFormat); + + QTextCharFormat commentFormat; + commentFormat.setForeground(Qt::gray); + commentFormat.setFontItalic(true); + setFormatFor(Comment, commentFormat); + + QTextCharFormat attributeFormat; + attributeFormat.setForeground(Qt::black); + attributeFormat.setFontWeight(QFont::Bold); + setFormatFor(Attribute, attributeFormat); + + QTextCharFormat valueFormat; + valueFormat.setForeground(Qt::blue); + setFormatFor(Value, valueFormat); +} + +void HtmlHighlighter::setFormatFor(Construct construct, + const QTextCharFormat &format) +{ + m_formats[construct] = format; + rehighlight(); +} + +void HtmlHighlighter::highlightBlock(const QString &text) +{ + static const QChar tab = u'\t'; + static const QChar space = u' '; + + int state = previousBlockState(); + qsizetype len = text.size(); + qsizetype start = 0; + qsizetype pos = 0; + + while (pos < len) { + switch (state) { + case NormalState: + default: + while (pos < len) { + QChar ch = text.at(pos); + if (ch == u'<') { + if (QStringView{text}.sliced(pos).startsWith(""_L1)) { + pos += 3; + state = NormalState; + break; + } + } + setFormat(start, pos - start, m_formats[Comment]); + break; + case InTag: + QChar quote = QChar::Null; + while (pos < len) { + QChar ch = text.at(pos); + if (quote.isNull()) { + start = pos; + if (ch == '\''_L1 || ch == u'"') { + quote = ch; + } else if (ch == u'>') { + ++pos; + setFormat(start, pos - start, m_formats[Tag]); + state = NormalState; + break; + } else if (QStringView{text}.sliced(pos).startsWith("/>"_L1)) { + pos += 2; + setFormat(start, pos - start, m_formats[Tag]); + state = NormalState; + break; + } else if (ch != space && text.at(pos) != tab) { + // Tag not ending, not a quote and no whitespace, so + // we must be dealing with an attribute. + ++pos; + while (pos < len && text.at(pos) != space + && text.at(pos) != tab + && text.at(pos) != u'=') + ++pos; + setFormat(start, pos - start, m_formats[Attribute]); + start = pos; + } + } else if (ch == quote) { + quote = QChar::Null; + + // Anything quoted is a value + setFormat(start, pos - start, m_formats[Value]); + } + ++pos; + } + break; + } + } + setCurrentBlockState(state); +} + diff --git a/src/htmltextedit.cpp b/src/htmltextedit.cpp new file mode 100644 index 0000000..df97703 --- /dev/null +++ b/src/htmltextedit.cpp @@ -0,0 +1,40 @@ +#include "htmltextedit.h" + +#include + +void HtmlTextEdit::contextMenuEvent(QContextMenuEvent *event) +{ + QMenu *menu = createStandardContextMenu(); + QMenu *htmlMenu = new QMenu(tr("Insert HTML entity"), menu); + + typedef struct { + const char *text; + const char *entity; + } Entry; + + const Entry entries[] = { + { "&& (&&)", "&" }, + { "& ", " " }, + { "&< (<)", "<" }, + { "&> (>)", ">" }, + { "&© (Copyright)", "©" }, + { "&® (Trade Mark)", "®" }, + }; + + for (const Entry &e : entries) { + QAction *entityAction = new QAction(QLatin1StringView(e.text), + htmlMenu); + entityAction->setData(QLatin1StringView(e.entity)); + htmlMenu->addAction(entityAction); + } + + menu->addMenu(htmlMenu); + connect(htmlMenu, &QMenu::triggered, this, &HtmlTextEdit::actionTriggered); + menu->exec(event->globalPos()); + delete menu; +} + +void HtmlTextEdit::actionTriggered(QAction *action) +{ + insertPlainText(action->data().toString()); +} diff --git a/src/richtexteditor.cpp b/src/richtexteditor.cpp new file mode 100644 index 0000000..e43f2c1 --- /dev/null +++ b/src/richtexteditor.cpp @@ -0,0 +1,179 @@ +#include "richtexteditor.h" +#include "richtexteditortoolbar.h" + +#include +#include +#include +#include + +#include + +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 + 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, +//