summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/kube/contents/ui/ComposerView.qml42
-rw-r--r--framework/qmldir1
-rw-r--r--framework/src/domain/composercontroller.cpp151
-rw-r--r--framework/src/domain/composercontroller.h17
4 files changed, 208 insertions, 3 deletions
diff --git a/components/kube/contents/ui/ComposerView.qml b/components/kube/contents/ui/ComposerView.qml
index 49a59024..9e48aba3 100644
--- a/components/kube/contents/ui/ComposerView.qml
+++ b/components/kube/contents/ui/ComposerView.qml
@@ -21,6 +21,7 @@
21import QtQuick 2.7 21import QtQuick 2.7
22import QtQuick.Controls 1.3 22import QtQuick.Controls 1.3
23import QtQuick.Layouts 1.1 23import QtQuick.Layouts 1.1
24import QtQuick.Dialogs 1.0 as Dialogs
24 25
25import org.kube.framework 1.0 as Kube 26import org.kube.framework 1.0 as Kube
26 27
@@ -253,6 +254,47 @@ Kube.View {
253 onActiveFocusChanged: closeFirstSplitIfNecessary() 254 onActiveFocusChanged: closeFirstSplitIfNecessary()
254 } 255 }
255 256
257 Row {
258 Layout.fillWidth: true
259 spacing: Kube.Units.largeSpacing
260 Flow {
261 id: attachments
262
263 layoutDirection: Qt.RightToLeft
264 spacing: Kube.Units.smallSpacing
265 clip: true
266
267 Repeater {
268 model: composerController.attachmentModel
269 delegate: Kube.AttachmentDelegate {
270 name: model.filename
271 icon: model.iconName
272 clip: true
273 }
274 }
275 }
276 Kube.Button {
277 text: "Attach file"
278
279 onClicked: {
280 fileDialogComponent.createObject(parent)
281 }
282
283 Component {
284 id: fileDialogComponent
285 Dialogs.FileDialog {
286 id: fileDialog
287 visible: true
288 title: "Choose a file to attach"
289 selectFolder: false
290 onAccepted: {
291 composerController.addAttachment(fileDialog.fileUrl)
292 }
293 }
294 }
295 }
296 }
297
256 Kube.TextArea { 298 Kube.TextArea {
257 id: content 299 id: content
258 Layout.fillWidth: true 300 Layout.fillWidth: true
diff --git a/framework/qmldir b/framework/qmldir
index 1265b68c..74f8a4af 100644
--- a/framework/qmldir
+++ b/framework/qmldir
@@ -26,6 +26,7 @@ TextArea 1.0 TextArea.qml
26Label 1.0 Label.qml 26Label 1.0 Label.qml
27View 1.0 View.qml 27View 1.0 View.qml
28AutocompleteLineEdit 1.0 AutocompleteLineEdit.qml 28AutocompleteLineEdit 1.0 AutocompleteLineEdit.qml
29AttachmentDelegate 1.0 AttachmentDelegate.qml
29singleton Messages 1.0 Messages.qml 30singleton Messages 1.0 Messages.qml
30singleton Colors 1.0 Colors.qml 31singleton Colors 1.0 Colors.qml
31singleton Icons 1.0 Icons.qml 32singleton Icons 1.0 Icons.qml
diff --git a/framework/src/domain/composercontroller.cpp b/framework/src/domain/composercontroller.cpp
index cbc43afe..7c495543 100644
--- a/framework/src/domain/composercontroller.cpp
+++ b/framework/src/domain/composercontroller.cpp
@@ -27,6 +27,10 @@
27#include <QSortFilterProxyModel> 27#include <QSortFilterProxyModel>
28#include <QList> 28#include <QList>
29#include <QDebug> 29#include <QDebug>
30#include <QMimeDatabase>
31#include <QUrlQuery>
32#include <QFileInfo>
33#include <QFile>
30#include <sink/store.h> 34#include <sink/store.h>
31#include <sink/log.h> 35#include <sink/log.h>
32 36
@@ -84,8 +88,16 @@ ComposerController::ComposerController()
84 mIdentitySelector{new IdentitySelector{*this}}, 88 mIdentitySelector{new IdentitySelector{*this}},
85 mToModel{new QStringListModel}, 89 mToModel{new QStringListModel},
86 mCcModel{new QStringListModel}, 90 mCcModel{new QStringListModel},
87 mBccModel{new QStringListModel} 91 mBccModel{new QStringListModel},
92 mAttachmentModel{new QStandardItemModel}
88{ 93{
94 mAttachmentModel->setItemRoleNames({{NameRole, "name"},
95 {FilenameRole, "filename"},
96 {ContentRole, "content"},
97 {MimeTypeRole, "mimetype"},
98 {DescriptionRole, "description"},
99 {IconNameRole, "iconName"},
100 {InlineRole, "inline"}});
89 updateSaveAsDraftAction(); 101 updateSaveAsDraftAction();
90 // mSendAction->monitorProperty<To>(); 102 // mSendAction->monitorProperty<To>();
91 // mSendAction->monitorProperty<Send>([] (const QString &) -> bool{ 103 // mSendAction->monitorProperty<Send>([] (const QString &) -> bool{
@@ -175,6 +187,46 @@ void ComposerController::removeBcc(const QString &s)
175 updateSendAction(); 187 updateSendAction();
176} 188}
177 189
190QAbstractItemModel *ComposerController::attachmentModel() const
191{
192 return mAttachmentModel.data();
193}
194
195void ComposerController::addAttachment(const QUrl &url)
196{
197 QMimeDatabase db;
198 auto mimeType = db.mimeTypeForUrl(url);
199 if (mimeType.name() == QLatin1String("inode/directory")) {
200 qWarning() << "Can't deal with directories yet.";
201 } else {
202 if (!url.isLocalFile()) {
203 qWarning() << "Cannot attach remote file: " << url;
204 return;
205 }
206
207 QFileInfo fileInfo(url.toLocalFile());
208 if (!fileInfo.exists()) {
209 qWarning() << "The file doesn't exist: " << url;
210 }
211
212 QFile file{fileInfo.filePath()};
213 file.open(QIODevice::ReadOnly);
214 const auto data = file.readAll();
215 auto item = new QStandardItem;
216 item->setData(fileInfo.fileName(), NameRole);
217 item->setData(mimeType.name().toLatin1(), MimeTypeRole);
218 item->setData(fileInfo.fileName(), FilenameRole);
219 item->setData(false, InlineRole);
220 item->setData(mimeType.iconName(), IconNameRole);
221 item->setData(data, ContentRole);
222 mAttachmentModel->appendRow(item);
223 }
224}
225
226void ComposerController::removeAttachment(const QString &s)
227{
228}
229
178Completer *ComposerController::recipientCompleter() const 230Completer *ComposerController::recipientCompleter() const
179{ 231{
180 return mRecipientCompleter.data(); 232 return mRecipientCompleter.data();
@@ -215,6 +267,56 @@ static QStringList getStringListFromAddresses(const QString &s)
215 return list; 267 return list;
216} 268}
217 269
270void ComposerController::addAttachmentPart(KMime::Content *partToAttach)
271{
272 auto item = new QStandardItem;
273
274 if (partToAttach->contentType()->mimeType() == "multipart/digest" ||
275 partToAttach->contentType()->mimeType() == "message/rfc822") {
276 // if it is a digest or a full message, use the encodedContent() of the attachment,
277 // which already has the proper headers
278 item->setData(partToAttach->encodedContent(), ContentRole);
279 } else {
280 item->setData(partToAttach->decodedContent(), ContentRole);
281 }
282 item->setData(partToAttach->contentType()->mimeType(), MimeTypeRole);
283
284 QMimeDatabase db;
285 auto mimeType = db.mimeTypeForName(partToAttach->contentType()->mimeType());
286 item->setData(mimeType.iconName(), IconNameRole);
287
288 if (partToAttach->contentDescription(false)) {
289 item->setData(partToAttach->contentDescription()->asUnicodeString(), DescriptionRole);
290 }
291 QString name;
292 QString filename;
293 if (partToAttach->contentType(false)) {
294 if (partToAttach->contentType()->hasParameter(QStringLiteral("name"))) {
295 name = partToAttach->contentType()->parameter(QStringLiteral("name"));
296 }
297 }
298 if (partToAttach->contentDisposition(false)) {
299 filename = partToAttach->contentDisposition()->filename();
300 item->setData(partToAttach->contentDisposition()->disposition() == KMime::Headers::CDinline, InlineRole);
301 }
302
303 if (name.isEmpty() && !filename.isEmpty()) {
304 name = filename;
305 }
306 if (filename.isEmpty() && !name.isEmpty()) {
307 filename = name;
308 }
309
310 if (!filename.isEmpty()) {
311 item->setData(filename, FilenameRole);
312 }
313 if (!name.isEmpty()) {
314 item->setData(name, NameRole);
315 }
316
317 mAttachmentModel->appendRow(item);
318}
319
218void ComposerController::setMessage(const KMime::Message::Ptr &msg) 320void ComposerController::setMessage(const KMime::Message::Ptr &msg)
219{ 321{
220 mToModel->setStringList(getStringListFromAddresses(msg->to(true)->asUnicodeString())); 322 mToModel->setStringList(getStringListFromAddresses(msg->to(true)->asUnicodeString()));
@@ -223,6 +325,12 @@ void ComposerController::setMessage(const KMime::Message::Ptr &msg)
223 325
224 setSubject(msg->subject(true)->asUnicodeString()); 326 setSubject(msg->subject(true)->asUnicodeString());
225 setBody(msg->body()); 327 setBody(msg->body());
328
329 //TODO use ObjecTreeParser to get encrypted attachments as well
330 foreach (const auto &att, msg->attachments()) {
331 addAttachmentPart(att);
332 }
333
226 setExistingMessage(msg); 334 setExistingMessage(msg);
227} 335}
228 336
@@ -236,7 +344,6 @@ void ComposerController::loadMessage(const QVariant &message, bool loadAsDraft)
236 Store::fetchOne<Mail>(query).then([this, loadAsDraft](const Mail &mail) { 344 Store::fetchOne<Mail>(query).then([this, loadAsDraft](const Mail &mail) {
237 setExistingMail(mail); 345 setExistingMail(mail);
238 346
239 //TODO this should probably happen as reaction to the property being set.
240 const auto mailData = KMime::CRLFtoLF(mail.getMimeMessage()); 347 const auto mailData = KMime::CRLFtoLF(mail.getMimeMessage());
241 if (!mailData.isEmpty()) { 348 if (!mailData.isEmpty()) {
242 KMime::Message::Ptr mail(new KMime::Message); 349 KMime::Message::Ptr mail(new KMime::Message);
@@ -285,7 +392,6 @@ KMime::Message::Ptr ComposerController::assembleMessage()
285 mail->from(true)->addAddress(getIdentity()); 392 mail->from(true)->addAddress(getIdentity());
286 393
287 mail->subject(true)->fromUnicodeString(getSubject(), "utf-8"); 394 mail->subject(true)->fromUnicodeString(getSubject(), "utf-8");
288 mail->setBody(getBody().toUtf8());
289 if (!mail->messageID()) { 395 if (!mail->messageID()) {
290 mail->messageID(true)->generate("org.kde.kube"); 396 mail->messageID(true)->generate("org.kde.kube");
291 } 397 }
@@ -293,6 +399,45 @@ KMime::Message::Ptr ComposerController::assembleMessage()
293 mail->date(true)->setDateTime(QDateTime::currentDateTimeUtc()); 399 mail->date(true)->setDateTime(QDateTime::currentDateTimeUtc());
294 } 400 }
295 401
402 auto root = mAttachmentModel->invisibleRootItem();
403 if (root->hasChildren()) {
404 mail->contentType(true)->setMimeType("multipart/mixed");
405 mail->contentType()->setBoundary(KMime::multiPartBoundary());
406 mail->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit);
407 mail->setPreamble("This is a multi-part message in MIME format.\n");
408 for (int row = 0; row < root->rowCount(); row++) {
409 auto item = root->child(row, 0);
410 const auto name = item->data(NameRole).toString();
411 const auto filename = item->data(FilenameRole).toString();
412 const auto mimeType = item->data(MimeTypeRole).toByteArray();
413 const auto isInline = item->data(InlineRole).toBool();
414 const auto content = item->data(ContentRole).toByteArray();
415
416 KMime::Content *part = new KMime::Content;
417 part->contentDisposition(true)->setFilename(filename);
418 if (isInline) {
419 part->contentDisposition(true)->setDisposition(KMime::Headers::CDinline);
420 } else {
421 part->contentDisposition(true)->setDisposition(KMime::Headers::CDattachment);
422 }
423 part->contentType(true)->setMimeType(mimeType);
424 part->contentType(true)->setName(name, "utf-8");
425 //Just always encode attachments base64 so it's safe for binary data
426 part->contentTransferEncoding(true)->setEncoding(KMime::Headers::CEbase64);
427 part->setBody(content);
428
429 mail->addContent(part);
430
431 auto mainMessage = new KMime::Content;
432 mainMessage->setBody(getBody().toUtf8());
433 mainMessage->contentType(true)->setMimeType("text/plain");
434 mail->addContent(mainMessage);
435 }
436 } else {
437 //FIXME same implementation as above for attachments
438 mail->setBody(getBody().toUtf8());
439 }
440
296 mail->assemble(); 441 mail->assemble();
297 return mail; 442 return mail;
298} 443}
diff --git a/framework/src/domain/composercontroller.h b/framework/src/domain/composercontroller.h
index 87349d0c..de154b66 100644
--- a/framework/src/domain/composercontroller.h
+++ b/framework/src/domain/composercontroller.h
@@ -24,6 +24,7 @@
24#include <QString> 24#include <QString>
25#include <QStringList> 25#include <QStringList>
26#include <QStringListModel> 26#include <QStringListModel>
27#include <QStandardItemModel>
27#include <QVariant> 28#include <QVariant>
28#include <sink/applicationdomaintype.h> 29#include <sink/applicationdomaintype.h>
29#include <KMime/Message> 30#include <KMime/Message>
@@ -66,6 +67,7 @@ class ComposerController : public Kube::Controller
66 Q_PROPERTY (QAbstractItemModel* toModel READ toModel CONSTANT) 67 Q_PROPERTY (QAbstractItemModel* toModel READ toModel CONSTANT)
67 Q_PROPERTY (QAbstractItemModel* ccModel READ ccModel CONSTANT) 68 Q_PROPERTY (QAbstractItemModel* ccModel READ ccModel CONSTANT)
68 Q_PROPERTY (QAbstractItemModel* bccModel READ bccModel CONSTANT) 69 Q_PROPERTY (QAbstractItemModel* bccModel READ bccModel CONSTANT)
70 Q_PROPERTY (QAbstractItemModel* attachmentModel READ attachmentModel CONSTANT)
69 71
70 KUBE_CONTROLLER_ACTION(send) 72 KUBE_CONTROLLER_ACTION(send)
71 KUBE_CONTROLLER_ACTION(saveAsDraft) 73 KUBE_CONTROLLER_ACTION(saveAsDraft)
@@ -81,6 +83,7 @@ public:
81 QAbstractItemModel *toModel() const; 83 QAbstractItemModel *toModel() const;
82 QAbstractItemModel *ccModel() const; 84 QAbstractItemModel *ccModel() const;
83 QAbstractItemModel *bccModel() const; 85 QAbstractItemModel *bccModel() const;
86 QAbstractItemModel *attachmentModel() const;
84 87
85 Q_INVOKABLE void addTo(const QString &); 88 Q_INVOKABLE void addTo(const QString &);
86 Q_INVOKABLE void removeTo(const QString &); 89 Q_INVOKABLE void removeTo(const QString &);
@@ -88,6 +91,8 @@ public:
88 Q_INVOKABLE void removeCc(const QString &); 91 Q_INVOKABLE void removeCc(const QString &);
89 Q_INVOKABLE void addBcc(const QString &); 92 Q_INVOKABLE void addBcc(const QString &);
90 Q_INVOKABLE void removeBcc(const QString &); 93 Q_INVOKABLE void removeBcc(const QString &);
94 Q_INVOKABLE void addAttachment(const QUrl &);
95 Q_INVOKABLE void removeAttachment(const QString &);
91 96
92public slots: 97public slots:
93 virtual void clear() Q_DECL_OVERRIDE; 98 virtual void clear() Q_DECL_OVERRIDE;
@@ -97,8 +102,19 @@ private slots:
97 void updateSaveAsDraftAction(); 102 void updateSaveAsDraftAction();
98 103
99private: 104private:
105 enum AttachmentRoles {
106 NameRole = Qt::UserRole + 1,
107 FilenameRole,
108 ContentRole,
109 MimeTypeRole,
110 DescriptionRole,
111 InlineRole,
112 IconNameRole
113 };
114
100 void recordForAutocompletion(const QByteArray &addrSpec, const QByteArray &displayName); 115 void recordForAutocompletion(const QByteArray &addrSpec, const QByteArray &displayName);
101 void setMessage(const QSharedPointer<KMime::Message> &msg); 116 void setMessage(const QSharedPointer<KMime::Message> &msg);
117 void addAttachmentPart(KMime::Content *partToAttach);
102 KMime::Message::Ptr assembleMessage(); 118 KMime::Message::Ptr assembleMessage();
103 119
104 QScopedPointer<Completer> mRecipientCompleter; 120 QScopedPointer<Completer> mRecipientCompleter;
@@ -106,4 +122,5 @@ private:
106 QScopedPointer<QStringListModel> mToModel; 122 QScopedPointer<QStringListModel> mToModel;
107 QScopedPointer<QStringListModel> mCcModel; 123 QScopedPointer<QStringListModel> mCcModel;
108 QScopedPointer<QStringListModel> mBccModel; 124 QScopedPointer<QStringListModel> mBccModel;
125 QScopedPointer<QStandardItemModel> mAttachmentModel;
109}; 126};