/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Messaging Framework. ** ** $QT_BEGIN_LICENSE:LGPL21$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** As a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "emailcomposer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "attachmentlistwidget.h" #include #include #include #include static int minimumLeftWidth = 65; static const QString placeholder("(no subject)"); enum RecipientType {To, Cc, Bcc }; typedef QPair Recipient; typedef QList RecipientList; static QCompleter* sentFolderCompleter() { const int completionAddressLimit(1000); QStringList addressList; QMailMessageKey::Properties props(QMailMessageKey::Recipients); QMailMessageKey key(QMailMessageKey::status(QMailMessage::Sent)); QMailMessageMetaDataList metaDataList(QMailStore::instance()->messagesMetaData(key, props, QMailStore::ReturnDistinct)); for (const QMailMessageMetaData &metaData : metaDataList) { foreach(QMailAddress address, metaData.recipients()) { QString s(address.toString()); if (!s.simplified().isEmpty()) { addressList.append(s); } } if (addressList.count() >= completionAddressLimit) break; } QCompleter *completer(new QCompleter(addressList)); completer->setCaseSensitivity(Qt::CaseInsensitive); return completer; } class RecipientWidget : public QWidget { Q_OBJECT public: RecipientWidget(QWidget* parent = Q_NULLPTR); bool isRemoveEnabled() const; void setRemoveEnabled(bool val); QString recipient() const; void setRecipient(const QString& r); bool isEmpty() const; void reset(); void clear(); RecipientType recipientType() const; void setRecipientType(RecipientType r); signals: void removeClicked(); void recipientChanged(); protected: bool eventFilter(QObject *obj, QEvent *event); private: QComboBox* m_typeCombo; QLineEdit* m_recipientEdit; QToolButton* m_removeButton; }; RecipientWidget::RecipientWidget(QWidget* parent) : QWidget(parent), m_typeCombo(new QComboBox(this)), m_recipientEdit(new QLineEdit(this)), m_removeButton(new QToolButton(this)) { QHBoxLayout* layout = new QHBoxLayout(this); layout->setSpacing(0); layout->setContentsMargins(0,0,0,0); m_typeCombo->addItem("To",To); m_typeCombo->addItem("Cc",Cc); m_typeCombo->addItem("Bcc",Bcc); layout->addWidget(m_typeCombo); m_typeCombo->setFocusPolicy(Qt::NoFocus); m_typeCombo->setMinimumWidth(minimumLeftWidth); connect(m_recipientEdit,SIGNAL(textEdited(QString)),this,SIGNAL(recipientChanged())); layout->addWidget(m_recipientEdit); setFocusProxy(m_recipientEdit); m_recipientEdit->installEventFilter(this); m_removeButton->setIcon(QIcon(":icon/clear")); connect(m_removeButton,SIGNAL(clicked(bool)),this,SIGNAL(removeClicked())); layout->addWidget(m_removeButton); m_removeButton->setFocusPolicy(Qt::NoFocus); setFocusPolicy(Qt::StrongFocus); m_recipientEdit->setCompleter(sentFolderCompleter()); } bool RecipientWidget::isRemoveEnabled() const { return m_removeButton->isEnabled(); } void RecipientWidget::setRemoveEnabled(bool val) { m_removeButton->setEnabled(val); } QString RecipientWidget::recipient() const { return m_recipientEdit->text(); } void RecipientWidget::setRecipient(const QString& r) { m_recipientEdit->setText(r); } bool RecipientWidget::isEmpty() const { return recipient().isEmpty(); } void RecipientWidget::clear() { m_recipientEdit->clear(); } RecipientType RecipientWidget::recipientType() const { return static_cast(m_typeCombo->itemData(m_typeCombo->currentIndex()).toUInt()); } void RecipientWidget::setRecipientType(RecipientType t) { for (int index = 0; index < m_typeCombo->count(); index++) { RecipientType v = static_cast(m_typeCombo->itemData(index).toUInt()); if (v == t) { m_typeCombo->setCurrentIndex(index); break; } } } bool RecipientWidget::eventFilter(QObject* obj, QEvent* event) { if (obj == m_recipientEdit && event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Backspace) if (isEmpty()) { emit removeClicked(); return true; } return false; } else return QObject::eventFilter(obj, event); } class RecipientListWidget : public QWidget { Q_OBJECT public: RecipientListWidget(QWidget* parent = Q_NULLPTR); QStringList recipients(RecipientType t) const; QStringList recipients() const; void setRecipients(RecipientType, const QStringList& list); void reset(); void clear(); signals: void changed(); private: int emptyRecipientSlots() const; bool containRecipient(RecipientType t, const QString& address) const; private slots: RecipientWidget* addRecipientWidget(); void removeRecipientWidget(); void recipientChanged(); private: QVBoxLayout* m_layout; QList m_widgetList; }; RecipientListWidget::RecipientListWidget(QWidget* parent ) : QWidget(parent), m_layout(new QVBoxLayout(this)) { m_layout->setSpacing(0); m_layout->setContentsMargins(0,0,0,0); reset(); } QStringList RecipientListWidget::recipients(RecipientType t) const { QStringList results; foreach(RecipientWidget* r,m_widgetList) if (!r->isEmpty() && r->recipientType() == t) results.append(r->recipient()); return results; } QStringList RecipientListWidget::recipients() const { QStringList results; foreach(RecipientWidget* r,m_widgetList) if (!r->isEmpty()) results.append(r->recipient()); return results; } void RecipientListWidget::setRecipients(RecipientType t, const QStringList& addresses) { if (addresses.isEmpty()) return; foreach(RecipientWidget* r, m_widgetList) { if (r->isEmpty()) { m_widgetList.removeAll(r); delete r; } } foreach(QString address, addresses) { if (!containRecipient(t,address)) { RecipientWidget* r = addRecipientWidget(); r->setRecipientType(t); r->setRecipient(address); } } addRecipientWidget(); } void RecipientListWidget::reset() { clear(); addRecipientWidget(); } void RecipientListWidget::clear() { foreach(RecipientWidget* r, m_widgetList) { m_widgetList.removeAll(r); delete r; } } int RecipientListWidget::emptyRecipientSlots() const { int emptyCount = 0; foreach(RecipientWidget* r,m_widgetList) { if (r->isEmpty()) emptyCount++; } return emptyCount; } bool RecipientListWidget::containRecipient(RecipientType t, const QString& address) const { foreach(RecipientWidget* r,m_widgetList) { if (r->recipientType() == t && r->recipient() == address) return true; } return false; } RecipientWidget* RecipientListWidget::addRecipientWidget() { RecipientWidget* r = new RecipientWidget(this); connect(r,SIGNAL(removeClicked()),this,SLOT(removeRecipientWidget())); connect(r,SIGNAL(recipientChanged()),this,SLOT(recipientChanged())); connect(r,SIGNAL(removeClicked()),this,SIGNAL(changed())); connect(r,SIGNAL(recipientChanged()),this,SIGNAL(changed())); setUpdatesEnabled(false); m_layout->addWidget(r); if (!m_widgetList.isEmpty()) m_widgetList.last()->setTabOrder(m_widgetList.last(),r); r->setRemoveEnabled(!m_widgetList.isEmpty()); m_widgetList.append(r); updateGeometry(); setUpdatesEnabled(true); return r; } void RecipientListWidget::removeRecipientWidget() { if (RecipientWidget* r = qobject_cast(sender())) { if (m_widgetList.count() <= 1) return; setUpdatesEnabled(false); int index = m_widgetList.indexOf(r); m_widgetList.removeAll(r); m_layout->removeWidget(r); r->deleteLater(); if (index >= m_widgetList.count()) index = m_widgetList.count()-1; if (m_widgetList.at(index)->isEmpty() && index > 0) index--; m_widgetList.at(index)->setFocus(); updateGeometry(); setUpdatesEnabled(true); } } void RecipientListWidget::recipientChanged() { if (qobject_cast(sender())) { if (emptyRecipientSlots() == 0) addRecipientWidget(); } } class Dictionary { public: static Dictionary *instance(); bool contains(const QString &word) { return _words.contains(word.toLower()); } bool empty() { return _words.isEmpty(); } private: Dictionary(); static Dictionary *_sDictionary; QSet _words; }; Dictionary* Dictionary::_sDictionary = 0; Dictionary* Dictionary::instance() { if (!_sDictionary) _sDictionary = new Dictionary(); return _sDictionary; } Dictionary::Dictionary() { //TODO Consider using Hunspell QStringList dictionaryFiles; dictionaryFiles << "/usr/share/dict/words" << "/usr/dict/words"; foreach(QString fileName, dictionaryFiles) { QFileInfo info(fileName); if (info.isReadable()) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) continue; QTextStream stream(&file); QString line; while (!stream.atEnd()) { line = stream.readLine(); if (!line.isEmpty()) _words.insert(line.toLower()); } file.close(); return; } } } class SpellingHighlighter : public QSyntaxHighlighter { Q_OBJECT public: SpellingHighlighter(QTextEdit *parent) :QSyntaxHighlighter(parent) {}; protected: virtual void highlightBlock(const QString &text); }; void SpellingHighlighter::highlightBlock(const QString &text) { if (text.startsWith(EmailComposerInterface::quotePrefix())) return; //don't find errors in quoted text Dictionary *dictionary = Dictionary::instance(); QTextCharFormat spellingFormat; spellingFormat.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); spellingFormat.setUnderlineColor(Qt::red); QRegularExpression wordExpression("(\\b\\w+\\b)"); QRegularExpressionMatch match; int index = text.indexOf(wordExpression, 0, &match); while (index >= 0) { int length = match.capturedLength(); if (!dictionary->contains(text.mid(index, length))) setFormat(index, length, spellingFormat); index = text.indexOf(wordExpression, index + length); } } class BodyTextEdit : public QTextEdit { Q_OBJECT public: BodyTextEdit(EmailComposerInterface* composer, QWidget* parent = Q_NULLPTR); bool isComposing(); bool isEmpty(); signals: void finished(); void positionChanged(int row, int col); protected: void keyPressEvent(QKeyEvent* e); private: EmailComposerInterface* m_composer; }; BodyTextEdit::BodyTextEdit(EmailComposerInterface* composer, QWidget* parent ) : QTextEdit(parent), m_composer(composer) { } bool BodyTextEdit::isComposing() { // ### FIXME always false since Qt 5 port return false; } bool BodyTextEdit::isEmpty() { if (!document()->isEmpty()) return false; // Otherwise there may be pre-edit input queued in the input context return !isComposing(); } void BodyTextEdit::keyPressEvent(QKeyEvent* e) { static const bool keypadAbsent(style()->inherits("QThumbStyle")); if (keypadAbsent) { if (e->key() == Qt::Key_Back) { e->accept(); emit finished(); return; } } else { if (e->key() == Qt::Key_Select) { if (!m_composer->isEmpty()) { e->accept(); emit finished(); } else { e->ignore(); } return; } if (e->key() == Qt::Key_Back) { // if ( Qtopia::mousePreferred() ) { // e->ignore(); // return; // } else if (isEmpty()) { if (isEmpty()) { e->accept(); emit finished(); return; } } } QTextEdit::keyPressEvent( e ); } void EmailComposerInterface::updateLabel() { static const bool keypadAbsent(style()->inherits("QThumbStyle")); if (keypadAbsent) { //if (isEmpty()) //QSoftMenuBar::setLabel(m_bodyEdit, Qt::Key_Back, QSoftMenuBar::Cancel); //else //QSoftMenuBar::setLabel(m_bodyEdit, Qt::Key_Back, QSoftMenuBar::Next); } else { //if (isEmpty()) { //QSoftMenuBar::setLabel(m_bodyEdit, Qt::Key_Back, QSoftMenuBar::Cancel); //QSoftMenuBar::setLabel(m_bodyEdit, Qt::Key_Select, QSoftMenuBar::NoLabel); //} else { //QSoftMenuBar::clearLabel(m_bodyEdit, Qt::Key_Back); // if (m_bodyEdit->isComposing()) //QSoftMenuBar::clearLabel(m_bodyEdit, Qt::Key_Select); // else //QSoftMenuBar::setLabel(m_bodyEdit, Qt::Key_Select, QSoftMenuBar::Next); //} } } void EmailComposerInterface::setCursorPosition() { if (m_cursorIndex != -1) { QTextCursor cursor(m_bodyEdit->textCursor()); cursor.setPosition(m_cursorIndex, QTextCursor::MoveAnchor); m_bodyEdit->setTextCursor(cursor); m_cursorIndex = -1; } } void EmailComposerInterface::selectAttachment() { QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Select attachments")); m_attachmentListWidget->addAttachments(fileNames); } EmailComposerInterface::EmailComposerInterface( QWidget *parent ) : QMailComposerInterface( parent ), m_cursorIndex( -1 ), m_composerWidget(0), m_bodyEdit(0), m_forwardLabel(0), m_forwardEdit(0), m_attachmentsLabel(0), m_widgetStack(0), m_attachmentAction(0), m_recipientListWidget(0), m_attachmentListWidget(0), m_subjectEdit(0), m_title(QString()), m_sourceStatus(0) { init(); } EmailComposerInterface::~EmailComposerInterface() { // Delete any temporary files we don't need foreach (const QString file, m_temporaries) { if (!QFile::remove(file)) { qWarning() << "Unable to remove temporary file:" << file; } } } void EmailComposerInterface::init() { //main layout QVBoxLayout* layout = new QVBoxLayout(this); layout->setSpacing(0); layout->setContentsMargins(0,0,0,0); QWidget::setLayout(layout); m_recipientListWidget = new RecipientListWidget(this); layout->addWidget(m_recipientListWidget); QWidget* subjectPanel = new QWidget(this); QHBoxLayout* subjectLayout = new QHBoxLayout(subjectPanel); subjectLayout->setSpacing(0); subjectLayout->setContentsMargins(0,0,0,0); QLabel* l = new QLabel(tr("Subject:")); l->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); l->setMinimumWidth(minimumLeftWidth); subjectLayout->addWidget(l); subjectLayout->addWidget(m_subjectEdit = new QLineEdit(subjectPanel)); connect(m_subjectEdit,SIGNAL(textEdited(QString)),this,SIGNAL(statusChanged(QString))); subjectPanel->setLayout(subjectLayout); layout->addWidget(subjectPanel); connect(m_recipientListWidget,SIGNAL(changed()),this,SIGNAL(changed())); //body edit m_bodyEdit = new BodyTextEdit(this,m_composerWidget); new SpellingHighlighter(m_bodyEdit); m_bodyEdit->setWordWrapMode(QTextOption::WordWrap); connect(m_bodyEdit, SIGNAL(textChanged()), this, SIGNAL(changed()) ); connect(m_bodyEdit->document(), SIGNAL(contentsChanged()), this, SLOT(updateLabel())); layout->addWidget(m_bodyEdit); m_forwardLabel = new QLabel(tr("Forwarded content:")); m_forwardLabel->setVisible(false); layout->addWidget(m_forwardLabel); //forwarded element edit m_forwardEdit = new QTextEdit(m_composerWidget); m_forwardEdit->setWordWrapMode(QTextOption::WordWrap); m_forwardEdit->setReadOnly(true); // Why doesn't this work? //m_forwardEdit->setAutoFillBackground(true); //m_forwardEdit->setBackgroundRole(QPalette::Window); QPalette p(m_forwardEdit->palette()); p.setColor(QPalette::Active, QPalette::Base, p.color(QPalette::Window)); m_forwardEdit->setPalette(p); m_forwardEdit->setVisible(false); layout->addWidget(m_forwardEdit); //attachments label m_attachmentsLabel = new QLabel(this); layout->addWidget(m_attachmentsLabel); m_attachmentsLabel->hide(); layout->addWidget(m_attachmentListWidget = new AttachmentListWidget(this)); //menus m_attachmentAction = new QAction( QIcon( ":icon/attach" ), tr("Attachments") + "...", this); connect( m_attachmentAction, SIGNAL(triggered()), this, SLOT(selectAttachment()) ); updateLabel(); setTabOrder(m_recipientListWidget,m_subjectEdit); setTabOrder(m_subjectEdit,m_bodyEdit); setTabOrder(m_bodyEdit,m_forwardEdit); setFocusProxy(m_bodyEdit); } void EmailComposerInterface::setPlainText( const QString& text, const QString& signature ) { if (!signature.isEmpty()) { QString msgText(text); if (msgText.endsWith(signature)) { // Signature already exists m_cursorIndex = msgText.length() - (signature.length() + 1); } else { // Append the signature msgText.append('\n').append(signature); m_cursorIndex = text.length(); } m_bodyEdit->setPlainText(msgText); // Move the cursor before the signature - setting directly fails... QTimer::singleShot(0, this, SLOT(setCursorPosition())); } else { m_bodyEdit->setPlainText(text); } } void EmailComposerInterface::getDetails(QMailMessage& mail) const { mail.setTo(QMailAddress::fromStringList(m_recipientListWidget->recipients(To))); mail.setCc(QMailAddress::fromStringList(m_recipientListWidget->recipients(Cc))); mail.setBcc(QMailAddress::fromStringList(m_recipientListWidget->recipients(Bcc))); QString subjectText = m_subjectEdit->text(); if (!subjectText.isEmpty()) mail.setSubject(subjectText); else subjectText = placeholder; } void EmailComposerInterface::setDetails(const QMailMessage& mail) { m_recipientListWidget->setRecipients(To,QMailAddress::toStringList(mail.to())); m_recipientListWidget->setRecipients(Cc,QMailAddress::toStringList(mail.cc())); m_recipientListWidget->setRecipients(Bcc,QMailAddress::toStringList(mail.bcc())); if ((mail.subject() != placeholder)) m_subjectEdit->setText(mail.subject().simplified()); } bool EmailComposerInterface::isEmpty() const { return m_bodyEdit->isEmpty() && m_attachmentListWidget->isEmpty(); } QMailMessage EmailComposerInterface::message() const { QMailMessage mail; QString messageText( m_bodyEdit->toPlainText() ); QMailMessageContentType type("text/plain; charset=UTF-8"); if (m_attachmentListWidget->isEmpty()) { mail.setBody( QMailMessageBody::fromData( messageText, type, QMailMessageBody::QuotedPrintable ) ); } else { QMailMessagePart textPart; textPart.setBody(QMailMessageBody::fromData(messageText.toUtf8(), type, QMailMessageBody::QuotedPrintable)); mail.setMultipartType(QMailMessagePartContainer::MultipartMixed); mail.appendPart(textPart); foreach (const QString& attachment, m_attachmentListWidget->attachments()) { if (attachment.startsWith("ref:")) { const QMailMessage referencedMessage(QMailMessageId(attachment.mid(4).toULongLong())); QMailMessageContentType type; if (referencedMessage.multipartType() == QMailMessage::MultipartNone) { // The part will have the same type as the original message type = referencedMessage.contentType(); } else { // Encapsulate the entire message type = QMailMessageContentType("message/rfc822"); } QMailMessageContentDisposition disposition(QMailMessageContentDisposition::Inline); disposition.setSize(referencedMessage.size()); mail.appendPart(QMailMessagePart::fromMessageReference(referencedMessage.id(), disposition, type, referencedMessage.transferEncoding())); mail.setStatus(QMailMessage::HasReferences, true); mail.setStatus(QMailMessage::HasUnresolvedReferences, true); } else if (attachment.startsWith("partRef:")) { QMailMessagePart::Location partLocation(attachment.mid(8)); const QMailMessage referencedMessage(partLocation.containingMessageId()); const QMailMessagePart &existingPart(referencedMessage.partAt(partLocation)); QMailMessageContentDisposition existingDisposition(existingPart.contentDisposition()); QMailMessageContentDisposition disposition(QMailMessageContentDisposition::Inline); disposition.setFilename(existingDisposition.filename()); disposition.setSize(existingDisposition.size()); mail.appendPart(QMailMessagePart::fromPartReference(existingPart.location(), disposition, existingPart.contentType(), existingPart.transferEncoding())); mail.setStatus(QMailMessage::HasReferences, true); mail.setStatus(QMailMessage::HasUnresolvedReferences, true); } else { QFileInfo fi(attachment); QString partName(fi.fileName()); QString filePath(fi.absoluteFilePath()); QString mimeType(QMail::mimeTypeFromFileName(attachment)); QMailMessageContentType type(mimeType.toLatin1()); type.setName(partName.toLatin1()); QMailMessageContentDisposition disposition( QMailMessageContentDisposition::Attachment ); disposition.setFilename(partName.toLatin1()); disposition.setSize(fi.size()); QMailMessageBodyFwd::TransferEncoding encoding(QMailMessageBody::Base64); QMailMessageBodyFwd::EncodingStatus encodingStatus(QMailMessageBody::RequiresEncoding); if (mimeType == "message/rfc822") { encoding = QMailMessageBody::NoEncoding; encodingStatus = QMailMessageBody::AlreadyEncoded; } QMailMessagePart part = QMailMessagePart::fromFile(filePath, disposition, type, encoding, encodingStatus); mail.appendPart(part); // Store the location of this file for future reference const QMailMessagePart &mailPart(mail.partAt(mail.partCount() - 1)); QString name("qmf-file-location-" + mailPart.location().toString(false)); mail.setCustomField(name, filePath); } } } mail.setMessageType( QMailMessage::Email ); getDetails(mail); if (m_sourceStatus & QMailMessage::LocalOnly) { mail.setStatus(QMailMessage::LocalOnly, true); } // Set the size estimate mail.setSize(mail.indicativeSize() * 1024); mail.setStatus(QMailMessage::HasAttachments, m_attachmentListWidget->attachments().count()); return mail; } void EmailComposerInterface::clear() { m_subjectEdit->clear(); m_recipientListWidget->reset(); m_bodyEdit->clear(); m_attachmentListWidget->clear(); // Delete any temporary files we don't need foreach (const QString file, m_temporaries) { if (!QFile::remove(file)) { qWarning() << "Unable to remove temporary file:" << file; } } // Any newly-composed messages are local messages only m_sourceStatus |= QMailMessage::LocalOnly; } void EmailComposerInterface::setSignature( const QString &sig ) { QString msgText( m_bodyEdit->toPlainText() ); if ( !msgText.isEmpty() && !m_signature.isEmpty() ) { // See if we need to remove the old signature if ( msgText.endsWith( m_signature ) ) msgText.chop( m_signature.length() + 1 ); } m_signature = sig; setPlainText( msgText, m_signature ); } void EmailComposerInterface::setSendingAccountId( const QMailAccountId &accountId ) { m_accountId = accountId; } // sharp 1839 to here static void checkOutlookString(QString &str) { int pos = 0; int newPos; QString oneAddr; QStringList newStr; if (str.indexOf(";") == -1) { // not Outlook style return; } while ((newPos = str.indexOf(";", pos)) != -1) { if (newPos > pos + 1) { // found some string oneAddr = str.mid(pos, newPos-pos); if (oneAddr.indexOf("@") != -1) { // not Outlook comment newStr.append(oneAddr); } } if ((pos = newPos + 1) >= str.length()) { break; } } str = newStr.join(", "); } void EmailComposerInterface::create(const QMailMessage& sourceMail) { m_forwardLabel->setVisible(false); m_forwardEdit->setVisible(false); if (sourceMail.multipartType() == QMailMessagePartContainer::MultipartNone) { if (sourceMail.hasBody()) setPlainText( sourceMail.body().data(), m_signature ); } else { // The only type of multipart message we currently compose is Mixed, with // all but the first part as out-of-line attachments int textPart = -1; for ( uint i = 0; i < sourceMail.partCount(); ++i ) { const QMailMessagePart &part(sourceMail.partAt(i)); // See if we have a filename to link to QString name("qmf-file-location-" + part.location().toString(false)); QString contentLocation = sourceMail.customField(name); if (contentLocation.isEmpty()) { // See if we can use the value in the message (remove any folded whitespace) contentLocation = QUrl(part.contentLocation().remove(QRegularExpression("\\s"))).toLocalFile(); } if (part.referenceType() != QMailMessagePart::None) { // Put the referenced data in the forwarded pane if (part.referenceType() == QMailMessagePart::MessageReference) { QMailMessage referencedMessage(part.messageReference()); m_attachmentListWidget->addAttachment(QString("ref:%1").arg(QString::number(referencedMessage.id().toULongLong()))); if ((referencedMessage.multipartType() == QMailMessage::MultipartNone) && (referencedMessage.hasBody()) && (referencedMessage.contentType().type().toLower() == "text")) { m_forwardEdit->setPlainText(referencedMessage.body().data()); } else { m_forwardEdit->setPlainText(tr("")); } } else { QMailMessagePart::Location partLocation(part.partReference()); QMailMessage referencedMessage(partLocation.containingMessageId()); const QMailMessagePart &referencedPart(referencedMessage.partAt(partLocation)); m_attachmentListWidget->addAttachment(QString("partRef:%1").arg(referencedPart.location().toString(true))); if (referencedPart.hasBody() && (referencedMessage.contentType().type().toLower() == "text")) { m_forwardEdit->setPlainText(referencedPart.body().data()); } else { m_forwardEdit->setPlainText(tr("")); } } m_forwardLabel->setVisible(true); m_forwardEdit->setVisible(true); } else if (part.hasBody() || !contentLocation.isEmpty()) { bool localFile(!contentLocation.isEmpty() && QFile::exists(contentLocation)); if ((textPart == -1) && (!localFile) && (part.contentType().type().toLower() == "text")) { // This is the first text part, we will use as the forwarded text body textPart = i; } else { if (!localFile) { // We need to create a temporary copy of this part's data to link to QString fileName(part.writeBodyTo(QMail::tempPath())); if (fileName.isEmpty()) { qWarning() << "Unable to save part to temporary file!"; continue; } else { m_temporaries.append(fileName); contentLocation = fileName; } } m_attachmentListWidget->addAttachment(contentLocation); } } } if (textPart != -1) { const QMailMessagePart& part = sourceMail.partAt(textPart); setPlainText( part.body().data(), m_signature ); } } //set the details setDetails(sourceMail); m_sourceStatus = sourceMail.status(); if (!sourceMail.id().isValid()) { // This is a new message m_sourceStatus |= QMailMessage::LocalOnly; } m_bodyEdit->setFocus(); m_bodyEdit->moveCursor(QTextCursor::Start); emit changed(); } void EmailComposerInterface::respond(QMailMessage::ResponseType type, const QMailMessage& source, const QMailMessagePart::Location& partLocation) { const QString fwdIndicator(tr("Fwd")); const QString shortFwdIndicator(tr("Fw", "2 letter short version of Fwd for forward")); const QString replyIndicator(tr("Re")); const QString subject = source.subject().toLower(); QString toAddress; QString ccAddress; QString subjectText; QMailAccount sendingAccount(m_accountId); QString fromAddress(sendingAccount.fromAddress().address()); QString originalText; int textPart = -1; QMailMessage mail; // Find the body of this message if ( source.hasBody() ) { originalText = source.body().data(); } else { for ( uint i = 0; i < source.partCount(); ++i ) { const QMailMessagePart &part = source.partAt(i); if (part.contentType().type().toLower() == "text") { // This is the first text part, we will use as the forwarded text body originalText = part.body().data(); textPart = i; break; } } } if ((type == QMailMessage::Forward) || (type == QMailMessage::ForwardPart)) { // Copy the existing mail mail = source; if ((subject.left(fwdIndicator.length() + 1) == (fwdIndicator.toLower() + ":")) || (subject.left(shortFwdIndicator.length() + 1) == (shortFwdIndicator.toLower() + ":"))) { subjectText = source.subject().simplified(); } else { subjectText = fwdIndicator + ": " + source.subject().simplified(); } } else { // Maintain the same ID in case we need part locations mail.setId(source.id()); if (subject.left(replyIndicator.length() + 1) == (replyIndicator.toLower() + ":")) { subjectText = source.subject().simplified(); } else { subjectText = replyIndicator + ": " + source.subject().simplified(); } QMailAddress replyAddress(source.replyTo()); if (replyAddress.isNull()) replyAddress = source.from(); QString str = replyAddress.address(); checkOutlookString(str); toAddress = str; QString messageId = mail.headerFieldText( "message-id" ).trimmed(); if ( !messageId.isEmpty() ) mail.setInReplyTo( messageId ); } QMailMessageContentType textContentType("text/plain; charset=UTF-8"); QString bodyText; if ((type == QMailMessage::Forward) || (type == QMailMessage::ForwardPart)) { QString forwardBlock = "\n------------ Forwarded Message ------------\n"; forwardBlock += "Date: " + source.date().toString() + '\n'; forwardBlock += "From: " + source.from().toString() + '\n'; forwardBlock += "To: " + QMailAddress::toStringList(source.to()).join(QLatin1String(", ")) + '\n'; forwardBlock += "Subject: " + source.subject().simplified() + '\n'; QMailAccount originAccount(source.parentAccountId()); bool viaReference((originAccount.status() & QMailAccount::CanReferenceExternalData) && (sendingAccount.status() & QMailAccount::CanTransmitViaReference)); if (type == QMailMessage::ForwardPart) { mail.clearParts(); mail.setMultipartType(QMailMessage::MultipartMixed); mail.appendPart(QMailMessagePart::fromData(forwardBlock, QMailMessageContentDisposition(QMailMessageContentDisposition::Inline), textContentType, QMailMessageBody::Base64)); // Add only the single relevant part QMailMessageContentDisposition disposition(QMailMessageContentDisposition::Attachment); const QMailMessagePart &sourcePart(source.partAt(partLocation)); if (viaReference) { mail.appendPart(QMailMessagePart::fromPartReference(partLocation, disposition, sourcePart.contentType(), sourcePart.transferEncoding())); } else { // We need to create a copy of the original part QByteArray partData(sourcePart.body().data(QMailMessageBody::Decoded)); mail.appendPart(QMailMessagePart::fromData(partData, disposition, sourcePart.contentType(), sourcePart.transferEncoding())); } bodyText = forwardBlock + '\n' + originalText; textPart = -1; } else { if (viaReference) { // We can send this message by reference mail.clearParts(); mail.setMultipartType(QMailMessage::MultipartMixed); QMailMessageContentDisposition disposition(QMailMessageContentDisposition::Inline); mail.appendPart(QMailMessagePart::fromData(forwardBlock, disposition, textContentType, QMailMessageBody::Base64)); mail.appendPart(QMailMessagePart::fromMessageReference(source.id(), disposition, source.contentType(), source.transferEncoding())); textPart = -1; } else { bodyText = forwardBlock + '\n' + originalText; } } } else { QDateTime dateTime = source.date().toLocalTime(); bodyText = "\nOn " + dateTime.toString(Qt::ISODate) + ", "; bodyText += source.from().name() + " wrote:\n" + EmailComposerInterface::quotePrefix(); int pos = bodyText.length(); bodyText += originalText; while ((pos = bodyText.indexOf('\n', pos)) != -1) bodyText.insert(++pos, EmailComposerInterface::quotePrefix()); bodyText.append('\n'); } // Whatever text subtype it was before, it's now plain... if (mail.partCount() == 0) { // Set the modified text as the body mail.setBody(QMailMessageBody::fromData(bodyText, textContentType, QMailMessageBody::Base64)); } else if (textPart != -1) { // Replace the original text with our modified version QMailMessagePart& part = mail.partAt(textPart); part.setBody(QMailMessageBody::fromData(bodyText, textContentType, QMailMessageBody::Base64)); } if (type == QMailMessage::ReplyToAll) { // Set the reply-to-all address list QList all; foreach (const QMailAddress& addr, source.to() + source.cc()) if ((addr.address() != fromAddress) && (addr.address() != toAddress)) all.append(addr); QString cc = QMailAddress::toStringList(all).join(", "); checkOutlookString( cc ); ccAddress = cc; } mail.removeHeaderField("To"); mail.removeHeaderField("Cc"); mail.removeHeaderField("Bcc"); mail.removeHeaderField("From"); mail.removeHeaderField("Reply-To"); if (!toAddress.isEmpty()) mail.setTo(QMailAddress(toAddress)); if (!fromAddress.isEmpty()) mail.setFrom(QMailAddress(fromAddress)); if (!ccAddress.isEmpty()) mail.setCc(QMailAddress::fromStringList(ccAddress)); if (!subjectText.isEmpty()) mail.setSubject(subjectText); create( mail ); } bool EmailComposerInterface::isReadyToSend() const { bool ready = !m_recipientListWidget->recipients().isEmpty(); return ready; } QString EmailComposerInterface::title() const { return m_title; } QString EmailComposerInterface::key() const { return "EmailComposer"; } QList EmailComposerInterface::messageTypes() const { return QList() << QMailMessage::Email; } QList EmailComposerInterface::contentTypes() const { return QList() << QMailMessage::RichTextContent << QMailMessage::PlainTextContent << QMailMessage::VCardContent << QMailMessage::MultipartContent; } QString EmailComposerInterface::name(QMailMessage::MessageType) const { return qApp->translate("EmailComposerPlugin","Email"); } QString EmailComposerInterface::displayName(QMailMessage::MessageType) const { return qApp->translate("EmailComposerPlugin","Email"); } QIcon EmailComposerInterface::displayIcon(QMailMessage::MessageType) const { return QIcon(":icon/email"); } void EmailComposerInterface::compose(QMailMessage::ResponseType type, const QMailMessage& sourceMail, const QMailMessagePart::Location& sourceLocation, QMailMessage::MessageType) { if (type == QMailMessage::NoResponse) { create(sourceMail); } else if (type == QMailMessage::Redirect) { // TODO: Implement redirect qWarning() << "Unable to handle request to redirect!"; } else { respond(type, sourceMail, sourceLocation); } } QList EmailComposerInterface::actions() const { return QList() << m_attachmentAction; } QString EmailComposerInterface::status() const { return m_subjectEdit->text(); } QString EmailComposerInterface::quotePrefix() { return "> "; } #include "emailcomposer.moc"