File indexing completed on 2024-11-24 04:52:59
0001 /* Copyright (C) 2012 Thomas Lübking <thomas.luebking@gmail.com> 0002 0003 This file is part of the Trojita Qt IMAP e-mail client, 0004 http://trojita.flaska.net/ 0005 0006 This program is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU General Public License as 0008 published by the Free Software Foundation; either version 2 of 0009 the License or (at your option) version 3 or any later version 0010 accepted by the membership of KDE e.V. (or its successor approved 0011 by the membership of KDE e.V.), which shall act as a proxy 0012 defined in Section 14 of version 3 of the license. 0013 0014 This program is distributed in the hope that it will be useful, 0015 but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0017 GNU General Public License for more details. 0018 0019 You should have received a copy of the GNU General Public License 0020 along with this program. If not, see <http://www.gnu.org/licenses/>. 0021 */ 0022 0023 #include "ComposerTextEdit.h" 0024 #include <QAction> 0025 #include <QApplication> 0026 #include <QClipboard> 0027 #include <QFontDatabase> 0028 #include <QMenu> 0029 #include <QMimeData> 0030 #include <QPainter> 0031 #include <QPaintEvent> 0032 #include <QTimer> 0033 #include <QUrl> 0034 0035 #include "Gui/Util.h" 0036 #include "UiUtils/Color.h" 0037 0038 ComposerTextEdit::ComposerTextEdit(QWidget *parent) : QTextEdit(parent) 0039 , m_couldBeSendRequest(false) 0040 , m_wrapIndicatorOffset(-1) 0041 { 0042 setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); 0043 setAcceptRichText(false); 0044 setLineWrapMode(QTextEdit::WidgetWidth); 0045 setWordWrapMode(QTextOption::WordWrap); 0046 m_wrapIndicatorOffset = fontMetrics().averageCharWidth() * 78; 0047 QColor c = palette().color(QPalette::Active, foregroundRole()); 0048 c.setAlpha(26); 0049 m_wrapIndicatorColor = UiUtils::tintColor(palette().color(QPalette::Active, backgroundRole()), c); 0050 0051 m_notificationTimer = new QTimer(this); 0052 m_notificationTimer->setSingleShot(true); 0053 connect(m_notificationTimer, &QTimer::timeout, this, &ComposerTextEdit::resetNotification); 0054 0055 m_pasteQuoted = new QAction(tr("Paste as Quoted Text"), this); 0056 connect(m_pasteQuoted, &QAction::triggered, this, &ComposerTextEdit::slotPasteAsQuotation); 0057 } 0058 0059 void ComposerTextEdit::notify(const QString &n, uint timeout) 0060 { 0061 m_notification = n; 0062 if (m_notification.isEmpty() || !timeout) { 0063 m_notificationTimer->stop(); 0064 } else { 0065 m_notificationTimer->start(timeout); 0066 } 0067 viewport()->update(); 0068 } 0069 0070 int ComposerTextEdit::idealWidth() const 0071 { 0072 const auto margins = contentsMargins(); 0073 return margins.left() + margins.right() + 80*QFontMetrics(font()).averageCharWidth(); 0074 } 0075 0076 void ComposerTextEdit::resetNotification() 0077 { 0078 notify(QString()); 0079 } 0080 0081 bool ComposerTextEdit::canInsertFromMimeData( const QMimeData * source ) const 0082 { 0083 QList<QUrl> urls = source->urls(); 0084 foreach (const QUrl &url, urls) { 0085 if (url.isLocalFile()) 0086 return true; 0087 } 0088 return QTextEdit::canInsertFromMimeData(source); 0089 } 0090 0091 void ComposerTextEdit::insertFromMimeData(const QMimeData *source) 0092 { 0093 QList<QUrl> urls = source->urls(); 0094 foreach (const QUrl &url, urls) { 0095 if (url.isLocalFile()) { 0096 emit urlsAdded(urls); 0097 return; 0098 } 0099 } 0100 QTextEdit::insertFromMimeData(source); 0101 } 0102 0103 0104 static inline bool isSendCombo(QKeyEvent *ke) { 0105 return (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) && ke->modifiers() == Qt::ControlModifier; 0106 } 0107 0108 void ComposerTextEdit::keyPressEvent(QKeyEvent *ke) { 0109 m_couldBeSendRequest = false; 0110 if (isSendCombo(ke)) { 0111 m_couldBeSendRequest = true; 0112 } 0113 QTextEdit::keyPressEvent(ke); 0114 } 0115 0116 void ComposerTextEdit::keyReleaseEvent(QKeyEvent *ke) 0117 { 0118 if (m_couldBeSendRequest && isSendCombo(ke)) { 0119 m_couldBeSendRequest = false; 0120 emit sendRequest(); 0121 return; 0122 } 0123 m_couldBeSendRequest = false; 0124 QTextEdit::keyReleaseEvent(ke); 0125 } 0126 0127 void ComposerTextEdit::paintEvent(QPaintEvent *pe) 0128 { 0129 // wrap indicator 0130 QPainter p(viewport()); 0131 p.setPen(m_wrapIndicatorColor); 0132 const QRect geo = viewport()->geometry(); 0133 const int x = geo.x() + m_wrapIndicatorOffset; 0134 p.drawLine(x, geo.y(), x, geo.x() + viewport()->height()); 0135 p.end(); 0136 0137 QTextEdit::paintEvent(pe); 0138 0139 if ( !m_notification.isEmpty() ) 0140 { 0141 const int s = width()/5; 0142 QRect r(s, 0, width()-2*s, height()); 0143 QPainter p(viewport()); 0144 p.setRenderHint(QPainter::Antialiasing); 0145 p.setClipRegion(pe->region()); 0146 QFont fnt = font(); 0147 fnt.setBold(true); 0148 fnt.setPointSize( fnt.pointSize()*2*r.width()/(3*QFontMetrics(fnt).horizontalAdvance(m_notification)) ); 0149 r.setHeight( QFontMetrics(fnt).height() + 16 ); 0150 r.moveCenter( rect().center() ); 0151 0152 QColor c = palette().color(viewport()->foregroundRole()); 0153 c.setAlpha( 2 * c.alpha() / 3 ); 0154 p.setBrush( c ); 0155 p.setPen( Qt::NoPen ); 0156 p.drawRoundedRect( r, 8,8 ); 0157 0158 p.setPen( palette().color(viewport()->backgroundRole()) ); 0159 p.setFont( fnt ); 0160 p.drawText(r, Qt::AlignCenter|Qt::TextDontClip, m_notification ); 0161 p.end(); 0162 } 0163 } 0164 0165 void ComposerTextEdit::contextMenuEvent(QContextMenuEvent *e) 0166 { 0167 QScopedPointer<QMenu> menu(createStandardContextMenu(e->pos())); 0168 0169 // We would like to place the action next to the existing "Paste" item. How to find it? These actions are created 0170 // in Qt4's src/gui/text/qtextcontrol.cpp, QTextControl::createStandardContextMenu. 0171 // 0172 // The first possibility is to take a look at where are these actions connected; we're looking for a connection 0173 // between triggered() and SLOT(paste()). Unfortunately, it seems that these are only available via QObjectPrivate. 0174 // 0175 // Another possibility is to take a look at the shortcuts. Unfortunately, these "shortcuts" are not really "shortcuts" 0176 // in the sense of "being available via QAction::shortcuts or QAction::shortcuts; they are instead (at least in Qt4) 0177 // handled via event handlers. 0178 // 0179 // Thomas suggested a nice hack, trying the QObject::disconnect. Unfortunately, the QAction is not connected to 0180 // QTextEdit::paste but to a QTextControl/QWidgetTextControl::paste, and these are private classes, which makes it 0181 // a tad complicated to find them via QObject::findChildren(). 0182 // 0183 // The API of QWebView with its standard actions looks like heaven compared to this stuff. 0184 // 0185 // This is why we take a look at the action's text and look for a particular string. Yes, it's ugly; patches welcome. 0186 QAction *pasteAction = 0; 0187 QString candidateStringForPaste = QKeySequence(QKeySequence::Paste).toString(); 0188 // Finally, the API for adding functions leaves something to be desired; QMenu::insertAction takes a pointer to the 0189 // "before" thing which is just... annoying here (even though it makes certain amount of sense with addAction which 0190 // appends). 0191 QAction *followingActionAfterPaste = 0; 0192 QList<QAction*> actions = menu->actions(); 0193 for (QList<QAction*>::const_iterator it = actions.constBegin(); it != actions.constEnd() && !pasteAction; ++it) { 0194 if (!candidateStringForPaste.isEmpty() && (*it)->text().contains(candidateStringForPaste)) { 0195 pasteAction = *it; 0196 if (it + 1 != actions.constEnd()) { 0197 followingActionAfterPaste = *(it + 1); 0198 } 0199 break; 0200 } 0201 } 0202 0203 menu->insertAction(followingActionAfterPaste, m_pasteQuoted); 0204 if (pasteAction) { 0205 m_pasteQuoted->setEnabled(pasteAction->isEnabled()); 0206 } else { 0207 m_pasteQuoted->setEnabled(true); 0208 } 0209 menu->exec(e->globalPos()); 0210 } 0211 0212 void ComposerTextEdit::slotPasteAsQuotation() 0213 { 0214 QString text = qApp->clipboard()->text(); 0215 if (text.isEmpty()) 0216 return; 0217 0218 QStringList lines = text.split(QLatin1Char('\n')); 0219 for (QStringList::iterator it = lines.begin(); it != lines.end(); ++it) { 0220 it->remove(QLatin1Char('\r')); 0221 if (it->startsWith(QLatin1Char('>'))) { 0222 *it = QLatin1Char('>') + *it; 0223 } else { 0224 *it = QLatin1String("> ") + *it; 0225 } 0226 } 0227 text = lines.join(QStringLiteral("\n")); 0228 if (!text.endsWith(QLatin1Char('\n'))) { 0229 text += QLatin1Char('\n'); 0230 } 0231 0232 QTextCursor cursor = textCursor(); 0233 cursor.beginEditBlock(); 0234 cursor.insertBlock(); 0235 cursor.insertText(text); 0236 cursor.endEditBlock(); 0237 setTextCursor(cursor); 0238 }