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