File indexing completed on 2024-06-02 05:48:31

0001 /*
0002 ** Copyright (C) 2013 Jiří Procházka (Hobrasoft)
0003 ** Contact: http://www.hobrasoft.cz/
0004 **
0005 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
0006 ** Contact: http://www.qt-project.org/legal
0007 **
0008 ** $QT_BEGIN_LICENSE:LGPL$
0009 ** GNU Lesser General Public License Usage
0010 ** This file is under the terms of the GNU Lesser General Public License
0011 ** version 2.1 as published by the Free Software Foundation and appearing
0012 ** in the file LICENSE.LGPL included in the packaging of this file.
0013 ** Please review the following information to ensure the
0014 ** GNU Lesser General Public License version 2.1 requirements
0015 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
0016 **
0017 ** In addition, as a special exception, Digia gives you certain additional
0018 ** rights.  These rights are described in the Digia Qt LGPL Exception
0019 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
0020 **
0021 ** $QT_END_LICENSE$
0022 */
0023 
0024 #include <mrichtextedit.h>
0025 
0026 #include <QApplication>
0027 #include <QClipboard>
0028 #include <QMimeData>
0029 #include <QFontDatabase>
0030 #include <QInputDialog>
0031 #include <QColorDialog>
0032 #include <QTextList>
0033 #include <QtDebug>
0034 #include <QFileDialog>
0035 #include <QImageReader>
0036 #include <QSettings>
0037 #include <QBuffer>
0038 #include <QUrl>
0039 #include <QPlainTextEdit>
0040 #include <QMenu>
0041 #include <QDialog>
0042 #include <QRegularExpression>
0043 
0044 MRichTextEdit::MRichTextEdit(QWidget *parent) : QWidget(parent) {
0045     setupUi(this);
0046     m_lastBlockList = nullptr;
0047 
0048     connect(f_textedit, SIGNAL(currentCharFormatChanged(QTextCharFormat)),
0049             this,     SLOT(slotCurrentCharFormatChanged(QTextCharFormat)));
0050     connect(f_textedit, SIGNAL(cursorPositionChanged()),
0051             this,     SLOT(slotCursorPositionChanged()));
0052 
0053     m_fontsize_h1 = 18;
0054     m_fontsize_h2 = 16;
0055     m_fontsize_h3 = 14;
0056     m_fontsize_h4 = 12;
0057 
0058     fontChanged(f_textedit->font());
0059 
0060     // paragraph formatting
0061 
0062     m_paragraphItems = QStringList{ tr("Standard"), tr("Heading 1"), tr("Heading 2"), tr("Heading 3"), tr("Heading 4"), tr("Monospace")};
0063     f_paragraph->addItems(m_paragraphItems);
0064 
0065     connect(f_paragraph, SIGNAL(activated(int)),
0066             this, SLOT(textStyle(int)));
0067 
0068     f_link->setShortcut(Qt::CTRL + Qt::Key_L);
0069 
0070     connect(f_link, SIGNAL(clicked(bool)), this, SLOT(textLink(bool)));
0071 
0072     // bold, italic & underline
0073 
0074     f_bold->setShortcut(Qt::CTRL + Qt::Key_B);
0075     f_italic->setShortcut(Qt::CTRL + Qt::Key_I);
0076     f_underline->setShortcut(Qt::CTRL + Qt::Key_U);
0077 
0078     connect(f_bold, SIGNAL(clicked()), this, SLOT(textBold()));
0079     connect(f_italic, SIGNAL(clicked()), this, SLOT(textItalic()));
0080     connect(f_underline, SIGNAL(clicked()), this, SLOT(textUnderline()));
0081     connect(f_strikeout, SIGNAL(clicked()), this, SLOT(textStrikeout()));
0082 
0083     QAction *removeFormat = new QAction(tr("Remove character formatting"), this);
0084     removeFormat->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_M));
0085     connect(removeFormat, SIGNAL(triggered()), this, SLOT(textRemoveFormat()));
0086     f_textedit->addAction(removeFormat);
0087 
0088     QAction *removeAllFormat = new QAction(tr("Remove all formatting"), this);
0089     connect(removeAllFormat, SIGNAL(triggered()), this, SLOT(textRemoveAllFormat()));
0090     f_textedit->addAction(removeAllFormat);
0091 
0092     QAction *textsource = new QAction(tr("Edit document source"), this);
0093     textsource->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_O));
0094     connect(textsource, SIGNAL(triggered()), this, SLOT(textSource()));
0095     f_textedit->addAction(textsource);
0096 
0097     QAction *clearText = new QAction(tr("Clear all content"), this);
0098     connect(clearText, SIGNAL(triggered()), this, SLOT(clearSource()));
0099     f_textedit->addAction(clearText);
0100 
0101     QMenu *menu = new QMenu(this);
0102     menu->addAction(removeAllFormat);
0103     menu->addAction(removeFormat);
0104     menu->addAction(textsource);
0105     menu->addAction(clearText);
0106     f_menu->setMenu(menu);
0107     f_menu->setPopupMode(QToolButton::InstantPopup);
0108 
0109     // lists
0110 
0111     f_list_bullet->setShortcut(Qt::CTRL + Qt::Key_Minus);
0112     f_list_ordered->setShortcut(Qt::CTRL + Qt::Key_Equal);
0113 
0114     connect(f_list_bullet, SIGNAL(clicked(bool)), this, SLOT(listBullet(bool)));
0115     connect(f_list_ordered, SIGNAL(clicked(bool)), this, SLOT(listOrdered(bool)));
0116 
0117     // indentation
0118 
0119     f_indent_dec->setShortcut(Qt::CTRL + Qt::Key_Comma);
0120     f_indent_inc->setShortcut(Qt::CTRL + Qt::Key_Period);
0121 
0122     connect(f_indent_inc, SIGNAL(clicked()), this, SLOT(increaseIndentation()));
0123     connect(f_indent_dec, SIGNAL(clicked()), this, SLOT(decreaseIndentation()));
0124 
0125     // font size
0126 
0127     QFontDatabase db;
0128     for(int size: db.standardSizes())
0129         f_fontsize->addItem(QString::number(size));
0130 
0131     connect(f_fontsize, SIGNAL(activated(QString)),
0132             this, SLOT(textSize(QString)));
0133     f_fontsize->setCurrentIndex(f_fontsize->findText(QString::number(QApplication::font()
0134                                                                    .pointSize())));
0135 
0136     // text foreground color
0137 
0138     QPixmap pix(16, 16);
0139     pix.fill(QApplication::palette().windowText().color());
0140     // text background color
0141 
0142     pix.fill(QApplication::palette().window().color());
0143 }
0144 
0145 
0146 void MRichTextEdit::textSource() {
0147     QDialog *dialog = new QDialog(this);
0148     QPlainTextEdit *pte = new QPlainTextEdit(dialog);
0149     pte->setPlainText( f_textedit->toHtml() );
0150     QGridLayout *gl = new QGridLayout(dialog);
0151     gl->addWidget(pte,0,0,1,1);
0152     dialog->setWindowTitle(tr("Document source"));
0153     dialog->setMinimumWidth (400);
0154     dialog->setMinimumHeight(600);
0155     dialog->exec();
0156 
0157     f_textedit->setHtml(pte->toPlainText());
0158 
0159     delete dialog;
0160 }
0161 
0162 void MRichTextEdit::clearSource(){
0163     f_textedit->clear();
0164 }
0165 
0166 
0167 void MRichTextEdit::textRemoveFormat() {
0168     QTextCharFormat fmt;
0169     fmt.setFontWeight(QFont::Normal);
0170     fmt.setFontUnderline  (false);
0171     fmt.setFontStrikeOut  (false);
0172     fmt.setFontItalic     (false);
0173     fmt.setFontPointSize  (9);
0174 
0175     f_bold      ->setChecked(false);
0176     f_underline ->setChecked(false);
0177     f_italic    ->setChecked(false);
0178     f_strikeout ->setChecked(false);
0179     f_fontsize  ->setCurrentIndex(f_fontsize->findText(QStringLiteral("9")));
0180 
0181     fmt.clearBackground();
0182 
0183     mergeFormatOnWordOrSelection(fmt);
0184 }
0185 
0186 
0187 void MRichTextEdit::textRemoveAllFormat() {
0188     f_bold      ->setChecked(false);
0189     f_underline ->setChecked(false);
0190     f_italic    ->setChecked(false);
0191     f_strikeout ->setChecked(false);
0192     f_fontsize  ->setCurrentIndex(f_fontsize->findText(QStringLiteral("9")));
0193     QString text = f_textedit->toPlainText();
0194     f_textedit->setPlainText(text);
0195 }
0196 
0197 
0198 void MRichTextEdit::textBold() {
0199     QTextCharFormat fmt;
0200     fmt.setFontWeight(f_bold->isChecked() ? QFont::Bold : QFont::Normal);
0201     mergeFormatOnWordOrSelection(fmt);
0202 }
0203 
0204 
0205 void MRichTextEdit::focusInEvent(QFocusEvent *) {
0206     f_textedit->setFocus(Qt::TabFocusReason);
0207 }
0208 
0209 
0210 void MRichTextEdit::textUnderline() {
0211     QTextCharFormat fmt;
0212     fmt.setFontUnderline(f_underline->isChecked());
0213     mergeFormatOnWordOrSelection(fmt);
0214 }
0215 
0216 void MRichTextEdit::textItalic() {
0217     QTextCharFormat fmt;
0218     fmt.setFontItalic(f_italic->isChecked());
0219     mergeFormatOnWordOrSelection(fmt);
0220 }
0221 
0222 void MRichTextEdit::textStrikeout() {
0223     QTextCharFormat fmt;
0224     fmt.setFontStrikeOut(f_strikeout->isChecked());
0225     mergeFormatOnWordOrSelection(fmt);
0226 }
0227 
0228 void MRichTextEdit::textSize(const QString &p) {
0229     qreal pointSize = p.toDouble();
0230     if (p.toFloat() > 0) {
0231         QTextCharFormat fmt;
0232         fmt.setFontPointSize(pointSize);
0233         mergeFormatOnWordOrSelection(fmt);
0234     }
0235 }
0236 
0237 void MRichTextEdit::textLink(bool checked) {
0238     bool unlink = false;
0239     QTextCharFormat fmt;
0240     if (checked) {
0241         QString url = f_textedit->currentCharFormat().anchorHref();
0242         bool ok;
0243         QString newUrl = QInputDialog::getText(this, tr("Create a link"),
0244                                         tr("Link URL:"), QLineEdit::Normal,
0245                                         url,
0246                                         &ok);
0247         if (ok) {
0248             fmt.setAnchor(true);
0249             fmt.setAnchorHref(newUrl);
0250             fmt.setForeground(QApplication::palette().color(QPalette::Link));
0251             fmt.setFontUnderline(true);
0252           } else {
0253             unlink = true;
0254             }
0255       } else {
0256         unlink = true;
0257         }
0258     if (unlink) {
0259         fmt.setAnchor(false);
0260         fmt.setForeground(QApplication::palette().color(QPalette::Text));
0261         fmt.setFontUnderline(false);
0262         }
0263     mergeFormatOnWordOrSelection(fmt);
0264 }
0265 
0266 void MRichTextEdit::textStyle(int index) {
0267     QTextCursor cursor = f_textedit->textCursor();
0268     cursor.beginEditBlock();
0269 
0270     // standard
0271     if (!cursor.hasSelection()) {
0272         cursor.select(QTextCursor::BlockUnderCursor);
0273         }
0274     QTextCharFormat fmt;
0275     cursor.setCharFormat(fmt);
0276     f_textedit->setCurrentCharFormat(fmt);
0277 
0278     if (index == ParagraphHeading1
0279             || index == ParagraphHeading2
0280             || index == ParagraphHeading3
0281             || index == ParagraphHeading4 ) {
0282         if (index == ParagraphHeading1) {
0283             fmt.setFontPointSize(m_fontsize_h1);
0284             }
0285         if (index == ParagraphHeading2) {
0286             fmt.setFontPointSize(m_fontsize_h2);
0287             }
0288         if (index == ParagraphHeading3) {
0289             fmt.setFontPointSize(m_fontsize_h3);
0290             }
0291         if (index == ParagraphHeading4) {
0292             fmt.setFontPointSize(m_fontsize_h4);
0293             }
0294         if (index == ParagraphHeading2 || index == ParagraphHeading4) {
0295             fmt.setFontItalic(true);
0296             }
0297 
0298         fmt.setFontWeight(QFont::Bold);
0299         }
0300     if (index == ParagraphMonospace) {
0301         fmt = cursor.charFormat();
0302         fmt.setFontFamily(QStringLiteral("Monospace"));
0303         fmt.setFontStyleHint(QFont::Monospace);
0304         fmt.setFontFixedPitch(true);
0305         }
0306     cursor.setCharFormat(fmt);
0307     f_textedit->setCurrentCharFormat(fmt);
0308 
0309     cursor.endEditBlock();
0310 }
0311 
0312 void MRichTextEdit::listBullet(bool checked) {
0313     if (checked) {
0314         f_list_ordered->setChecked(false);
0315         }
0316     list(checked, QTextListFormat::ListDisc);
0317 }
0318 
0319 void MRichTextEdit::listOrdered(bool checked) {
0320     if (checked) {
0321         f_list_bullet->setChecked(false);
0322         }
0323     list(checked, QTextListFormat::ListDecimal);
0324 }
0325 
0326 void MRichTextEdit::list(bool checked, QTextListFormat::Style style) {
0327     QTextCursor cursor = f_textedit->textCursor();
0328     cursor.beginEditBlock();
0329     if (!checked) {
0330         QTextBlockFormat obfmt = cursor.blockFormat();
0331         QTextBlockFormat bfmt;
0332         bfmt.setIndent(obfmt.indent());
0333         cursor.setBlockFormat(bfmt);
0334       } else {
0335         QTextListFormat listFmt;
0336         if (cursor.currentList()) {
0337             listFmt = cursor.currentList()->format();
0338             }
0339         listFmt.setStyle(style);
0340         cursor.createList(listFmt);
0341         }
0342     cursor.endEditBlock();
0343 }
0344 
0345 void MRichTextEdit::mergeFormatOnWordOrSelection(const QTextCharFormat &format) {
0346     QTextCursor cursor = f_textedit->textCursor();
0347     if (!cursor.hasSelection()) {
0348         cursor.select(QTextCursor::WordUnderCursor);
0349         }
0350     cursor.mergeCharFormat(format);
0351     f_textedit->mergeCurrentCharFormat(format);
0352     f_textedit->setFocus(Qt::TabFocusReason);
0353 }
0354 
0355 void MRichTextEdit::slotCursorPositionChanged() {
0356     QTextList *l = f_textedit->textCursor().currentList();
0357     if (m_lastBlockList && (l == m_lastBlockList || (l != nullptr && m_lastBlockList != nullptr
0358                                  && l->format().style() == m_lastBlockList->format().style()))) {
0359         return;
0360         }
0361     m_lastBlockList = l;
0362     if (l) {
0363         QTextListFormat lfmt = l->format();
0364         if (lfmt.style() == QTextListFormat::ListDisc) {
0365             f_list_bullet->setChecked(true);
0366             f_list_ordered->setChecked(false);
0367           } else if (lfmt.style() == QTextListFormat::ListDecimal) {
0368             f_list_bullet->setChecked(false);
0369             f_list_ordered->setChecked(true);
0370           } else {
0371             f_list_bullet->setChecked(false);
0372             f_list_ordered->setChecked(false);
0373             }
0374       } else {
0375         f_list_bullet->setChecked(false);
0376         f_list_ordered->setChecked(false);
0377         }
0378 }
0379 
0380 void MRichTextEdit::fontChanged(const QFont &f) {
0381     f_fontsize->setCurrentIndex(f_fontsize->findText(QString::number(f.pointSize())));
0382     f_bold->setChecked(f.bold());
0383     f_italic->setChecked(f.italic());
0384     f_underline->setChecked(f.underline());
0385     f_strikeout->setChecked(f.strikeOut());
0386     if (f.pointSize() == m_fontsize_h1) {
0387         f_paragraph->setCurrentIndex(ParagraphHeading1);
0388       } else if (f.pointSize() == m_fontsize_h2) {
0389         f_paragraph->setCurrentIndex(ParagraphHeading2);
0390       } else if (f.pointSize() == m_fontsize_h3) {
0391         f_paragraph->setCurrentIndex(ParagraphHeading3);
0392       } else if (f.pointSize() == m_fontsize_h4) {
0393         f_paragraph->setCurrentIndex(ParagraphHeading4);
0394       } else {
0395         if (f.fixedPitch() && f.family() == QStringLiteral("Monospace")) {
0396             f_paragraph->setCurrentIndex(ParagraphMonospace);
0397           } else {
0398             f_paragraph->setCurrentIndex(ParagraphStandard);
0399             }
0400         }
0401     if (f_textedit->textCursor().currentList()) {
0402         QTextListFormat lfmt = f_textedit->textCursor().currentList()->format();
0403         if (lfmt.style() == QTextListFormat::ListDisc) {
0404             f_list_bullet->setChecked(true);
0405             f_list_ordered->setChecked(false);
0406           } else if (lfmt.style() == QTextListFormat::ListDecimal) {
0407             f_list_bullet->setChecked(false);
0408             f_list_ordered->setChecked(true);
0409           } else {
0410             f_list_bullet->setChecked(false);
0411             f_list_ordered->setChecked(false);
0412             }
0413       } else {
0414         f_list_bullet->setChecked(false);
0415         f_list_ordered->setChecked(false);
0416       }
0417 }
0418 
0419 void MRichTextEdit::slotCurrentCharFormatChanged(const QTextCharFormat &format) {
0420     fontChanged(format.font());
0421     f_link->setChecked(format.isAnchor());
0422 }
0423 
0424 QString MRichTextEdit::toHtml() const {
0425     // only return html *if* there's text in it.
0426     // without this, the empty text has a lot of html metadata.
0427     const bool isTextEmpty = f_textedit->toPlainText().isEmpty();
0428     if (isTextEmpty) {
0429         return QString();
0430     }
0431 
0432     QString s = f_textedit->toHtml();
0433     // convert emails to links
0434     s = s.replace(QRegularExpression(QStringLiteral("(<[^a][^>]+>(?:<span[^>]+>)?|\\s)([a-zA-Z\\d]+@[a-zA-Z\\d]+\\.[a-zA-Z]+)")),
0435                   QStringLiteral("\\1<a href=\"mailto:\\2\">\\2</a>"));
0436     // convert links
0437     s = s.replace(QRegularExpression(QStringLiteral("(<[^a][^>]+>(?:<span[^>]+>)?|\\s)((?:https?|ftp|file)://[^\\s'\"<>]+)")),
0438                   QStringLiteral("\\1<a href=\"\\2\">\\2</a>"));
0439     // see also: Utils::linkify()
0440     return s;
0441 }
0442 
0443 void MRichTextEdit::increaseIndentation() {
0444     indent(+1);
0445 }
0446 
0447 void MRichTextEdit::decreaseIndentation() {
0448     indent(-1);
0449 }
0450 
0451 void MRichTextEdit::indent(int delta) {
0452     QTextCursor cursor = f_textedit->textCursor();
0453     cursor.beginEditBlock();
0454     QTextBlockFormat bfmt = cursor.blockFormat();
0455     int ind = bfmt.indent();
0456     if (ind + delta >= 0) {
0457         bfmt.setIndent(ind + delta);
0458         }
0459     cursor.setBlockFormat(bfmt);
0460     cursor.endEditBlock();
0461 }
0462 
0463 void MRichTextEdit::setText(const QString& text) {
0464     if (text.isEmpty()) {
0465         setPlainText(text);
0466         return;
0467     }
0468 
0469     if (text[0] == QLatin1Char('<')) {
0470         setHtml(text);
0471     } else {
0472         setPlainText(text);
0473     }
0474 }
0475 
0476 void MRichTextEdit::insertImage() {
0477     QSettings s;
0478     QString attdir = s.value(QStringLiteral("rich-text-edit/general/filedialog-path")).toString();
0479     QString file = QFileDialog::getOpenFileName(this,
0480                                     tr("Select an image"),
0481                                     attdir,
0482                                     tr("JPEG (*.jpg);; GIF (*.gif);; PNG (*.png);; BMP (*.bmp);; All (*)"));
0483     QImage image = QImageReader(file).read();
0484 
0485     f_textedit->dropImage(image, QFileInfo(file).suffix().toUpper());
0486 
0487 }
0488 
0489