summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Vrátil <dvratil@kde.org>2016-08-02 07:34:19 (GMT)
committerDaniel Vrátil <dvratil@kde.org>2016-08-02 07:34:19 (GMT)
commita84cfe5d03185ccf6cec0e6358fa3225d336d0ac (patch)
treeae0e5e0a4b09bdd89c65982f71aac7145ab0991e
parent42d2ecb22f57e242f783d9c478f8b0660df29f5f (diff)
Allow only one recipient per line in RecipientsEditor
For the opportunistic encryption we want to show an icon next to each recipient showing status of the encryption key we are going to use. It is however not possible to put multiple icons mid-text into K(Q)LineEdit, only at the beginning of the line which is a problem if there are multiple recipients in a single line. The RecipientsEditor already jumps to next line automatically when you hit select a contact from autocompletion, so this patch applies the same behaviour to manual input as well. Just typing a comma (or semicolon) or pasting a comma-separeted list of recipients into the recipient edit will automatically split the input to multiple lines, having one recipient per line. Differential Review: https://phabricator.kde.org/D2313
-rw-r--r--CMakeLists.txt4
-rw-r--r--messagecomposer/autotests/CMakeLists.txt2
-rw-r--r--messagecomposer/autotests/recipientseditortest.cpp133
-rw-r--r--messagecomposer/src/recipient/recipientline.cpp25
-rw-r--r--messagecomposer/src/recipient/recipientline.h10
-rw-r--r--messagecomposer/src/recipient/recipientseditor.cpp63
-rw-r--r--messagecomposer/src/recipient/recipientseditor.h2
7 files changed, 233 insertions, 6 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index be8ace8..abc996b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,7 +18,7 @@ include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
include(ECMQtDeclareLoggingCategory)
include(ECMAddTests)
-set(PIM_VERSION "5.3.45")
+set(PIM_VERSION "5.3.46")
set(MESSAGELIB_LIB_VERSION ${PIM_VERSION})
set(AKONADIMIME_LIB_VERSION "5.3.40")
@@ -37,7 +37,7 @@ set(KMAILTRANSPORT_LIB_VERSION "5.3.40")
set(KMBOX_LIB_VERSION "5.3.40")
set(KMIME_LIB_VERSION "5.3.40")
set(KPIMTEXTEDIT_LIB_VERSION "5.3.40")
-set(LIBKDEPIM_LIB_VERSION "5.3.40")
+set(LIBKDEPIM_LIB_VERSION "5.3.41")
set(LIBKLEO_LIB_VERSION "5.3.40")
set(PIMCOMMON_LIB_VERSION "5.3.41")
diff --git a/messagecomposer/autotests/CMakeLists.txt b/messagecomposer/autotests/CMakeLists.txt
index 42fbc6c..73c4354 100644
--- a/messagecomposer/autotests/CMakeLists.txt
+++ b/messagecomposer/autotests/CMakeLists.txt
@@ -61,6 +61,8 @@ add_messagecomposer_test( textparttest.cpp )
add_messagecomposer_test( globalparttest.cpp )
add_messagecomposer_test( composerviewbasetest.cpp )
+add_messagecomposer_test( recipientseditortest.cpp )
+
# Crypto
add_messagecomposer_cryptotest( signjobtest.cpp )
add_messagecomposer_cryptotest( encryptjobtest.cpp )
diff --git a/messagecomposer/autotests/recipientseditortest.cpp b/messagecomposer/autotests/recipientseditortest.cpp
new file mode 100644
index 0000000..403f083
--- /dev/null
+++ b/messagecomposer/autotests/recipientseditortest.cpp
@@ -0,0 +1,133 @@
+/*
+ Copyright (C) 2016 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include <QObject>
+#include <QTest>
+
+#include "recipient/recipientseditor.h"
+#include "recipient/recipientline.h"
+
+#include <KMime/Types>
+#include <QClipboard>
+#include <QSignalSpy>
+
+class RecipientsLineTestFactory : public MessageComposer::RecipientLineFactory
+{
+ Q_OBJECT
+
+public:
+ explicit RecipientsLineTestFactory(QObject *parent = Q_NULLPTR)
+ : MessageComposer::RecipientLineFactory(parent)
+ {
+ }
+
+ KPIM::MultiplyingLine *newLine(QWidget *parent) Q_DECL_OVERRIDE
+ {
+ auto line = qobject_cast<MessageComposer::RecipientLineNG*>(
+ MessageComposer::RecipientLineFactory::newLine(parent));
+ line->setEnableAkonadiSearch(false);
+ line->setEnableIndexSearch(false);
+ return line;
+ }
+};
+
+
+
+class RecipientsEditorTest : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void test_addLineOnCommaPress();
+ void test_splitStringInputToLines();
+ void test_splitPastedListToLines();
+};
+
+
+void RecipientsEditorTest::test_addLineOnCommaPress()
+{
+ MessageComposer::RecipientsEditor editor(new RecipientsLineTestFactory());
+ editor.show();
+ QTest::qWaitForWindowActive(&editor);
+
+ QCOMPARE(editor.recipients().size(), 0);
+
+ auto lineEdit = editor.lines().first()->findChild<MessageComposer::RecipientLineEdit*>();
+ lineEdit->setFocus();
+
+ // Simulate typing email address
+ QTest::keyClicks(lineEdit, QStringLiteral("\"Vratil, Daniel\" <dvratil@kde.org>"), Qt::NoModifier, 10);
+
+ QCOMPARE(editor.recipients().size(), 1);
+ QCOMPARE(editor.recipients().first()->email(), QStringLiteral("dvratil@kde.org"));
+
+ QTest::keyClick(lineEdit, Qt::Key_Comma, Qt::NoModifier, 0);
+
+ lineEdit = editor.lines().at(1)->findChild<MessageComposer::RecipientLineEdit*>();
+ QVERIFY(lineEdit->hasFocus());
+
+ QTest::keyClicks(lineEdit, QStringLiteral("test@example.test"), Qt::NoModifier, 10);
+ QCOMPARE(editor.recipients().size(), 2);
+ QCOMPARE(editor.recipients().at(1)->email(), QStringLiteral("test@example.test"));
+ QCOMPARE(editor.recipients().at(1)->type(), editor.recipients().at(0)->type());
+}
+
+void RecipientsEditorTest::test_splitStringInputToLines()
+{
+ MessageComposer::RecipientsEditor editor(new RecipientsLineTestFactory());
+
+ QCOMPARE(editor.recipients().size(), 0);
+
+ const auto mboxes = KMime::Types::Mailbox::listFromUnicodeString(QStringLiteral("test@example.com, \"Vrátil, Daniel\" <dvratil@kde.org>"));
+ editor.setRecipientString(mboxes, MessageComposer::Recipient::To);
+
+ QCOMPARE(editor.recipients().size(), 2);
+ QCOMPARE(editor.recipients().at(0)->email(), QStringLiteral("test@example.com"));
+ QCOMPARE(editor.recipients().at(1)->email(), QStringLiteral("dvratil@kde.org"));
+}
+
+void RecipientsEditorTest::test_splitPastedListToLines()
+{
+ MessageComposer::RecipientsEditor editor(new RecipientsLineTestFactory());
+
+ QCOMPARE(editor.recipients().size(), 0);
+
+ const auto clipboard = QApplication::clipboard();
+ const QString oldText = clipboard->text();
+
+ clipboard->setText(QStringLiteral("test@example.com, \"Vrátil, Daniel\" <dvratil@kde.org>"));
+
+
+ auto lineEdit = editor.lines().at(0)->findChild<MessageComposer::RecipientLineEdit*>();
+ // paste() is protected in KPIM::AddresseeLineEdit
+ QMetaObject::invokeMethod(lineEdit, "paste");
+
+ // This will still fail the test, but will allow us to reset the clipboard
+ [&editor]() {
+ QCOMPARE(editor.recipients().size(), 2);
+ QCOMPARE(editor.recipients().at(0)->email(), QStringLiteral("test@example.com"));
+ QCOMPARE(editor.recipients().at(1)->email(), QStringLiteral("dvratil@kde.org"));
+ }();
+
+ clipboard->setText(oldText);
+}
+
+QTEST_MAIN(RecipientsEditorTest)
+
+#include "recipientseditortest.moc"
diff --git a/messagecomposer/src/recipient/recipientline.cpp b/messagecomposer/src/recipient/recipientline.cpp
index 3751c7b..9eb694e 100644
--- a/messagecomposer/src/recipient/recipientline.cpp
+++ b/messagecomposer/src/recipient/recipientline.cpp
@@ -267,3 +267,28 @@ void RecipientLineNG::setIcon(const QIcon &icon, const QString &tooltip)
{
mEdit->setIcon(icon, tooltip);
}
+
+void RecipientLineNG::setEnableIndexSearch(bool enableIndexSearch)
+{
+ mEdit->setEnableBalooSearch(enableIndexSearch);
+}
+
+bool RecipientLineNG::enableIndexSearch() const
+{
+ return mEdit->enableBalooSearch();
+}
+
+void RecipientLineNG::setEnableAkonadiSearch(bool enableAkonadiSearch)
+{
+ mEdit->setEnableAkonadiSearch(enableAkonadiSearch);
+}
+
+bool RecipientLineNG::enableAkonadiSearch() const
+{
+ return mEdit->enableAkonadiSearch();
+}
+
+QString RecipientLineNG::rawData() const
+{
+ return mEdit->text();
+}
diff --git a/messagecomposer/src/recipient/recipientline.h b/messagecomposer/src/recipient/recipientline.h
index 33557be..3445e16 100644
--- a/messagecomposer/src/recipient/recipientline.h
+++ b/messagecomposer/src/recipient/recipientline.h
@@ -46,7 +46,7 @@ protected:
void keyPressEvent(QKeyEvent *ev) Q_DECL_OVERRIDE;
};
-class RecipientLineEdit : public MessageComposer::ComposerLineEdit
+class MESSAGECOMPOSER_EXPORT RecipientLineEdit : public MessageComposer::ComposerLineEdit
{
Q_OBJECT
public:
@@ -101,6 +101,14 @@ public:
*/
void setRecentAddressConfig(KConfig *config);
+ void setEnableIndexSearch(bool enableIndexSearch);
+ bool enableIndexSearch() const;
+
+ void setEnableAkonadiSearch(bool enableAkonadiSearch);
+ bool enableAkonadiSearch() const;
+
+ QString rawData() const;
+
Q_SIGNALS:
void typeModified(RecipientLineNG *);
void addRecipient(RecipientLineNG *, const QString &);
diff --git a/messagecomposer/src/recipient/recipientseditor.cpp b/messagecomposer/src/recipient/recipientseditor.cpp
index c0d660e..7b769e6 100644
--- a/messagecomposer/src/recipient/recipientseditor.cpp
+++ b/messagecomposer/src/recipient/recipientseditor.cpp
@@ -36,9 +36,12 @@
#include <KMime/Headers>
#include <KLocalizedString>
#include <KMessageBox>
+#include <KEmailAddress>
#include <QLayout>
#include <QDebug>
+#include <QKeyEvent>
+#include <QRegularExpression>
using namespace MessageComposer;
using namespace KPIM;
@@ -70,16 +73,23 @@ class MessageComposer::RecipientsEditorPrivate
public:
RecipientsEditorPrivate()
: mRecentAddressConfig(Q_NULLPTR),
- mSideWidget(Q_NULLPTR)
+ mSideWidget(Q_NULLPTR),
+ mSkipTotal(false)
{
}
KConfig *mRecentAddressConfig;
RecipientsEditorSideWidget *mSideWidget;
+ bool mSkipTotal;
};
RecipientsEditor::RecipientsEditor(QWidget *parent)
- : MultiplyingLineEditor(new RecipientLineFactory(Q_NULLPTR), parent),
+ : RecipientsEditor(new RecipientLineFactory(Q_NULLPTR), parent)
+{
+}
+
+RecipientsEditor::RecipientsEditor(RecipientLineFactory *lineFactory, QWidget *parent)
+ : MultiplyingLineEditor(lineFactory, parent),
d(new MessageComposer::RecipientsEditorPrivate)
{
factory()->setParent(this); // HACK: can't use 'this' above since it's not yet constructed at that point
@@ -87,6 +97,12 @@ RecipientsEditor::RecipientsEditor(QWidget *parent)
layout()->addWidget(d->mSideWidget);
+ // Install global event filter and listen for keypress events for RecipientLineEdits.
+ // Unfortunately we can't install ourselves directly as event filter for the edits,
+ // because the RecipientLineEdit has its own event filter installed into QApplication
+ // and so it would eat the event before it would reach us.
+ qApp->installEventFilter(this);
+
connect(d->mSideWidget, &RecipientsEditorSideWidget::pickedRecipient, this, &RecipientsEditor::slotPickedRecipient);
connect(d->mSideWidget, &RecipientsEditorSideWidget::saveDistributionList, this, &RecipientsEditor::saveDistributionList);
@@ -286,11 +302,38 @@ void RecipientsEditor::slotLineDeleted(int pos)
slotCalculateTotal();
}
+bool RecipientsEditor::eventFilter(QObject *object, QEvent *event)
+{
+ if (event->type() == QEvent::KeyPress && qobject_cast<RecipientLineEdit*>(object)) {
+ auto ke = static_cast<QKeyEvent*>(event);
+ // Treats comma or semicolon as email separator, will automatically move focus
+ // to a new line, basically preventing user from inputting more than one
+ // email address per line, which breaks our opportunistic crypto in composer
+ if (ke->key() == Qt::Key_Comma || (
+ ke->key() == Qt::Key_Semicolon && MessageComposerSettings::self()->allowSemicolonAsAddressSeparator())) {
+ auto line = qobject_cast<RecipientLineNG*>(object->parent());
+ const auto split = KEmailAddress::splitAddressList(line->rawData() + QLatin1String(", "));
+ if (split.size() > 1) {
+ addRecipient(QString(), line->recipientType());
+ setFocusBottom();
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
void RecipientsEditor::slotCalculateTotal()
{
int count = 0;
int empty = 0;
+ // Prevent endless recursion when splitting recipient
+ if (d->mSkipTotal) {
+ return;
+ }
+
MultiplyingLine *line;
foreach (line, lines()) {
RecipientLineNG *rec = qobject_cast< RecipientLineNG * >(line);
@@ -298,7 +341,21 @@ void RecipientsEditor::slotCalculateTotal()
if (rec->isEmpty()) {
++empty;
} else {
- count += rec->recipientsCount();
+ const int recipientsCount = rec->recipientsCount();
+ if (recipientsCount > 1) {
+ // Ensure we always have only one recipient per line
+ d->mSkipTotal = true;
+ Recipient::Ptr recipient = rec->recipient();
+ const auto split = KEmailAddress::splitAddressList(recipient->email());
+ for (int i = 1 /* sic! */; i < split.count(); ++i) {
+ addRecipient(split[i], rec->recipientType());
+ }
+ recipient->setEmail(split[0]);
+ rec->setData(recipient);
+ setFocusBottom(); // focus next empty entry
+ d->mSkipTotal = false;
+ }
+ count += recipientsCount;
}
}
}
diff --git a/messagecomposer/src/recipient/recipientseditor.h b/messagecomposer/src/recipient/recipientseditor.h
index 08b5287..8ea9df2 100644
--- a/messagecomposer/src/recipient/recipientseditor.h
+++ b/messagecomposer/src/recipient/recipientseditor.h
@@ -56,6 +56,7 @@ class MESSAGECOMPOSER_EXPORT RecipientsEditor : public KPIM::MultiplyingLineEdit
Q_OBJECT
public:
explicit RecipientsEditor(QWidget *parent = Q_NULLPTR);
+ explicit RecipientsEditor(RecipientLineFactory *lineFactory, QWidget *parent = Q_NULLPTR);
~RecipientsEditor();
Recipient::List recipients() const;
@@ -96,6 +97,7 @@ protected Q_SLOTS:
void addRecipient(RecipientLineNG *, const QString &);
protected:
+ bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE;
RecipientLineNG *activeLine() const Q_DECL_OVERRIDE;
private: