clazy: fix QString(QLatin1String(...))
[trojita.git] / src / Composer / MessageComposer.cpp
blobc135cada50903a1fb2b5643870ef023006096cc8
1 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
3 This file is part of the Trojita Qt IMAP e-mail client,
4 http://trojita.flaska.net/
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License as
8 published by the Free Software Foundation; either version 2 of
9 the License or (at your option) version 3 or any later version
10 accepted by the membership of KDE e.V. (or its successor approved
11 by the membership of KDE e.V.), which shall act as a proxy
12 defined in Section 14 of version 3 of the license.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include "MessageComposer.h"
24 #include <QBuffer>
25 #include <QMimeData>
26 #include <QMimeDatabase>
27 #include <QUrl>
28 #include <QUuid>
29 #include "Common/Application.h"
30 #include "Composer/ComposerAttachments.h"
31 #include "Imap/Encoders.h"
32 #include "Imap/Model/ItemRoles.h"
33 #include "Imap/Model/Model.h"
34 #include "Imap/Model/Utils.h"
35 #include "UiUtils/IconLoader.h"
37 namespace {
38 static QString xTrojitaAttachmentList = QStringLiteral("application/x-trojita-attachments-list");
39 static QString xTrojitaMessageList = QStringLiteral("application/x-trojita-message-list");
40 static QString xTrojitaImapPart = QStringLiteral("application/x-trojita-imap-part");
43 namespace Composer {
45 MessageComposer::MessageComposer(Imap::Mailbox::Model *model, QObject *parent) :
46 QAbstractListModel(parent), m_model(model), m_shouldPreload(false), m_reportTrojitaVersions(true)
50 MessageComposer::~MessageComposer()
52 qDeleteAll(m_attachments);
55 int MessageComposer::rowCount(const QModelIndex &parent) const
57 return parent.isValid() ? 0 : m_attachments.size();
60 QVariant MessageComposer::data(const QModelIndex &index, int role) const
62 if (!index.isValid() || index.column() != 0 || index.row() < 0 || index.row() >= m_attachments.size())
63 return QVariant();
65 switch (role) {
66 case Qt::DisplayRole:
67 return m_attachments[index.row()]->caption();
68 case Qt::ToolTipRole:
69 return m_attachments[index.row()]->tooltip();
70 case Qt::DecorationRole:
72 // This is more or less copy-pasted from Gui/AttachmentView.cpp. Unfortunately, sharing the implementation
73 // is not trivial due to the way how the static libraries are currently built.
74 QMimeType mimeType = QMimeDatabase().mimeTypeForName(QString::fromUtf8(m_attachments[index.row()]->mimeType()));
75 if (mimeType.isValid() && !mimeType.isDefault()) {
76 return QIcon::fromTheme(mimeType.iconName(), UiUtils::loadIcon(QStringLiteral("mail-attachment")));
77 } else {
78 return UiUtils::loadIcon(QStringLiteral("mail-attachment"));
81 case Imap::Mailbox::RoleAttachmentContentDispositionMode:
82 return static_cast<int>(m_attachments[index.row()]->contentDispositionMode());
84 return QVariant();
87 Qt::DropActions MessageComposer::supportedDropActions() const
89 return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction;
92 Qt::ItemFlags MessageComposer::flags(const QModelIndex &index) const
94 Qt::ItemFlags f = QAbstractListModel::flags(index);
96 if (index.isValid()) {
97 f |= Qt::ItemIsDragEnabled;
99 f |= Qt::ItemIsDropEnabled;
100 return f;
103 QMimeData *MessageComposer::mimeData(const QModelIndexList &indexes) const
105 QByteArray encodedData;
106 QDataStream stream(&encodedData, QIODevice::WriteOnly);
107 stream.setVersion(QDataStream::Qt_4_6);
109 QList<AttachmentItem*> items;
110 Q_FOREACH(const QModelIndex &index, indexes) {
111 if (index.model() != this || !index.isValid() || index.column() != 0 || index.parent().isValid())
112 continue;
113 if (index.row() < 0 || index.row() >= m_attachments.size())
114 continue;
115 items << m_attachments[index.row()];
118 if (items.isEmpty())
119 return 0;
121 stream << items.size();
122 Q_FOREACH(const AttachmentItem *attachment, items) {
123 attachment->asDroppableMimeData(stream);
125 QMimeData *res = new QMimeData();
126 res->setData(xTrojitaAttachmentList, encodedData);
127 return res;
130 bool MessageComposer::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
132 if (action == Qt::IgnoreAction)
133 return true;
135 if (column > 0)
136 return false;
138 if (!m_model)
139 return false;
141 Q_UNUSED(row);
142 Q_UNUSED(parent);
143 // FIXME: would be cool to support attachment reshuffling and to respect the desired drop position
146 if (data->hasFormat(xTrojitaAttachmentList)) {
147 QByteArray encodedData = data->data(xTrojitaAttachmentList);
148 QDataStream stream(&encodedData, QIODevice::ReadOnly);
149 return dropAttachmentList(stream);
150 } else if (data->hasFormat(xTrojitaMessageList)) {
151 QByteArray encodedData = data->data(xTrojitaMessageList);
152 QDataStream stream(&encodedData, QIODevice::ReadOnly);
153 return dropImapMessage(stream);
154 } else if (data->hasFormat(xTrojitaImapPart)) {
155 QByteArray encodedData = data->data(xTrojitaImapPart);
156 QDataStream stream(&encodedData, QIODevice::ReadOnly);
157 return dropImapPart(stream);
158 } else if (data->hasUrls()) {
159 bool attached = false;
160 QList<QUrl> urls = data->urls();
161 foreach (const QUrl &url, urls) {
162 if (url.isLocalFile()) {
163 // Careful here -- we definitely don't want the boolean evaluation shortcuts taking effect!
164 // At the same time, any file being recognized and attached is enough to "satisfy" the drop
165 attached = addFileAttachment(url.path()) || attached;
168 return attached;
169 } else {
170 return false;
174 /** @short Container wrapper which calls qDeleteAll on all items which remain in the list at the time of destruction */
175 template <typename T>
176 class WillDeleteAll {
177 public:
178 T d;
179 ~WillDeleteAll() {
180 qDeleteAll(d);
184 /** @short Handle a drag-and-drop of a list of attachments */
185 bool MessageComposer::dropAttachmentList(QDataStream &stream)
187 stream.setVersion(QDataStream::Qt_4_6);
188 if (stream.atEnd()) {
189 qDebug() << "drag-and-drop: cannot decode data: end of stream";
190 return false;
192 int num;
193 stream >> num;
194 if (stream.status() != QDataStream::Ok) {
195 qDebug() << "drag-and-drop: stream failed:" << stream.status();
196 return false;
198 if (num < 0) {
199 qDebug() << "drag-and-drop: invalid number of items";
200 return false;
203 // A crude RAII here; there are many places where the validation might fail even though we have already allocated memory
204 WillDeleteAll<QList<AttachmentItem*>> items;
206 for (int i = 0; i < num; ++i) {
207 int kind = -1;
208 stream >> kind;
210 switch (kind) {
211 case AttachmentItem::ATTACHMENT_IMAP_MESSAGE:
213 QString mailbox;
214 uint uidValidity;
215 QList<uint> uids;
216 stream >> mailbox >> uidValidity >> uids;
217 if (!validateDropImapMessage(stream, mailbox, uidValidity, uids))
218 return false;
219 if (uids.size() != 1) {
220 qDebug() << "drag-and-drop: malformed data for a single message in a mixed list: too many UIDs";
221 return false;
223 try {
224 items.d << new ImapMessageAttachmentItem(m_model, mailbox, uidValidity, uids.front());
225 } catch (Imap::UnknownMessageIndex &) {
226 return false;
229 break;
232 case AttachmentItem::ATTACHMENT_IMAP_PART:
234 QString mailbox;
235 uint uidValidity;
236 uint uid;
237 QByteArray trojitaPath;
238 if (!validateDropImapPart(stream, mailbox, uidValidity, uid, trojitaPath))
239 return false;
240 try {
241 items.d << new ImapPartAttachmentItem(m_model, mailbox, uidValidity, uid, trojitaPath);
242 } catch (Imap::UnknownMessageIndex &) {
243 return false;
246 break;
249 case AttachmentItem::ATTACHMENT_FILE:
251 QString fileName;
252 stream >> fileName;
253 items.d << new FileAttachmentItem(fileName);
254 break;
257 default:
258 qDebug() << "drag-and-drop: invalid kind of attachment";
259 return false;
263 beginInsertRows(QModelIndex(), m_attachments.size(), m_attachments.size() + items.d.size() - 1);
264 Q_FOREACH(AttachmentItem *attachment, items.d) {
265 if (m_shouldPreload)
266 attachment->preload();
267 m_attachments << attachment;
269 items.d.clear();
270 endInsertRows();
272 return true;
275 /** @short Check that the data representing a list of messages is correct */
276 bool MessageComposer::validateDropImapMessage(QDataStream &stream, QString &mailbox, uint &uidValidity, QList<uint> &uids) const
278 if (stream.status() != QDataStream::Ok) {
279 qDebug() << "drag-and-drop: stream failed:" << stream.status();
280 return false;
283 Imap::Mailbox::TreeItemMailbox *mboxPtr = m_model->findMailboxByName(mailbox);
284 if (!mboxPtr) {
285 qDebug() << "drag-and-drop: mailbox not found";
286 return false;
289 if (uids.size() < 1) {
290 qDebug() << "drag-and-drop: no UIDs passed";
291 return false;
293 if (!uidValidity) {
294 qDebug() << "drag-and-drop: invalid UIDVALIDITY";
295 return false;
298 return true;
301 /** @short Handle a drag-and-drop of a list of messages */
302 bool MessageComposer::dropImapMessage(QDataStream &stream)
304 stream.setVersion(QDataStream::Qt_4_6);
305 if (stream.atEnd()) {
306 qDebug() << "drag-and-drop: cannot decode data: end of stream";
307 return false;
309 QString mailbox;
310 uint uidValidity;
311 QList<uint> uids;
312 stream >> mailbox >> uidValidity >> uids;
313 if (!validateDropImapMessage(stream, mailbox, uidValidity, uids))
314 return false;
315 if (!stream.atEnd()) {
316 qDebug() << "drag-and-drop: cannot decode data: too much data";
317 return false;
320 WillDeleteAll<QList<AttachmentItem*>> items;
321 Q_FOREACH(const uint uid, uids) {
322 try {
323 items.d << new ImapMessageAttachmentItem(m_model, mailbox, uidValidity, uid);
324 } catch (Imap::UnknownMessageIndex &) {
325 return false;
327 items.d.last()->setContentDispositionMode(CDN_INLINE);
329 beginInsertRows(QModelIndex(), m_attachments.size(), m_attachments.size() + uids.size() - 1);
330 Q_FOREACH(AttachmentItem *attachment, items.d) {
331 if (m_shouldPreload)
332 attachment->preload();
333 m_attachments << attachment;
335 items.d.clear();
336 endInsertRows();
338 return true;
341 /** @short Check that the data representing a single message part are correct */
342 bool MessageComposer::validateDropImapPart(QDataStream &stream, QString &mailbox, uint &uidValidity, uint &uid, QByteArray &trojitaPath) const
344 stream >> mailbox >> uidValidity >> uid >> trojitaPath;
345 if (stream.status() != QDataStream::Ok) {
346 qDebug() << "drag-and-drop: stream failed:" << stream.status();
347 return false;
349 Imap::Mailbox::TreeItemMailbox *mboxPtr = m_model->findMailboxByName(mailbox);
350 if (!mboxPtr) {
351 qDebug() << "drag-and-drop: mailbox not found";
352 return false;
355 if (!uidValidity || !uid || trojitaPath.isEmpty()) {
356 qDebug() << "drag-and-drop: invalid data";
357 return false;
359 return true;
362 /** @short Handle a drag-adn-drop of a list of message parts */
363 bool MessageComposer::dropImapPart(QDataStream &stream)
365 stream.setVersion(QDataStream::Qt_4_6);
366 if (stream.atEnd()) {
367 qDebug() << "drag-and-drop: cannot decode data: end of stream";
368 return false;
370 QString mailbox;
371 uint uidValidity;
372 uint uid;
373 QByteArray trojitaPath;
374 if (!validateDropImapPart(stream, mailbox, uidValidity, uid, trojitaPath))
375 return false;
376 if (!stream.atEnd()) {
377 qDebug() << "drag-and-drop: cannot decode data: too much data";
378 return false;
381 AttachmentItem *item;
382 try {
383 item = new ImapPartAttachmentItem(m_model, mailbox, uidValidity, uid, trojitaPath);
384 } catch (Imap::UnknownMessageIndex &) {
385 return false;
388 beginInsertRows(QModelIndex(), m_attachments.size(), m_attachments.size());
389 m_attachments << item;
390 if (m_shouldPreload)
391 m_attachments.back()->preload();
392 endInsertRows();
394 return true;
397 QStringList MessageComposer::mimeTypes() const
399 return QStringList() << xTrojitaMessageList << xTrojitaImapPart << xTrojitaAttachmentList << QStringLiteral("text/uri-list");
402 void MessageComposer::setFrom(const Imap::Message::MailAddress &from)
404 m_from = from;
407 void MessageComposer::setRecipients(const QList<QPair<Composer::RecipientKind, Imap::Message::MailAddress> > &recipients)
409 m_recipients = recipients;
412 /** @short Set the value for the In-Reply-To header as per RFC 5322, section 3.6.4
414 The expected values to be passed here do *not* contain the angle brackets. This is in accordance with
415 the very last sentence of that section which says that the angle brackets are not part of the msg-id.
417 void MessageComposer::setInReplyTo(const QList<QByteArray> &inReplyTo)
419 m_inReplyTo = inReplyTo;
422 /** @short Set the value for the References header as per RFC 5322, section 3.6.4
424 @see setInReplyTo
426 void MessageComposer::setReferences(const QList<QByteArray> &references)
428 m_references = references;
431 void MessageComposer::setTimestamp(const QDateTime &timestamp)
433 m_timestamp = timestamp;
436 void MessageComposer::setSubject(const QString &subject)
438 m_subject = subject;
441 void MessageComposer::setOrganization(const QString &organization)
443 m_organization = organization;
446 void MessageComposer::setText(const QString &text)
448 m_text = text;
451 bool MessageComposer::isReadyForSerialization() const
453 Q_FOREACH(const AttachmentItem *attachment, m_attachments) {
454 if (!attachment->isAvailableLocally())
455 return false;
457 return true;
460 QByteArray MessageComposer::generateMessageId(const Imap::Message::MailAddress &sender)
462 if (sender.host.isEmpty()) {
463 // There's no usable domain, let's just bail out of here
464 return QByteArray();
466 return QUuid::createUuid().toByteArray().replace("{", "").replace("}", "") + "@" + sender.host.toUtf8();
469 /** @short Generate a random enough MIME boundary */
470 QByteArray MessageComposer::generateMimeBoundary()
472 // Usage of "=_" is recommended by RFC2045 as it's guaranteed to never occur in a quoted-printable source
473 return QByteArray("trojita=_") + QUuid::createUuid().toByteArray().replace("{", "").replace("}", "");
476 QByteArray MessageComposer::encodeHeaderField(const QString &text)
478 /* This encodes an "unstructured" header field */
479 return Imap::encodeRFC2047StringWithAsciiPrefix(text);
482 namespace {
484 /** @short Write a list of recipients into an output buffer */
485 static void processListOfRecipientsIntoHeader(const QByteArray &prefix, const QList<QByteArray> &addresses, QByteArray &out)
487 // Qt and STL are different, it looks like we cannot easily use something as simple as the ostream_iterator here :(
488 if (!addresses.isEmpty()) {
489 out.append(prefix);
490 for (int i = 0; i < addresses.size() - 1; ++i)
491 out.append(addresses[i]).append(",\r\n ");
492 out.append(addresses.last()).append("\r\n");
498 void MessageComposer::writeCommonMessageBeginning(QIODevice *target, const QByteArray boundary) const
500 // The From header
501 target->write(QByteArray("From: ").append(m_from.asMailHeader()).append("\r\n"));
503 // All recipients
504 // Got to group the headers so that both of (To, Cc) are present at most once
505 QList<QByteArray> rcptTo, rcptCc;
506 for (auto it = m_recipients.begin(); it != m_recipients.end(); ++it) {
507 switch(it->first) {
508 case Composer::ADDRESS_TO:
509 rcptTo << it->second.asMailHeader();
510 break;
511 case Composer::ADDRESS_CC:
512 rcptCc << it->second.asMailHeader();
513 break;
514 case Composer::ADDRESS_BCC:
515 break;
516 case Composer::ADDRESS_FROM:
517 case Composer::ADDRESS_SENDER:
518 case Composer::ADDRESS_REPLY_TO:
519 // These should never ever be produced by Trojita for now
520 Q_ASSERT(false);
521 break;
525 QByteArray recipientHeaders;
526 processListOfRecipientsIntoHeader("To: ", rcptTo, recipientHeaders);
527 processListOfRecipientsIntoHeader("Cc: ", rcptCc, recipientHeaders);
528 target->write(recipientHeaders);
530 // Other message metadata
531 target->write(encodeHeaderField(QLatin1String("Subject: ") + m_subject) + "\r\n" +
532 "Date: " + Imap::dateTimeToRfc2822(m_timestamp).toUtf8() + "\r\n" +
533 "MIME-Version: 1.0\r\n");
534 QByteArray messageId = generateMessageId(m_from);
535 if (!messageId.isEmpty()) {
536 target->write("Message-ID: <" + messageId + ">\r\n");
538 writeHeaderWithMsgIds(target, "In-Reply-To", m_inReplyTo);
539 writeHeaderWithMsgIds(target, "References", m_references);
540 if (!m_organization.isEmpty()) {
541 target->write(encodeHeaderField(QLatin1String("Organization: ") + m_organization) + "\r\n");
543 if (m_reportTrojitaVersions) {
544 target->write(QString::fromUtf8("User-Agent: Trojita/%1; %2\r\n").arg(
545 Common::Application::version, Imap::Mailbox::systemPlatformVersion()).toUtf8());
546 } else {
547 target->write("User-Agent: Trojita\r\n");
550 // Headers depending on actual message body data
551 if (!m_attachments.isEmpty()) {
552 target->write("Content-Type: multipart/mixed;\r\n\tboundary=\"" + boundary + "\"\r\n"
553 "\r\nThis is a multipart/mixed message in MIME format.\r\n\r\n"
554 "--" + boundary + "\r\n");
557 target->write("Content-Type: text/plain; charset=utf-8; format=flowed\r\n"
558 "Content-Transfer-Encoding: quoted-printable\r\n"
559 "\r\n");
560 target->write(Imap::quotedPrintableEncode(Imap::wrapFormatFlowed(m_text).toUtf8()));
563 /** @short Write a header consisting of a list of message-ids
565 Empty headers will not be produced, and the result is wrapped at an appropriate length.
567 The header name must not contain the colon, it is added automatically.
569 void MessageComposer::writeHeaderWithMsgIds(QIODevice *target, const QByteArray &headerName,
570 const QList<QByteArray> &messageIds) const
572 if (messageIds.isEmpty())
573 return;
575 target->write(headerName + ":");
576 int charCount = headerName.length() + 1;
577 for (int i = 0; i < messageIds.size(); ++i) {
578 // Wrapping shall happen at 78 columns, three bytes are eaten by "space < >"
579 if (i != 0 && charCount != 0 && charCount + messageIds[i].length() > 78 - 3) {
580 // got to wrap the header to respect a reasonably small line size
581 charCount = 0;
582 target->write("\r\n");
584 // and now just append one more item
585 target->write(" <" + messageIds[i] + ">");
586 charCount += messageIds[i].length() + 3;
588 target->write("\r\n");
591 bool MessageComposer::writeAttachmentHeader(QIODevice *target, QString *errorMessage, const AttachmentItem *attachment, const QByteArray &boundary) const
593 if (!attachment->isAvailableLocally() && attachment->imapUrl().isEmpty()) {
594 *errorMessage = tr("Attachment %1 is not available").arg(attachment->caption());
595 return false;
597 target->write("\r\n--" + boundary + "\r\n"
598 "Content-Type: " + attachment->mimeType() + "\r\n");
599 target->write(attachment->contentDispositionHeader());
601 switch (attachment->suggestedCTE()) {
602 case AttachmentItem::CTE_BASE64:
603 target->write("Content-Transfer-Encoding: base64\r\n");
604 break;
605 case AttachmentItem::CTE_7BIT:
606 target->write("Content-Transfer-Encoding: 7bit\r\n");
607 break;
608 case AttachmentItem::CTE_8BIT:
609 target->write("Content-Transfer-Encoding: 8bit\r\n");
610 break;
611 case AttachmentItem::CTE_BINARY:
612 target->write("Content-Transfer-Encoding: binary\r\n");
613 break;
616 target->write("\r\n");
617 return true;
620 bool MessageComposer::writeAttachmentBody(QIODevice *target, QString *errorMessage, const AttachmentItem *attachment) const
622 if (!attachment->isAvailableLocally()) {
623 *errorMessage = tr("Attachment %1 is not available").arg(attachment->caption());
624 return false;
626 QSharedPointer<QIODevice> io = attachment->rawData();
627 if (!io) {
628 *errorMessage = tr("Attachment %1 disappeared").arg(attachment->caption());
629 return false;
631 while (!io->atEnd()) {
632 switch (attachment->suggestedCTE()) {
633 case AttachmentItem::CTE_BASE64:
634 // Base64 maps 6bit chunks into a single byte. Output shall have no more than 76 characters per line
635 // (not counting the CRLF pair).
636 target->write(io->read(76*6/8).toBase64() + "\r\n");
637 break;
638 default:
639 target->write(io->readAll());
642 return true;
645 bool MessageComposer::asRawMessage(QIODevice *target, QString *errorMessage) const
647 // We don't bother with checking that our boundary is not present in the individual parts. That's arguably wrong,
648 // but we don't have much choice if we ever plan to use CATENATE. It also looks like this is exactly how other MUAs
649 // oeprate as well, so let's just join the universal dontcareism here.
650 QByteArray boundary(generateMimeBoundary());
652 writeCommonMessageBeginning(target, boundary);
654 if (!m_attachments.isEmpty()) {
655 Q_FOREACH(const AttachmentItem *attachment, m_attachments) {
656 if (!writeAttachmentHeader(target, errorMessage, attachment, boundary))
657 return false;
658 if (!writeAttachmentBody(target, errorMessage, attachment))
659 return false;
661 target->write("\r\n--" + boundary + "--\r\n");
663 return true;
666 bool MessageComposer::asCatenateData(QList<Imap::Mailbox::CatenatePair> &target, QString *errorMessage) const
668 using namespace Imap::Mailbox;
669 target.clear();
670 QByteArray boundary(generateMimeBoundary());
671 target.append(qMakePair(CATENATE_TEXT, QByteArray()));
673 // write the initial data
675 QBuffer io(&target.back().second);
676 io.open(QIODevice::ReadWrite);
677 writeCommonMessageBeginning(&io, boundary);
680 if (!m_attachments.isEmpty()) {
681 Q_FOREACH(const AttachmentItem *attachment, m_attachments) {
682 if (target.back().first != CATENATE_TEXT) {
683 target.append(qMakePair(CATENATE_TEXT, QByteArray()));
685 QBuffer io(&target.back().second);
686 io.open(QIODevice::Append);
688 if (!writeAttachmentHeader(&io, errorMessage, attachment, boundary))
689 return false;
691 QByteArray url = attachment->imapUrl();
692 if (url.isEmpty()) {
693 // Cannot use CATENATE here
694 if (!writeAttachmentBody(&io, errorMessage, attachment))
695 return false;
696 } else {
697 target.append(qMakePair(CATENATE_URL, url));
700 if (target.back().first != CATENATE_TEXT) {
701 target.append(qMakePair(CATENATE_TEXT, QByteArray()));
703 QBuffer io(&target.back().second);
704 io.open(QIODevice::Append);
705 io.write("\r\n--" + boundary + "--\r\n");
707 return true;
710 QDateTime MessageComposer::timestamp() const
712 return m_timestamp;
715 QList<QByteArray> MessageComposer::inReplyTo() const
717 return m_inReplyTo;
720 QList<QByteArray> MessageComposer::references() const
722 return m_references;
725 QByteArray MessageComposer::rawFromAddress() const
727 return m_from.asSMTPMailbox();
730 QList<QByteArray> MessageComposer::rawRecipientAddresses() const
732 QList<QByteArray> res;
734 for (auto it = m_recipients.begin(); it != m_recipients.end(); ++it) {
735 res << it->second.asSMTPMailbox();
738 return res;
741 bool MessageComposer::addFileAttachment(const QString &path)
743 beginInsertRows(QModelIndex(), m_attachments.size(), m_attachments.size());
744 QScopedPointer<AttachmentItem> attachment(new FileAttachmentItem(path));
745 if (!attachment->isAvailableLocally())
746 return false;
747 if (m_shouldPreload)
748 attachment->preload();
749 m_attachments << attachment.take();
750 endInsertRows();
751 return true;
754 void MessageComposer::removeAttachment(const QModelIndex &index)
756 if (!index.isValid() || index.column() != 0 || index.row() < 0 || index.row() >= m_attachments.size())
757 return;
759 beginRemoveRows(QModelIndex(), index.row(), index.row());
760 delete m_attachments.takeAt(index.row());
761 endRemoveRows();
764 void MessageComposer::setAttachmentName(const QModelIndex &index, const QString &newName)
766 if (!index.isValid() || index.column() != 0 || index.row() < 0 || index.row() >= m_attachments.size())
767 return;
769 if (m_attachments[index.row()]->setPreferredFileName(newName))
770 emit dataChanged(index, index);
773 void MessageComposer::setAttachmentContentDisposition(const QModelIndex &index, const ContentDisposition disposition)
775 if (!index.isValid() || index.column() != 0 || index.row() < 0 || index.row() >= m_attachments.size())
776 return;
778 if (m_attachments[index.row()]->setContentDispositionMode(disposition))
779 emit dataChanged(index, index);
782 void MessageComposer::setPreloadEnabled(const bool preload)
784 m_shouldPreload = preload;
787 void MessageComposer::setReplyingToMessage(const QModelIndex &index)
789 m_replyingTo = index;
792 QModelIndex MessageComposer::replyingToMessage() const
794 return m_replyingTo;
797 QModelIndex MessageComposer::forwardingMessage() const
799 return m_forwarding;
802 void MessageComposer::prepareForwarding(const QModelIndex &index, const ForwardMode mode)
804 m_forwarding = index;
806 switch (mode) {
807 case Composer::ForwardMode::FORWARD_AS_ATTACHMENT:
809 beginInsertRows(QModelIndex(), m_attachments.size(), m_attachments.size());
810 QString mailbox = m_forwarding.data(Imap::Mailbox::RoleMailboxName).toString();
811 uint uidValidity = m_forwarding.data(Imap::Mailbox::RoleMailboxUidValidity).toUInt();
812 uint uid = m_forwarding.data(Imap::Mailbox::RoleMessageUid).toUInt();
813 QScopedPointer<AttachmentItem> attachment(new ImapMessageAttachmentItem(m_model, mailbox, uidValidity, uid));
814 if (m_shouldPreload) {
815 attachment->preload();
817 attachment->setContentDispositionMode(CDN_INLINE);
818 m_attachments << attachment.take();
819 endInsertRows();
820 break;
825 void MessageComposer::setReportTrojitaVersions(const bool reportVersions)
827 m_reportTrojitaVersions = reportVersions;