summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/package/contents/ui/AutocompleteLineEdit.qml144
-rw-r--r--components/package/contents/ui/FocusComposer.qml35
-rw-r--r--framework/domain/CMakeLists.txt1
-rw-r--r--framework/domain/composercontroller.cpp106
-rw-r--r--framework/domain/composercontroller.h8
-rw-r--r--framework/domain/recepientautocompletionmodel.cpp110
-rw-r--r--framework/domain/recepientautocompletionmodel.h55
7 files changed, 426 insertions, 33 deletions
diff --git a/components/package/contents/ui/AutocompleteLineEdit.qml b/components/package/contents/ui/AutocompleteLineEdit.qml
new file mode 100644
index 00000000..173a006c
--- /dev/null
+++ b/components/package/contents/ui/AutocompleteLineEdit.qml
@@ -0,0 +1,144 @@
1/*
2 Copyright (C) 2016 Christian Mollekopf, <mollekopf@kolabsys.com>
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License along
15 with this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17*/
18
19import QtQuick 2.7
20import QtQuick.Controls 1.4
21import QtQuick.Controls 2.0 as Controls2
22import QtQuick.Layouts 1.1
23import QtQuick.Dialogs 1.0
24
25import org.kde.kirigami 1.0 as Kirigami
26
27TextField {
28 id: textField
29
30 property string searchTerm
31 property variant model
32 onTextChanged: {
33 if (text.length >= 2) {
34 searchTerm = text
35 startCompleting()
36 } else {
37 searchTerm = ""
38 abort()
39 }
40 }
41 Keys.onDownPressed: {
42 listView.incrementCurrentIndex()
43 }
44 Keys.onUpPressed: {
45 listView.decrementCurrentIndex()
46 }
47 Keys.onRightPressed: {
48 startCompleting()
49 }
50 Keys.onTabPressed: {
51 if (popup.visible) {
52 listView.incrementCurrentIndex()
53 } else {
54 event.accepted = false
55 }
56 }
57 Keys.onReturnPressed: {
58 accept()
59 }
60 Keys.onEscapePressed: {
61 abort()
62 }
63
64 function startCompleting() {
65 if (!popup.visible) {
66 popup.open()
67 listView.currentIndex = -1
68 }
69 }
70
71 function accept() {
72 textField.text = listView.currentItem.text;
73 popup.close()
74 }
75
76 function abort() {
77 popup.close()
78 }
79
80 Controls2.Popup {
81 id: popup
82 x: 0
83 y: textField.y + textField.height
84 padding: 0
85 contentWidth: rect.width
86 contentHeight: rect.height
87
88 Rectangle {
89 color: Kirigami.Theme.backgroundColor
90 id: rect
91 anchors.top: popup.top
92 anchors.left: popup.left
93 height: listView.contentHeight
94 width: textField.width
95 border.color: "Black"
96 radius: 5
97 ScrollView {
98 id: scrollView
99 anchors.fill: parent
100 ListView {
101 id: listView
102 height: childrenRect.height
103 width: scrollView.width
104 interactive: true
105 model: textField.model
106 delegate: Kirigami.AbstractListItem {
107 id: listDelegate
108 property string text: model.text
109
110 width: listView.width
111 height: textField.height
112
113 enabled: true
114 supportsMouseEvents: true
115
116 checked: listView.currentIndex == index
117 onClicked: {
118 listView.currentIndex = model.index
119 accept()
120 }
121
122 //Content
123 Item {
124 width: parent.width
125 height: parent.height
126
127 Column {
128 anchors {
129 verticalCenter: parent.verticalCenter
130 left: parent.left
131 }
132
133 Text{
134 text: model.text
135 color: listDelegate.checked ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor
136 }
137 }
138 }
139 }
140 }
141 }
142 }
143 }
144}
diff --git a/components/package/contents/ui/FocusComposer.qml b/components/package/contents/ui/FocusComposer.qml
index 741faeba..f2ec2826 100644
--- a/components/package/contents/ui/FocusComposer.qml
+++ b/components/package/contents/ui/FocusComposer.qml
@@ -93,29 +93,30 @@ Controls2.Popup {
93 text: "To" 93 text: "To"
94 } 94 }
95 95
96 RowLayout { 96 AutocompleteLineEdit {
97 Layout.fillWidth: true 97 id: to
98
99 Controls.TextField {
100 id: to
101 98
102 Layout.fillWidth: true 99 Layout.fillWidth: true
103 100
104 text: composer.to 101 text: composer.to
102 onTextChanged: {
103 composer.to = text;
104 }
105 105
106 onTextChanged: { 106 model: composer.recepientAutocompletionModel
107 composer.to = text; 107 onSearchTermChanged: {
108 } 108 composer.recepientSearchString = searchTerm
109 } 109 }
110 } 110 }
111 111
112
112 Kirigami.Label { 113 Kirigami.Label {
113 text: "Cc" 114 text: "Cc"
114 115
115 visible: cc.visible 116 visible: cc.visible
116 } 117 }
117 118
118 Controls.TextField { 119 AutocompleteLineEdit {
119 id: cc 120 id: cc
120 121
121 Layout.fillWidth: true 122 Layout.fillWidth: true
@@ -127,6 +128,11 @@ Controls2.Popup {
127 onTextChanged: { 128 onTextChanged: {
128 composer.cc = text; 129 composer.cc = text;
129 } 130 }
131
132 model: composer.recepientAutocompletionModel
133 onSearchTermChanged: {
134 composer.recepientSearchString = searchTerm
135 }
130 } 136 }
131 137
132 Kirigami.Label { 138 Kirigami.Label {
@@ -135,7 +141,7 @@ Controls2.Popup {
135 visible: bcc.visible 141 visible: bcc.visible
136 } 142 }
137 143
138 Controls.TextField { 144 AutocompleteLineEdit {
139 id: bcc 145 id: bcc
140 146
141 Layout.fillWidth: true 147 Layout.fillWidth: true
@@ -147,6 +153,11 @@ Controls2.Popup {
147 onTextChanged: { 153 onTextChanged: {
148 composer.bcc = text; 154 composer.bcc = text;
149 } 155 }
156
157 model: composer.recepientAutocompletionModel
158 onSearchTermChanged: {
159 composer.recepientSearchString = searchTerm
160 }
150 } 161 }
151 } 162 }
152 163
diff --git a/framework/domain/CMakeLists.txt b/framework/domain/CMakeLists.txt
index 094eac04..24fdb49f 100644
--- a/framework/domain/CMakeLists.txt
+++ b/framework/domain/CMakeLists.txt
@@ -18,6 +18,7 @@ set(mailplugin_SRCS
18 accountsmodel.cpp 18 accountsmodel.cpp
19 outboxmodel.cpp 19 outboxmodel.cpp
20 identitiesmodel.cpp 20 identitiesmodel.cpp
21 recepientautocompletionmodel.cpp
21 settings/accountsettings.cpp 22 settings/accountsettings.cpp
22) 23)
23find_package(KF5 REQUIRED COMPONENTS Package) 24find_package(KF5 REQUIRED COMPONENTS Package)
diff --git a/framework/domain/composercontroller.cpp b/framework/domain/composercontroller.cpp
index b2ad7ecc..7fd2593d 100644
--- a/framework/domain/composercontroller.cpp
+++ b/framework/domain/composercontroller.cpp
@@ -25,14 +25,20 @@
25#include <KMime/Message> 25#include <KMime/Message>
26#include <KCodecs/KEmailAddress> 26#include <KCodecs/KEmailAddress>
27#include <QVariant> 27#include <QVariant>
28#include <QSortFilterProxyModel>
29#include <QList>
28#include <QDebug> 30#include <QDebug>
29#include <QQmlEngine> 31#include <QQmlEngine>
30#include <sink/store.h> 32#include <sink/store.h>
33#include <sink/log.h>
31 34
32#include "accountsmodel.h" 35#include "accountsmodel.h"
33#include "identitiesmodel.h" 36#include "identitiesmodel.h"
37#include "recepientautocompletionmodel.h"
34#include "mailtemplates.h" 38#include "mailtemplates.h"
35 39
40SINK_DEBUG_AREA("composercontroller");
41
36ComposerController::ComposerController(QObject *parent) : QObject(parent) 42ComposerController::ComposerController(QObject *parent) : QObject(parent)
37{ 43{
38} 44}
@@ -102,6 +108,18 @@ void ComposerController::setBody(const QString &body)
102 } 108 }
103} 109}
104 110
111QString ComposerController::recepientSearchString() const
112{
113 return QString();
114}
115
116void ComposerController::setRecepientSearchString(const QString &s)
117{
118 if (auto model = static_cast<RecipientAutocompletionModel*>(recepientAutocompletionModel())) {
119 model->setFilter(s);
120 }
121}
122
105QAbstractItemModel *ComposerController::identityModel() const 123QAbstractItemModel *ComposerController::identityModel() const
106{ 124{
107 static auto model = new IdentitiesModel(); 125 static auto model = new IdentitiesModel();
@@ -109,6 +127,13 @@ QAbstractItemModel *ComposerController::identityModel() const
109 return model; 127 return model;
110} 128}
111 129
130QAbstractItemModel *ComposerController::recepientAutocompletionModel() const
131{
132 static auto model = new RecipientAutocompletionModel();
133 QQmlEngine::setObjectOwnership(model, QQmlEngine::CppOwnership);
134 return model;
135}
136
112QStringList ComposerController::attachemts() const 137QStringList ComposerController::attachemts() const
113{ 138{
114 return m_attachments; 139 return m_attachments;
@@ -153,43 +178,82 @@ void ComposerController::loadMessage(const QVariant &message, bool loadAsDraft)
153 }).exec(); 178 }).exec();
154} 179}
155 180
156KMime::Message::Ptr ComposerController::assembleMessage() 181void ComposerController::recordForAutocompletion(const QByteArray &addrSpec, const QByteArray &displayName)
157{ 182{
158 auto mail = m_msg.value<KMime::Message::Ptr>(); 183 if (auto model = static_cast<RecipientAutocompletionModel*>(recepientAutocompletionModel())) {
159 if (!mail) { 184 model->addEntry(addrSpec, displayName);
160 mail = KMime::Message::Ptr::create();
161 } 185 }
162 for (const auto &to : KEmailAddress::splitAddressList(m_to)) { 186}
187
188void applyAddresses(const QString &list, std::function<void(const QByteArray &, const QByteArray &)> callback)
189{
190 for (const auto &to : KEmailAddress::splitAddressList(list)) {
163 QByteArray displayName; 191 QByteArray displayName;
164 QByteArray addrSpec; 192 QByteArray addrSpec;
165 QByteArray comment; 193 QByteArray comment;
166 KEmailAddress::splitAddress(to.toUtf8(), displayName, addrSpec, comment); 194 KEmailAddress::splitAddress(to.toUtf8(), displayName, addrSpec, comment);
195 callback(addrSpec, displayName);
196 }
197}
198
199bool ComposerController::identityIsSet() const
200{
201 return (identityModel()->rowCount() > 0) && (m_currentAccountIndex >= 0);
202}
203
204KMime::Message::Ptr ComposerController::assembleMessage()
205{
206 auto mail = m_msg.value<KMime::Message::Ptr>();
207 if (!mail) {
208 mail = KMime::Message::Ptr::create();
209 }
210 applyAddresses(m_to, [&](const QByteArray &addrSpec, const QByteArray &displayName) {
167 mail->to(true)->addAddress(addrSpec, displayName); 211 mail->to(true)->addAddress(addrSpec, displayName);
212 recordForAutocompletion(addrSpec, displayName);
213 });
214 applyAddresses(m_cc, [&](const QByteArray &addrSpec, const QByteArray &displayName) {
215 mail->cc(true)->addAddress(addrSpec, displayName);
216 recordForAutocompletion(addrSpec, displayName);
217 });
218 applyAddresses(m_bcc, [&](const QByteArray &addrSpec, const QByteArray &displayName) {
219 mail->bcc(true)->addAddress(addrSpec, displayName);
220 recordForAutocompletion(addrSpec, displayName);
221 });
222 if (!identityIsSet()) {
223 SinkWarning() << "We don't have an identity to send the mail with.";
224 } else {
225 auto currentIndex = identityModel()->index(m_currentAccountIndex, 0);
226 KMime::Types::Mailbox mb;
227 mb.setName(currentIndex.data(IdentitiesModel::Username).toString());
228 mb.setAddress(currentIndex.data(IdentitiesModel::Address).toString().toUtf8());
229 mail->from(true)->addAddress(mb);
230 mail->subject(true)->fromUnicodeString(m_subject, "utf-8");
231 mail->setBody(m_body.toUtf8());
232 mail->assemble();
233 return mail;
168 } 234 }
169 auto currentIndex = identityModel()->index(m_currentAccountIndex, 0); 235 return KMime::Message::Ptr();
170 KMime::Types::Mailbox mb;
171 mb.setName(currentIndex.data(IdentitiesModel::Username).toString());
172 mb.setAddress(currentIndex.data(IdentitiesModel::Address).toString().toUtf8());
173 mail->from(true)->addAddress(mb);
174 mail->subject(true)->fromUnicodeString(m_subject, "utf-8");
175 mail->setBody(m_body.toUtf8());
176 mail->assemble();
177 return mail;
178} 236}
179 237
180void ComposerController::send() 238void ComposerController::send()
181{ 239{
182 auto mail = assembleMessage(); 240 auto mail = assembleMessage();
183 auto currentAccountId = identityModel()->index(m_currentAccountIndex, 0).data(IdentitiesModel::AccountId).toByteArray();
184 241
185 Kube::Context context; 242 //TODO deactivate action if we don't have the identiy set
186 context.setProperty("message", QVariant::fromValue(mail)); 243 if (!identityIsSet()) {
187 context.setProperty("accountId", QVariant::fromValue(currentAccountId)); 244 SinkWarning() << "We don't have an identity to send the mail with.";
245 } else {
246 auto currentAccountId = identityModel()->index(m_currentAccountIndex, 0).data(IdentitiesModel::AccountId).toByteArray();
188 247
189 qDebug() << "Current account " << currentAccountId; 248 Kube::Context context;
249 context.setProperty("message", QVariant::fromValue(mail));
250 context.setProperty("accountId", QVariant::fromValue(currentAccountId));
190 251
191 Kube::Action("org.kde.kube.actions.sendmail", context).execute(); 252 qDebug() << "Current account " << currentAccountId;
192 clear(); 253
254 Kube::Action("org.kde.kube.actions.sendmail", context).execute();
255 clear();
256 }
193} 257}
194 258
195void ComposerController::saveAsDraft() 259void ComposerController::saveAsDraft()
diff --git a/framework/domain/composercontroller.h b/framework/domain/composercontroller.h
index 8390c639..aa2ae0d7 100644
--- a/framework/domain/composercontroller.h
+++ b/framework/domain/composercontroller.h
@@ -38,6 +38,8 @@ class ComposerController : public QObject
38 Q_PROPERTY (QString bcc READ bcc WRITE setBcc NOTIFY bccChanged) 38 Q_PROPERTY (QString bcc READ bcc WRITE setBcc NOTIFY bccChanged)
39 Q_PROPERTY (QString subject READ subject WRITE setSubject NOTIFY subjectChanged) 39 Q_PROPERTY (QString subject READ subject WRITE setSubject NOTIFY subjectChanged)
40 Q_PROPERTY (QString body READ body WRITE setBody NOTIFY bodyChanged) 40 Q_PROPERTY (QString body READ body WRITE setBody NOTIFY bodyChanged)
41 Q_PROPERTY (QString recepientSearchString READ recepientSearchString WRITE setRecepientSearchString)
42 Q_PROPERTY (QAbstractItemModel* recepientAutocompletionModel READ recepientAutocompletionModel CONSTANT)
41 Q_PROPERTY (QAbstractItemModel* identityModel READ identityModel CONSTANT) 43 Q_PROPERTY (QAbstractItemModel* identityModel READ identityModel CONSTANT)
42 Q_PROPERTY (int currentIdentityIndex MEMBER m_currentAccountIndex) 44 Q_PROPERTY (int currentIdentityIndex MEMBER m_currentAccountIndex)
43 Q_PROPERTY (QStringList attachments READ attachemts NOTIFY attachmentsChanged) 45 Q_PROPERTY (QStringList attachments READ attachemts NOTIFY attachmentsChanged)
@@ -60,7 +62,11 @@ public:
60 QString body() const; 62 QString body() const;
61 void setBody(const QString &body); 63 void setBody(const QString &body);
62 64
65 QString recepientSearchString() const;
66 void setRecepientSearchString(const QString &body);
67
63 QAbstractItemModel *identityModel() const; 68 QAbstractItemModel *identityModel() const;
69 QAbstractItemModel *recepientAutocompletionModel() const;
64 70
65 QStringList attachemts() const; 71 QStringList attachemts() const;
66 Q_INVOKABLE void loadMessage(const QVariant &draft, bool loadAsDraft); 72 Q_INVOKABLE void loadMessage(const QVariant &draft, bool loadAsDraft);
@@ -81,6 +87,8 @@ public slots:
81 void addAttachment(const QUrl &fileUrl); 87 void addAttachment(const QUrl &fileUrl);
82 88
83private: 89private:
90 bool identityIsSet() const;
91 void recordForAutocompletion(const QByteArray &addrSpec, const QByteArray &displayName);
84 void setMessage(const QSharedPointer<KMime::Message> &msg); 92 void setMessage(const QSharedPointer<KMime::Message> &msg);
85 QSharedPointer<KMime::Message> assembleMessage(); 93 QSharedPointer<KMime::Message> assembleMessage();
86 QString m_to; 94 QString m_to;
diff --git a/framework/domain/recepientautocompletionmodel.cpp b/framework/domain/recepientautocompletionmodel.cpp
new file mode 100644
index 00000000..4e5fed95
--- /dev/null
+++ b/framework/domain/recepientautocompletionmodel.cpp
@@ -0,0 +1,110 @@
1/*
2 Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19#include "recepientautocompletionmodel.h"
20
21#include <QStandardItemModel>
22#include <QSettings>
23#include <QStandardPaths>
24#include <QSet>
25#include <QDebug>
26#include <QTimer>
27
28RecipientAutocompletionModel::RecipientAutocompletionModel(QObject *parent)
29 : QSortFilterProxyModel(),
30 mSourceModel(new QStandardItemModel),
31 mTimer(new QTimer)
32{
33 setSourceModel(mSourceModel.data());
34 setDynamicSortFilter(true);
35 setFilterCaseSensitivity(Qt::CaseInsensitive);
36 mTimer->setSingleShot(true);
37 QObject::connect(mTimer.data(), &QTimer::timeout, this, &RecipientAutocompletionModel::save);
38
39 load();
40}
41
42RecipientAutocompletionModel::~RecipientAutocompletionModel()
43{
44 save();
45}
46
47static QString getPath()
48{
49 return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/kube/recepientautocompletion.ini";
50}
51
52void RecipientAutocompletionModel::save()
53{
54 QSet<QString> list;
55 for (int row = 0; row < mSourceModel->rowCount(); row++) {
56 list << mSourceModel->item(row)->data(Text).toString();
57 }
58
59 qWarning() << "Path " << getPath();
60 QSettings settings(getPath(), QSettings::IniFormat);
61 settings.setValue("list", QStringList{list.toList()});
62}
63
64void RecipientAutocompletionModel::load()
65{
66 qWarning() << "Path " << getPath();
67 QSettings settings(getPath(), QSettings::IniFormat);
68 auto list = settings.value("list").toStringList();
69 auto add = [] (const QString &n) {
70 auto item = new QStandardItem{n};
71 item->setData(n, Text);
72 return item;
73 };
74 for (const auto &entry : list) {
75 mSourceModel->appendRow(add(entry));
76 }
77}
78
79QHash< int, QByteArray > RecipientAutocompletionModel::roleNames() const
80{
81 QHash<int, QByteArray> roles;
82 roles[Text] = "text";
83 roles[Color] = "color";
84 return roles;
85}
86
87void RecipientAutocompletionModel::addEntry(const QByteArray &address, const QByteArray &name)
88{
89 auto add = [] (const QString &n) {
90 auto item = new QStandardItem{n};
91 item->setData(n, Text);
92 return item;
93 };
94 auto formattedName = [&] () {
95 if (name.isEmpty()) {
96 return QString(address);
97 }
98 return QString("%1 <%2>").arg(QString(address), QString(name));
99 }();
100 auto matches = mSourceModel->findItems(formattedName);
101 if (matches.isEmpty()) {
102 mSourceModel->appendRow(add(formattedName));
103 mTimer->start(100);
104 }
105}
106
107void RecipientAutocompletionModel::setFilter(const QString &filter)
108{
109 setFilterWildcard("*" + filter +"*");
110}
diff --git a/framework/domain/recepientautocompletionmodel.h b/framework/domain/recepientautocompletionmodel.h
new file mode 100644
index 00000000..7e89e513
--- /dev/null
+++ b/framework/domain/recepientautocompletionmodel.h
@@ -0,0 +1,55 @@
1/*
2 Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19
20#pragma once
21
22#include <QSortFilterProxyModel>
23#include <QScopedPointer>
24
25class QStandardItemModel;
26class QTimer;
27
28class RecipientAutocompletionModel : public QSortFilterProxyModel
29{
30 Q_OBJECT
31
32public:
33 RecipientAutocompletionModel(QObject *parent = Q_NULLPTR);
34 ~RecipientAutocompletionModel();
35
36 enum Roles {
37 Text = Qt::UserRole + 1,
38 Color
39 };
40 Q_ENUMS(Roles)
41
42 QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
43
44 void addEntry(const QByteArray &address, const QByteArray &name);
45 void setFilter(const QString &);
46
47private slots:
48 void save();
49
50private:
51 void load();
52
53 QScopedPointer<QStandardItemModel> mSourceModel;
54 QScopedPointer<QTimer> mTimer;
55};