/* ** Copyright (C) 2013 Jiří Procházka (Hobrasoft) ** Contact: http://www.hobrasoft.cz/ ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** This file is under the terms of the GNU Lesser General Public License ** version 2.1 as published by the Free Software Foundation and appearing ** in the file LICENSE.LGPL included in the packaging of this file. ** Please review the following information to ensure the ** GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ */ #include "mrichtextedit.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MRichTextEdit::MRichTextEdit(QWidget *parent) : QWidget(parent) { setupUi(this); m_lastBlockList = 0; f_textedit->setTabStopWidth(40); connect(f_textedit, SIGNAL(currentCharFormatChanged(QTextCharFormat)), this, SLOT(slotCurrentCharFormatChanged(QTextCharFormat))); connect(f_textedit, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged())); m_fontsize_h1 = 18; m_fontsize_h2 = 16; m_fontsize_h3 = 14; m_fontsize_h4 = 12; fontChanged(f_textedit->font()); bgColorChanged(f_textedit->textColor()); fgColorChanged(f_textedit->textColor()); // paragraph formatting m_paragraphItems << tr("Standard") << tr("Heading 1") << tr("Heading 2") << tr("Heading 3") << tr("Heading 4") << tr("Monospace"); f_paragraph->addItems(m_paragraphItems); connect(f_paragraph, SIGNAL(activated(int)), this, SLOT(textStyle(int))); // undo & redo f_undo->setShortcut(QKeySequence::Undo); f_redo->setShortcut(QKeySequence::Redo); connect(f_textedit->document(), SIGNAL(undoAvailable(bool)), f_undo, SLOT(setEnabled(bool))); connect(f_textedit->document(), SIGNAL(redoAvailable(bool)), f_redo, SLOT(setEnabled(bool))); f_undo->setEnabled(f_textedit->document()->isUndoAvailable()); f_redo->setEnabled(f_textedit->document()->isRedoAvailable()); connect(f_undo, SIGNAL(clicked()), f_textedit, SLOT(undo())); connect(f_redo, SIGNAL(clicked()), f_textedit, SLOT(redo())); // cut, copy & paste f_cut->setShortcut(QKeySequence::Cut); f_copy->setShortcut(QKeySequence::Copy); f_paste->setShortcut(QKeySequence::Paste); f_cut->setEnabled(false); f_copy->setEnabled(false); connect(f_cut, SIGNAL(clicked()), f_textedit, SLOT(cut())); connect(f_copy, SIGNAL(clicked()), f_textedit, SLOT(copy())); connect(f_paste, SIGNAL(clicked()), f_textedit, SLOT(paste())); connect(f_textedit, SIGNAL(copyAvailable(bool)), f_cut, SLOT(setEnabled(bool))); connect(f_textedit, SIGNAL(copyAvailable(bool)), f_copy, SLOT(setEnabled(bool))); #ifndef QT_NO_CLIPBOARD connect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(slotClipboardDataChanged())); #endif // link f_link->setShortcut(Qt::CTRL + Qt::Key_L); connect(f_link, SIGNAL(clicked(bool)), this, SLOT(textLink(bool))); // bold, italic & underline f_bold->setShortcut(Qt::CTRL + Qt::Key_B); f_italic->setShortcut(Qt::CTRL + Qt::Key_I); f_underline->setShortcut(Qt::CTRL + Qt::Key_U); connect(f_bold, SIGNAL(clicked()), this, SLOT(textBold())); connect(f_italic, SIGNAL(clicked()), this, SLOT(textItalic())); connect(f_underline, SIGNAL(clicked()), this, SLOT(textUnderline())); connect(f_strikeout, SIGNAL(clicked()), this, SLOT(textStrikeout())); f_align_left->setChecked(true); connect(f_align_left, SIGNAL(clicked(bool)), this, SLOT(textAlignLeft())); connect(f_align_center, SIGNAL(clicked(bool)), this, SLOT(textAlignCenter())); connect(f_align_right, SIGNAL(clicked(bool)), this, SLOT(textAlignRight())); connect(f_align_justify, SIGNAL(clicked(bool)), this, SLOT(textAlignJustify())); QAction *removeFormat = new QAction(tr("Remove character formatting"), this); removeFormat->setShortcut(QKeySequence("CTRL+M")); connect(removeFormat, SIGNAL(triggered()), this, SLOT(textRemoveFormat())); f_textedit->addAction(removeFormat); QAction *removeAllFormat = new QAction(tr("Remove all formatting"), this); connect(removeAllFormat, SIGNAL(triggered()), this, SLOT(textRemoveAllFormat())); f_textedit->addAction(removeAllFormat); QAction *textsource = new QAction(tr("Edit document source"), this); textsource->setShortcut(QKeySequence("CTRL+O")); connect(textsource, SIGNAL(triggered()), this, SLOT(textSource())); f_textedit->addAction(textsource); QMenu *menu = new QMenu(this); menu->addAction(removeAllFormat); menu->addAction(removeFormat); menu->addAction(textsource); f_menu->setMenu(menu); f_menu->setPopupMode(QToolButton::InstantPopup); // lists f_list_bullet->setShortcut(Qt::CTRL + Qt::Key_Minus); f_list_ordered->setShortcut(Qt::CTRL + Qt::Key_Equal); connect(f_list_bullet, SIGNAL(clicked(bool)), this, SLOT(listBullet(bool))); connect(f_list_ordered, SIGNAL(clicked(bool)), this, SLOT(listOrdered(bool))); // indentation f_indent_dec->setShortcut(Qt::CTRL + Qt::Key_Comma); f_indent_inc->setShortcut(Qt::CTRL + Qt::Key_Period); connect(f_indent_inc, SIGNAL(clicked()), this, SLOT(increaseIndentation())); connect(f_indent_dec, SIGNAL(clicked()), this, SLOT(decreaseIndentation())); // font size QFontDatabase db; foreach(int size, db.standardSizes()) f_fontsize->addItem(QString::number(size)); connect(f_fontsize, SIGNAL(activated(QString)), this, SLOT(textSize(QString))); f_fontsize->setCurrentIndex(f_fontsize->findText(QString::number(QApplication::font() .pointSize()))); // text color QPixmap pix(16, 16); pix.fill(QApplication::palette().background().color()); f_bgcolor->setIcon(pix); connect(f_bgcolor, SIGNAL(clicked()), this, SLOT(textBgColor())); QPixmap pix2(16, 16); pix2.fill(QApplication::palette().foreground().color()); f_fgcolor->setIcon(pix2); connect(f_fgcolor, SIGNAL(clicked()), this, SLOT(textFgColor())); // images connect(f_image, SIGNAL(clicked()), this, SLOT(insertImage())); } void MRichTextEdit::textSource() { QDialog *dialog = new QDialog(this); QPlainTextEdit *pte = new QPlainTextEdit(dialog); pte->setPlainText( f_textedit->toHtml() ); QGridLayout *gl = new QGridLayout(dialog); gl->addWidget(pte,0,0,1,1); dialog->setWindowTitle(tr("Document source")); dialog->setMinimumWidth (400); dialog->setMinimumHeight(600); dialog->exec(); f_textedit->setHtml(pte->toPlainText()); delete dialog; } void MRichTextEdit::textRemoveFormat() { QTextCharFormat fmt; fmt.setFontWeight(QFont::Normal); fmt.setFontUnderline (false); fmt.setFontStrikeOut (false); fmt.setFontItalic (false); fmt.setFontPointSize (9); // fmt.setFontFamily ("Helvetica"); // fmt.setFontStyleHint (QFont::SansSerif); // fmt.setFontFixedPitch (true); f_bold ->setChecked(false); f_underline ->setChecked(false); f_italic ->setChecked(false); f_strikeout ->setChecked(false); f_fontsize ->setCurrentIndex(f_fontsize->findText("9")); // QTextBlockFormat bfmt = cursor.blockFormat(); // bfmt->setIndent(0); fmt.clearBackground(); mergeFormatOnWordOrSelection(fmt); } void MRichTextEdit::textRemoveAllFormat() { f_bold ->setChecked(false); f_underline ->setChecked(false); f_italic ->setChecked(false); f_strikeout ->setChecked(false); f_fontsize ->setCurrentIndex(f_fontsize->findText("9")); QString text = f_textedit->toPlainText(); f_textedit->setPlainText(text); } void MRichTextEdit::textBold() { QTextCharFormat fmt; fmt.setFontWeight(f_bold->isChecked() ? QFont::Bold : QFont::Normal); mergeFormatOnWordOrSelection(fmt); } void MRichTextEdit::focusInEvent(QFocusEvent *) { f_textedit->setFocus(Qt::TabFocusReason); } void MRichTextEdit::textUnderline() { QTextCharFormat fmt; fmt.setFontUnderline(f_underline->isChecked()); mergeFormatOnWordOrSelection(fmt); } void MRichTextEdit::textItalic() { QTextCharFormat fmt; fmt.setFontItalic(f_italic->isChecked()); mergeFormatOnWordOrSelection(fmt); } void MRichTextEdit::textStrikeout() { QTextCharFormat fmt; fmt.setFontStrikeOut(f_strikeout->isChecked()); mergeFormatOnWordOrSelection(fmt); } void MRichTextEdit::textSize(const QString &p) { qreal pointSize = p.toFloat(); if (p.toFloat() > 0) { QTextCharFormat fmt; fmt.setFontPointSize(pointSize); mergeFormatOnWordOrSelection(fmt); } } void MRichTextEdit::textLink(bool checked) { bool unlink = false; QTextCharFormat fmt; if (checked) { QString url = f_textedit->currentCharFormat().anchorHref(); bool ok; QString newUrl = QInputDialog::getText(this, tr("Create a link"), tr("Link URL:"), QLineEdit::Normal, url, &ok); if (ok) { fmt.setAnchor(true); fmt.setAnchorHref(newUrl); fmt.setForeground(QApplication::palette().color(QPalette::Link)); fmt.setFontUnderline(true); } else { unlink = true; } } else { unlink = true; } if (unlink) { fmt.setAnchor(false); fmt.setForeground(QApplication::palette().color(QPalette::Text)); fmt.setFontUnderline(false); } mergeFormatOnWordOrSelection(fmt); } void MRichTextEdit::textStyle(int index) { QTextCursor cursor = f_textedit->textCursor(); cursor.beginEditBlock(); // standard if (!cursor.hasSelection()) { cursor.select(QTextCursor::BlockUnderCursor); } QTextCharFormat fmt; cursor.setCharFormat(fmt); f_textedit->setCurrentCharFormat(fmt); if (index == ParagraphHeading1 || index == ParagraphHeading2 || index == ParagraphHeading3 || index == ParagraphHeading4 ) { if (index == ParagraphHeading1) { fmt.setFontPointSize(m_fontsize_h1); } if (index == ParagraphHeading2) { fmt.setFontPointSize(m_fontsize_h2); } if (index == ParagraphHeading3) { fmt.setFontPointSize(m_fontsize_h3); } if (index == ParagraphHeading4) { fmt.setFontPointSize(m_fontsize_h4); } if (index == ParagraphHeading2 || index == ParagraphHeading4) { fmt.setFontItalic(true); } fmt.setFontWeight(QFont::Bold); } if (index == ParagraphMonospace) { fmt = cursor.charFormat(); fmt.setFontFamily("Monospace"); fmt.setFontStyleHint(QFont::Monospace); fmt.setFontFixedPitch(true); } cursor.setCharFormat(fmt); f_textedit->setCurrentCharFormat(fmt); cursor.endEditBlock(); } void MRichTextEdit::textBgColor() { QColor col = QColorDialog::getColor(f_textedit->textBackgroundColor(), this); QTextCursor cursor = f_textedit->textCursor(); if (!cursor.hasSelection()) { cursor.select(QTextCursor::WordUnderCursor); } QTextCharFormat fmt = cursor.charFormat(); if (col.isValid()) { fmt.setBackground(col); } else { fmt.clearBackground(); } cursor.setCharFormat(fmt); f_textedit->setCurrentCharFormat(fmt); bgColorChanged(col); } void MRichTextEdit::textFgColor() { QColor col = QColorDialog::getColor(f_textedit->textColor(), this); QTextCursor cursor = f_textedit->textCursor(); if (!cursor.hasSelection()) { cursor.select(QTextCursor::WordUnderCursor); } QTextCharFormat fmt = cursor.charFormat(); if (col.isValid()) { fmt.setForeground(col); } else { fmt.clearForeground(); } cursor.setCharFormat(fmt); f_textedit->setCurrentCharFormat(fmt); fgColorChanged(col); } void MRichTextEdit::textAlignLeft() { if (f_align_left->isChecked()) { f_align_center->setChecked(false); f_align_right->setChecked(false); f_align_justify->setChecked(false); QTextBlockFormat fmt; fmt.setAlignment(Qt::AlignLeft); mergeBlockFormatOnWordOrSelection(fmt); } } void MRichTextEdit::textAlignCenter() { if (f_align_center->isChecked()) { f_align_left->setChecked(false); f_align_right->setChecked(false); f_align_justify->setChecked(false); QTextBlockFormat fmt; fmt.setAlignment(Qt::AlignHCenter); mergeBlockFormatOnWordOrSelection(fmt); } } void MRichTextEdit::textAlignRight() { if (f_align_right->isChecked()) { f_align_left->setChecked(false); f_align_center->setChecked(false); f_align_justify->setChecked(false); QTextBlockFormat fmt; fmt.setAlignment(Qt::AlignRight); mergeBlockFormatOnWordOrSelection(fmt); } } void MRichTextEdit::textAlignJustify() { if (f_align_justify->isChecked()) { f_align_left->setChecked(false); f_align_right->setChecked(false); f_align_center->setChecked(false); QTextBlockFormat fmt; fmt.setAlignment(Qt::AlignJustify); mergeBlockFormatOnWordOrSelection(fmt); } } void MRichTextEdit::listBullet(bool checked) { if (checked) { f_list_ordered->setChecked(false); } list(checked, QTextListFormat::ListDisc); } void MRichTextEdit::listOrdered(bool checked) { if (checked) { f_list_bullet->setChecked(false); } list(checked, QTextListFormat::ListDecimal); } void MRichTextEdit::list(bool checked, QTextListFormat::Style style) { QTextCursor cursor = f_textedit->textCursor(); cursor.beginEditBlock(); if (!checked) { QTextBlockFormat obfmt = cursor.blockFormat(); QTextBlockFormat bfmt; bfmt.setIndent(obfmt.indent()); cursor.setBlockFormat(bfmt); } else { QTextListFormat listFmt; if (cursor.currentList()) { listFmt = cursor.currentList()->format(); } listFmt.setStyle(style); cursor.createList(listFmt); } cursor.endEditBlock(); } void MRichTextEdit::mergeFormatOnWordOrSelection(const QTextCharFormat &format) { QTextCursor cursor = f_textedit->textCursor(); if (!cursor.hasSelection()) { cursor.select(QTextCursor::WordUnderCursor); } cursor.mergeCharFormat(format); f_textedit->mergeCurrentCharFormat(format); f_textedit->setFocus(Qt::TabFocusReason); } void MRichTextEdit::mergeBlockFormatOnWordOrSelection(const QTextBlockFormat &format) { QTextCursor cursor = f_textedit->textCursor(); if (!cursor.hasSelection()) { cursor.select(QTextCursor::WordUnderCursor); } cursor.mergeBlockFormat(format); //f_textedit->mergeCurrentCharFormat(format); f_textedit->setFocus(Qt::TabFocusReason); } void MRichTextEdit::slotCursorPositionChanged() { QTextList *l = f_textedit->textCursor().currentList(); if (m_lastBlockList && (l == m_lastBlockList || (l != 0 && m_lastBlockList != 0 && l->format().style() == m_lastBlockList->format().style()))) { return; } m_lastBlockList = l; if (l) { QTextListFormat lfmt = l->format(); if (lfmt.style() == QTextListFormat::ListDisc) { f_list_bullet->setChecked(true); f_list_ordered->setChecked(false); } else if (lfmt.style() == QTextListFormat::ListDecimal) { f_list_bullet->setChecked(false); f_list_ordered->setChecked(true); } else { f_list_bullet->setChecked(false); f_list_ordered->setChecked(false); } } else { f_list_bullet->setChecked(false); f_list_ordered->setChecked(false); } Qt::Alignment alignment = f_textedit->textCursor().blockFormat().alignment(); switch (alignment) { case Qt::AlignLeft: if (!f_align_left->isChecked()) { f_align_left->setChecked(true); f_align_center->setChecked(false); f_align_right->setChecked(false); f_align_justify->setChecked(false); } break; case Qt::AlignHCenter: if (!f_align_center->isChecked()) { f_align_left->setChecked(false); f_align_center->setChecked(true); f_align_right->setChecked(false); f_align_justify->setChecked(false); } break; case Qt::AlignRight: if (!f_align_right->isChecked()) { f_align_left->setChecked(false); f_align_center->setChecked(false); f_align_right->setChecked(true); f_align_justify->setChecked(false); } break; case Qt::AlignJustify: if (!f_align_justify->isChecked()) { f_align_left->setChecked(false); f_align_center->setChecked(false); f_align_right->setChecked(false); f_align_justify->setChecked(true); } break; default: break; } } void MRichTextEdit::fontChanged(const QFont &f) { f_fontsize->setCurrentIndex(f_fontsize->findText(QString::number(f.pointSize()))); f_bold->setChecked(f.bold()); f_italic->setChecked(f.italic()); f_underline->setChecked(f.underline()); f_strikeout->setChecked(f.strikeOut()); if (f.pointSize() == m_fontsize_h1) { f_paragraph->setCurrentIndex(ParagraphHeading1); } else if (f.pointSize() == m_fontsize_h2) { f_paragraph->setCurrentIndex(ParagraphHeading2); } else if (f.pointSize() == m_fontsize_h3) { f_paragraph->setCurrentIndex(ParagraphHeading3); } else if (f.pointSize() == m_fontsize_h4) { f_paragraph->setCurrentIndex(ParagraphHeading4); } else { if (f.fixedPitch() && f.family() == "Monospace") { f_paragraph->setCurrentIndex(ParagraphMonospace); } else { f_paragraph->setCurrentIndex(ParagraphStandard); } } if (f_textedit->textCursor().currentList()) { QTextListFormat lfmt = f_textedit->textCursor().currentList()->format(); if (lfmt.style() == QTextListFormat::ListDisc) { f_list_bullet->setChecked(true); f_list_ordered->setChecked(false); } else if (lfmt.style() == QTextListFormat::ListDecimal) { f_list_bullet->setChecked(false); f_list_ordered->setChecked(true); } else { f_list_bullet->setChecked(false); f_list_ordered->setChecked(false); } } else { f_list_bullet->setChecked(false); f_list_ordered->setChecked(false); } } void MRichTextEdit::bgColorChanged(const QColor &c) { QPixmap pix(16, 16); if (c.isValid()) { pix.fill(c); } else { pix.fill(QApplication::palette().background().color()); } f_bgcolor->setIcon(pix); } void MRichTextEdit::fgColorChanged(const QColor &c) { QPixmap pix(16, 16); if (c.isValid()) { pix.fill(c); } else { pix.fill(QApplication::palette().foreground().color()); } f_fgcolor->setIcon(pix); } void MRichTextEdit::slotCurrentCharFormatChanged(const QTextCharFormat &format) { fontChanged(format.font()); bgColorChanged((format.background().isOpaque()) ? format.background().color() : QColor()); fgColorChanged((format.foreground().isOpaque()) ? format.foreground().color() : QColor()); f_link->setChecked(format.isAnchor()); } void MRichTextEdit::slotClipboardDataChanged() { #ifndef QT_NO_CLIPBOARD if (const QMimeData *md = QApplication::clipboard()->mimeData()) f_paste->setEnabled(md->hasText()); #endif } QString MRichTextEdit::toHtml() const { QString s = f_textedit->toHtml(); // convert emails to links s = s.replace(QRegExp("(<[^a][^>]+>(?:]+>)?|\\s)([a-zA-Z\\d]+@[a-zA-Z\\d]+\\.[a-zA-Z]+)"), "\\1\\2"); // convert links s = s.replace(QRegExp("(<[^a][^>]+>(?:]+>)?|\\s)((?:https?|ftp|file)://[^\\s'\"<>]+)"), "\\1\\2"); return s; } QString MRichTextEdit::toHtmlNoFontSize() const { QString s = f_textedit->toHtml(); s.replace(QRegularExpression("font-size:[0-9]+pt\\s?"), ""); return s; } void MRichTextEdit::increaseIndentation() { indent(+1); } void MRichTextEdit::decreaseIndentation() { indent(-1); } void MRichTextEdit::indent(int delta) { QTextCursor cursor = f_textedit->textCursor(); cursor.beginEditBlock(); QTextBlockFormat bfmt = cursor.blockFormat(); int ind = bfmt.indent(); if (ind + delta >= 0) { bfmt.setIndent(ind + delta); } cursor.setBlockFormat(bfmt); cursor.endEditBlock(); } void MRichTextEdit::setText(const QString& text) { if (text.isEmpty()) { setPlainText(text); return; } if (text[0] == '<') { setHtml(text); } else { setPlainText(text); } } void MRichTextEdit::insertImage() { QSettings s; QString attdir = s.value("general/filedialog-path").toString(); QString file = QFileDialog::getOpenFileName(this, tr("Select an image"), attdir, tr("Images (*.png *.xpm *.jpg *.gif *.bmp);; All (*)")); if (!file.isEmpty()) { QImage image = QImageReader(file).read(); f_textedit->dropImage(image, QFileInfo(file).suffix().toUpper().toLocal8Bit().data()); } }