1 /*
2     identitydialog.cpp
3 
4     This file is part of KMail, the KDE mail client.
5     SPDX-FileCopyrightText: 2002 Marc Mutz <mutz@kde.org>
6     SPDX-FileCopyrightText: 2014-2021 Laurent Montel <montel@kde.org>
7 
8     SPDX-License-Identifier: GPL-2.0-only
9 */
10 
11 #include "identitydialog.h"
12 #include "identityaddvcarddialog.h"
13 #include "identityeditvcarddialog.h"
14 #include "identityfolderrequester.h"
15 #include "identityinvalidfolder.h"
16 
17 #include <MessageComposer/MessageComposerSettings>
18 #include <QGpgME/Job>
19 #include <QGpgME/Protocol>
20 
21 #include <KIdentityManagement/IdentityManager>
22 
23 // other KMail headers:
24 #include "xfaceconfigurator.h"
25 #include <KEditListWidget>
26 #include <MailCommon/FolderRequester>
27 #include "kmkernel.h"
28 #include "settings/kmailsettings.h"
29 
30 #include <MailCommon/MailKernel>
31 
32 #include "job/addressvalidationjob.h"
33 #include "templatesconfiguration_kfg.h"
34 #include <MessageComposer/Kleo_Util>
35 #include <MessageCore/StringUtil>
36 #include <Sonnet/DictionaryComboBox>
37 #include <TemplateParser/TemplatesConfiguration>
38 // other kdepim headers:
39 #include <KIdentityManagement/Identity>
40 #include <KIdentityManagement/SignatureConfigurator>
41 
42 #include <PimCommon/AutoCorrectionLanguage>
43 #include <PimCommon/PimUtil>
44 
45 #include <Libkdepim/LineEditCatchReturnKey>
46 #include <PimCommonAkonadi/AddresseeLineEdit>
47 // libkleopatra:
48 #include <Libkleo/DefaultKeyFilter>
49 #include <Libkleo/DefaultKeyGenerationJob>
50 #include <Libkleo/KeySelectionCombo>
51 #include <Libkleo/ProgressDialog>
52 
53 // gpgme++
54 #include <gpgme++/keygenerationresult.h>
55 
56 #include <KEmailAddress>
57 #include <MailTransport/Transport>
58 #include <MailTransport/TransportComboBox>
59 #include <MailTransport/TransportManager>
60 #include <PimCommon/EmailValidator>
61 using MailTransport::TransportManager;
62 
63 // other KDE headers:
64 #include "kmail_debug.h"
65 #include <KLocalizedString>
66 #include <KMessageBox>
67 #include <QComboBox>
68 #include <QIcon>
69 #include <QPushButton>
70 #include <QTabWidget>
71 
72 // Qt headers:
73 #include <QCheckBox>
74 #include <QDir>
75 #include <QFile>
76 #include <QFileInfo>
77 #include <QFormLayout>
78 #include <QGridLayout>
79 #include <QHostInfo>
80 #include <QLabel>
81 #include <QToolButton>
82 #include <QVBoxLayout>
83 
84 // other headers:
85 #include <algorithm>
86 #include <gpgme++/key.h>
87 #include <iterator>
88 
89 #include <Akonadi/CollectionFetchJob>
90 #include <Akonadi/CollectionModifyJob>
91 #include <Akonadi/EntityDisplayAttribute>
92 #include <Akonadi/KMime/SpecialMailCollections>
93 #include <QDialogButtonBox>
94 #include <QStandardPaths>
95 
96 using namespace KPIM;
97 using namespace MailTransport;
98 using namespace MailCommon;
99 
100 namespace KMail
101 {
102 class KeySelectionCombo : public Kleo::KeySelectionCombo
103 {
104     Q_OBJECT
105 
106 public:
107     enum KeyType {
108         SigningKey,
109         EncryptionKey,
110     };
111 
112     explicit KeySelectionCombo(KeyType keyType, GpgME::Protocol protocol, QWidget *parent);
113     ~KeySelectionCombo() override;
114 
115     void setIdentity(const QString &name, const QString &email);
116 
117     void init() override;
118 
119 private:
120     void onCustomItemSelected(const QVariant &type);
121     QString mEmail;
122     QString mName;
123     const KeyType mKeyType;
124     const GpgME::Protocol mProtocol;
125 };
126 
127 class KeyGenerationJob : public QGpgME::Job
128 {
129     Q_OBJECT
130 
131 public:
132     explicit KeyGenerationJob(const QString &name, const QString &email, KeySelectionCombo *parent);
133     ~KeyGenerationJob() override;
134 
135     void slotCancel() override;
136     void start();
137 
138 private:
139     void keyGenerated(const GpgME::KeyGenerationResult &result);
140     const QString mName;
141     const QString mEmail;
142     QGpgME::Job *mJob = nullptr;
143 };
144 
KeyGenerationJob(const QString & name,const QString & email,KeySelectionCombo * parent)145 KeyGenerationJob::KeyGenerationJob(const QString &name, const QString &email, KeySelectionCombo *parent)
146     : QGpgME::Job(parent)
147     , mName(name)
148     , mEmail(email)
149 {
150 }
151 
152 KeyGenerationJob::~KeyGenerationJob() = default;
153 
slotCancel()154 void KeyGenerationJob::slotCancel()
155 {
156     if (mJob) {
157         mJob->slotCancel();
158     }
159 }
160 
start()161 void KeyGenerationJob::start()
162 {
163     auto job = new Kleo::DefaultKeyGenerationJob(this);
164     connect(job, &Kleo::DefaultKeyGenerationJob::result, this, &KeyGenerationJob::keyGenerated);
165     job->start(mEmail, mName);
166     mJob = job;
167 }
168 
keyGenerated(const GpgME::KeyGenerationResult & result)169 void KeyGenerationJob::keyGenerated(const GpgME::KeyGenerationResult &result)
170 {
171     mJob = nullptr;
172     if (result.error()) {
173         KMessageBox::error(qobject_cast<QWidget *>(parent()),
174                            i18n("Error while generating new key pair: %1", QString::fromUtf8(result.error().asString())),
175                            i18n("Key Generation Error"));
176         Q_EMIT done();
177         return;
178     }
179 
180     auto combo = qobject_cast<KeySelectionCombo *>(parent());
181     combo->setDefaultKey(QLatin1String(result.fingerprint()));
182     connect(combo, &KeySelectionCombo::keyListingFinished, this, &KeyGenerationJob::done);
183     combo->refreshKeys();
184 }
185 
KeySelectionCombo(KeyType keyType,GpgME::Protocol protocol,QWidget * parent)186 KeySelectionCombo::KeySelectionCombo(KeyType keyType, GpgME::Protocol protocol, QWidget *parent)
187     : Kleo::KeySelectionCombo(parent)
188     , mKeyType(keyType)
189     , mProtocol(protocol)
190 {
191 }
192 
193 KeySelectionCombo::~KeySelectionCombo() = default;
194 
setIdentity(const QString & name,const QString & email)195 void KeySelectionCombo::setIdentity(const QString &name, const QString &email)
196 {
197     mName = name;
198     mEmail = email;
199     setIdFilter(email);
200 }
201 
init()202 void KeySelectionCombo::init()
203 {
204     Kleo::KeySelectionCombo::init();
205 
206     std::shared_ptr<Kleo::DefaultKeyFilter> keyFilter(new Kleo::DefaultKeyFilter);
207     keyFilter->setIsOpenPGP(mProtocol == GpgME::OpenPGP ? Kleo::DefaultKeyFilter::Set : Kleo::DefaultKeyFilter::NotSet);
208     if (mKeyType == SigningKey) {
209         keyFilter->setCanSign(Kleo::DefaultKeyFilter::Set);
210         keyFilter->setHasSecret(Kleo::DefaultKeyFilter::Set);
211     } else {
212         keyFilter->setCanEncrypt(Kleo::DefaultKeyFilter::Set);
213     }
214     setKeyFilter(keyFilter);
215     prependCustomItem(QIcon(), i18n("No key"), QStringLiteral("no-key"));
216     if (mProtocol == GpgME::OpenPGP) {
217         appendCustomItem(QIcon::fromTheme(QStringLiteral("password-generate")), i18n("Generate a new key pair"), QStringLiteral("generate-new-key"));
218     }
219 
220     connect(this, &KeySelectionCombo::customItemSelected, this, &KeySelectionCombo::onCustomItemSelected);
221 }
222 
onCustomItemSelected(const QVariant & type)223 void KeySelectionCombo::onCustomItemSelected(const QVariant &type)
224 {
225     if (type == QLatin1String("no-key")) {
226         return;
227     } else if (type == QLatin1String("generate-new-key")) {
228         auto job = new KeyGenerationJob(mName, mEmail, this);
229         auto dlg = new Kleo::ProgressDialog(job, i18n("Generating new key pair..."), parentWidget());
230         dlg->setModal(true);
231         setEnabled(false);
232         connect(job, &KeyGenerationJob::done, this, [this]() {
233             setEnabled(true);
234         });
235         job->start();
236     }
237 }
238 
IdentityDialog(QWidget * parent)239 IdentityDialog::IdentityDialog(QWidget *parent)
240     : QDialog(parent)
241 {
242     setWindowTitle(i18nc("@title:window", "Edit Identity"));
243     auto mainLayout = new QVBoxLayout(this);
244 
245     auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help, this);
246     connect(buttonBox->button(QDialogButtonBox::Help), &QPushButton::clicked, this, &IdentityDialog::slotHelp);
247     QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
248     okButton->setDefault(true);
249     okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
250     connect(buttonBox, &QDialogButtonBox::accepted, this, &IdentityDialog::slotAccepted);
251     connect(buttonBox, &QDialogButtonBox::rejected, this, &IdentityDialog::reject);
252 
253     //
254     // Tab Widget: General
255     //
256     int row = -1;
257     auto page = new QWidget(this);
258     mainLayout->addWidget(page);
259     mainLayout->addWidget(buttonBox);
260     auto vlay = new QVBoxLayout(page);
261     vlay->setContentsMargins({});
262     mTabWidget = new QTabWidget(page);
263     mTabWidget->setObjectName(QStringLiteral("config-identity-tab"));
264     vlay->addWidget(mTabWidget);
265 
266     auto tab = new QWidget(mTabWidget);
267     mTabWidget->addTab(tab, i18nc("@title:tab General identity settings.", "General"));
268 
269     auto formLayout = new QFormLayout(tab);
270 
271     // "Name" line edit and label:
272     ++row;
273     mNameEdit = new QLineEdit(tab);
274     new LineEditCatchReturnKey(mNameEdit, this);
275     auto label = new QLabel(i18n("&Your name:"), tab);
276     formLayout->addRow(label, mNameEdit);
277     label->setBuddy(mNameEdit);
278 
279     QString msg = i18n(
280         "<qt><h3>Your name</h3>"
281         "<p>This field should contain your name as you would like "
282         "it to appear in the email header that is sent out;</p>"
283         "<p>if you leave this blank your real name will not "
284         "appear, only the email address.</p></qt>");
285     label->setWhatsThis(msg);
286     mNameEdit->setWhatsThis(msg);
287 
288     // "Organization" line edit and label:
289     ++row;
290     mOrganizationEdit = new QLineEdit(tab);
291     new LineEditCatchReturnKey(mOrganizationEdit, this);
292     label = new QLabel(i18n("Organi&zation:"), tab);
293     formLayout->addRow(label, mOrganizationEdit);
294     label->setBuddy(mOrganizationEdit);
295 
296     msg = i18n(
297         "<qt><h3>Organization</h3>"
298         "<p>This field should have the name of your organization "
299         "if you would like it to be shown in the email header that "
300         "is sent out.</p>"
301         "<p>It is safe (and normal) to leave this blank.</p></qt>");
302     label->setWhatsThis(msg);
303     mOrganizationEdit->setWhatsThis(msg);
304 
305     // "Email Address" line edit and label:
306     // (row 3: spacer)
307     ++row;
308     mEmailEdit = new QLineEdit(tab);
309     new LineEditCatchReturnKey(mEmailEdit, this);
310     label = new QLabel(i18n("&Email address:"), tab);
311     formLayout->addRow(label, mEmailEdit);
312     label->setBuddy(mEmailEdit);
313 
314     msg = i18n(
315         "<qt><h3>Email address</h3>"
316         "<p>This field should have your full email address.</p>"
317         "<p>This address is the primary one, used for all outgoing mail. "
318         "If you have more than one address, either create a new identity, "
319         "or add additional alias addresses in the field below.</p>"
320         "<p>If you leave this blank, or get it wrong, people "
321         "will have trouble replying to you.</p></qt>");
322     label->setWhatsThis(msg);
323     mEmailEdit->setWhatsThis(msg);
324 
325     auto emailValidator = new PimCommon::EmailValidator(this);
326     mEmailEdit->setValidator(emailValidator);
327 
328     // "Email Aliases" string text edit and label:
329     ++row;
330     mAliasEdit = new KEditListWidget(tab);
331 
332     auto emailValidator1 = new PimCommon::EmailValidator(this);
333     mAliasEdit->lineEdit()->setValidator(emailValidator1);
334 
335     label = new QLabel(i18n("Email a&liases:"), tab);
336     formLayout->addRow(label, mAliasEdit);
337     label->setBuddy(mAliasEdit);
338 
339     msg = i18n(
340         "<qt><h3>Email aliases</h3>"
341         "<p>This field contains alias addresses that should also "
342         "be considered as belonging to this identity (as opposed "
343         "to representing a different identity).</p>"
344         "<p>Example:</p>"
345         "<table>"
346         "<tr><th>Primary address:</th><td>first.last@example.org</td></tr>"
347         "<tr><th>Aliases:</th><td>first@example.org<br>last@example.org</td></tr>"
348         "</table>"
349         "<p>Type one alias address per line.</p></qt>");
350     label->setToolTip(msg);
351     mAliasEdit->setWhatsThis(msg);
352 
353     //
354     // Tab Widget: Cryptography
355     //
356     row = -1;
357     mCryptographyTab = tab = new QWidget(mTabWidget);
358     mTabWidget->addTab(tab, i18n("Cryptography"));
359     formLayout = new QFormLayout(tab);
360 
361     // "OpenPGP Signature Key" requester and label:
362     mPGPSigningKeyRequester = new KeySelectionCombo(KeySelectionCombo::SigningKey, GpgME::OpenPGP, tab);
363     msg = i18n(
364         "<qt><p>The OpenPGP key you choose here will be used "
365         "to digitally sign messages. You can also use GnuPG keys.</p>"
366         "<p>You can leave this blank, but KMail will not be able "
367         "to digitally sign emails using OpenPGP; "
368         "normal mail functions will not be affected.</p>"
369         "<p>You can find out more about keys at <a>https://www.gnupg.org</a></p></qt>");
370     label = new QLabel(i18n("OpenPGP signing key:"), tab);
371     label->setBuddy(mPGPSigningKeyRequester);
372     mPGPSigningKeyRequester->setWhatsThis(msg);
373     label->setWhatsThis(msg);
374 
375     formLayout->addRow(label, mPGPSigningKeyRequester);
376 
377     // "OpenPGP Encryption Key" requester and label:
378     mPGPEncryptionKeyRequester = new KeySelectionCombo(KeySelectionCombo::EncryptionKey, GpgME::OpenPGP, tab);
379     msg = i18n(
380         "<qt><p>The OpenPGP key you choose here will be used "
381         "to encrypt messages to yourself and for the \"Attach My Public Key\" "
382         "feature in the composer. You can also use GnuPG keys.</p>"
383         "<p>You can leave this blank, but KMail will not be able "
384         "to encrypt copies of outgoing messages to you using OpenPGP; "
385         "normal mail functions will not be affected.</p>"
386         "<p>You can find out more about keys at <a>https://www.gnupg.org</a></p></qt>");
387     label = new QLabel(i18n("OpenPGP encryption key:"), tab);
388     label->setBuddy(mPGPEncryptionKeyRequester);
389     mPGPEncryptionKeyRequester->setWhatsThis(msg);
390     label->setWhatsThis(msg);
391 
392     formLayout->addRow(label, mPGPEncryptionKeyRequester);
393 
394     // "S/MIME Signature Key" requester and label:
395     mSMIMESigningKeyRequester = new KeySelectionCombo(KeySelectionCombo::SigningKey, GpgME::CMS, tab);
396     msg = i18n(
397         "<qt><p>The S/MIME (X.509) certificate you choose here will be used "
398         "to digitally sign messages.</p>"
399         "<p>You can leave this blank, but KMail will not be able "
400         "to digitally sign emails using S/MIME; "
401         "normal mail functions will not be affected.</p></qt>");
402     label = new QLabel(i18n("S/MIME signing certificate:"), tab);
403     label->setBuddy(mSMIMESigningKeyRequester);
404     mSMIMESigningKeyRequester->setWhatsThis(msg);
405     label->setWhatsThis(msg);
406     formLayout->addRow(label, mSMIMESigningKeyRequester);
407 
408     const QGpgME::Protocol *smimeProtocol = QGpgME::smime();
409 
410     label->setEnabled(smimeProtocol);
411     mSMIMESigningKeyRequester->setEnabled(smimeProtocol);
412 
413     // "S/MIME Encryption Key" requester and label:
414     mSMIMEEncryptionKeyRequester = new KeySelectionCombo(KeySelectionCombo::EncryptionKey, GpgME::CMS, tab);
415     msg = i18n(
416         "<qt><p>The S/MIME certificate you choose here will be used "
417         "to encrypt messages to yourself and for the \"Attach My Certificate\" "
418         "feature in the composer.</p>"
419         "<p>You can leave this blank, but KMail will not be able "
420         "to encrypt copies of outgoing messages to you using S/MIME; "
421         "normal mail functions will not be affected.</p></qt>");
422     label = new QLabel(i18n("S/MIME encryption certificate:"), tab);
423     label->setBuddy(mSMIMEEncryptionKeyRequester);
424     mSMIMEEncryptionKeyRequester->setWhatsThis(msg);
425     label->setWhatsThis(msg);
426 
427     formLayout->addRow(label, mSMIMEEncryptionKeyRequester);
428 
429     label->setEnabled(smimeProtocol);
430     mSMIMEEncryptionKeyRequester->setEnabled(smimeProtocol);
431 
432     // "Preferred Crypto Message Format" combobox and label:
433     mPreferredCryptoMessageFormat = new QComboBox(tab);
434     QStringList l;
435     l << Kleo::cryptoMessageFormatToLabel(Kleo::AutoFormat) << Kleo::cryptoMessageFormatToLabel(Kleo::InlineOpenPGPFormat)
436       << Kleo::cryptoMessageFormatToLabel(Kleo::OpenPGPMIMEFormat) << Kleo::cryptoMessageFormatToLabel(Kleo::SMIMEFormat)
437       << Kleo::cryptoMessageFormatToLabel(Kleo::SMIMEOpaqueFormat);
438     mPreferredCryptoMessageFormat->addItems(l);
439     label = new QLabel(i18nc("preferred format of encrypted messages", "Preferred format:"), tab);
440     label->setBuddy(mPreferredCryptoMessageFormat);
441 
442     formLayout->addRow(label, mPreferredCryptoMessageFormat);
443 
444     mAutoSign = new QCheckBox(i18n("Automatically sign messages"));
445     formLayout->addWidget(mAutoSign);
446 
447     mAutoEncrypt = new QCheckBox(i18n("Automatically encrypt messages when possible"));
448     formLayout->addWidget(mAutoEncrypt);
449 
450     //
451     // Tab Widget: Advanced
452     //
453     tab = new QWidget(mTabWidget);
454     auto advancedMainLayout = new QVBoxLayout(tab);
455     mIdentityInvalidFolder = new IdentityInvalidFolder(tab);
456     advancedMainLayout->addWidget(mIdentityInvalidFolder);
457     mTabWidget->addTab(tab, i18nc("@title:tab Advanced identity settings.", "Advanced"));
458     formLayout = new QFormLayout;
459     advancedMainLayout->addLayout(formLayout);
460 
461     // "Reply-To Address" line edit and label:
462     mReplyToEdit = new PimCommon::AddresseeLineEdit(tab, true);
463     mReplyToEdit->setClearButtonEnabled(true);
464     mReplyToEdit->setObjectName(QStringLiteral("mReplyToEdit"));
465     label = new QLabel(i18n("&Reply-To address:"), tab);
466     label->setBuddy(mReplyToEdit);
467     formLayout->addRow(label, mReplyToEdit);
468     msg = i18n(
469         "<qt><h3>Reply-To addresses</h3>"
470         "<p>This sets the <tt>Reply-to:</tt> header to contain a "
471         "different email address to the normal <tt>From:</tt> "
472         "address.</p>"
473         "<p>This can be useful when you have a group of people "
474         "working together in similar roles. For example, you "
475         "might want any emails sent to have your email in the "
476         "<tt>From:</tt> field, but any responses to go to "
477         "a group address.</p>"
478         "<p>If in doubt, leave this field blank.</p></qt>");
479     label->setWhatsThis(msg);
480     mReplyToEdit->setWhatsThis(msg);
481 
482     // "CC addresses" line edit and label:
483     mCcEdit = new PimCommon::AddresseeLineEdit(tab, true);
484     mCcEdit->setClearButtonEnabled(true);
485     mCcEdit->setObjectName(QStringLiteral("mCcEdit"));
486     label = new QLabel(i18n("&CC addresses:"), tab);
487     label->setBuddy(mCcEdit);
488     formLayout->addRow(label, mCcEdit);
489 
490     msg = i18n(
491         "<qt><h3>CC (Carbon Copy) addresses</h3>"
492         "<p>The addresses that you enter here will be added to each "
493         "outgoing mail that is sent with this identity.</p>"
494         "<p>This is commonly used to send a copy of each sent message to "
495         "another account of yours.</p>"
496         "<p>To specify more than one address, use commas to separate "
497         "the list of CC recipients.</p>"
498         "<p>If in doubt, leave this field blank.</p></qt>");
499     label->setWhatsThis(msg);
500     mCcEdit->setWhatsThis(msg);
501 
502     // "BCC addresses" line edit and label:
503     mBccEdit = new PimCommon::AddresseeLineEdit(tab, true);
504     mBccEdit->setClearButtonEnabled(true);
505     mBccEdit->setObjectName(QStringLiteral("mBccEdit"));
506     label = new QLabel(i18n("&BCC addresses:"), tab);
507     label->setBuddy(mBccEdit);
508     formLayout->addRow(label, mBccEdit);
509     msg = i18n(
510         "<qt><h3>BCC (Blind Carbon Copy) addresses</h3>"
511         "<p>The addresses that you enter here will be added to each "
512         "outgoing mail that is sent with this identity. They will not "
513         "be visible to other recipients.</p>"
514         "<p>This is commonly used to send a copy of each sent message to "
515         "another account of yours.</p>"
516         "<p>To specify more than one address, use commas to separate "
517         "the list of BCC recipients.</p>"
518         "<p>If in doubt, leave this field blank.</p></qt>");
519     label->setWhatsThis(msg);
520     mBccEdit->setWhatsThis(msg);
521 
522     // "Dictionary" combo box and label:
523     mDictionaryCombo = new Sonnet::DictionaryComboBox(tab);
524     label = new QLabel(i18n("D&ictionary:"), tab);
525     label->setBuddy(mDictionaryCombo);
526     formLayout->addRow(label, mDictionaryCombo);
527 
528     // "Sent-mail Folder" combo box and label:
529     mFccFolderRequester = new IdentityFolderRequester(tab);
530     mFccFolderRequester->setSelectFolderTitleDialog(i18n("Select Send-mail Folder"));
531     mFccFolderRequester->setShowOutbox(false);
532     mSentMailFolderCheck = new QCheckBox(i18n("Sent-mail &folder:"), tab);
533     connect(mSentMailFolderCheck, &QCheckBox::toggled, mFccFolderRequester, &MailCommon::FolderRequester::setEnabled);
534     formLayout->addRow(mSentMailFolderCheck, mFccFolderRequester);
535 
536     // "Drafts Folder" combo box and label:
537     mDraftsFolderRequester = new IdentityFolderRequester(tab);
538     mDraftsFolderRequester->setSelectFolderTitleDialog(i18n("Select Draft Folder"));
539     mDraftsFolderRequester->setShowOutbox(false);
540     label = new QLabel(i18n("&Drafts folder:"), tab);
541     label->setBuddy(mDraftsFolderRequester);
542     formLayout->addRow(label, mDraftsFolderRequester);
543 
544     // "Templates Folder" combo box and label:
545     mTemplatesFolderRequester = new IdentityFolderRequester(tab);
546     mTemplatesFolderRequester->setSelectFolderTitleDialog(i18n("Select Templates Folder"));
547     mTemplatesFolderRequester->setShowOutbox(false);
548     label = new QLabel(i18n("&Templates folder:"), tab);
549     label->setBuddy(mTemplatesFolderRequester);
550     formLayout->addRow(label, mTemplatesFolderRequester);
551 
552     // "Special transport" combobox and label:
553     mTransportCheck = new QCheckBox(i18n("Outgoing Account:"), tab);
554     mTransportCombo = new TransportComboBox(tab);
555     mTransportCombo->setEnabled(false); // since !mTransportCheck->isChecked()
556     formLayout->addRow(mTransportCheck, mTransportCombo);
557 
558     connect(mTransportCheck, &QCheckBox::toggled, mTransportCombo, &MailTransport::TransportComboBox::setEnabled);
559 
560     mAttachMyVCard = new QCheckBox(i18n("Attach my vCard to message"), tab);
561     mEditVCard = new QPushButton(i18n("Create..."), tab);
562     connect(mEditVCard, &QPushButton::clicked, this, &IdentityDialog::slotEditVcard);
563     formLayout->addRow(mAttachMyVCard, mEditVCard);
564 
565     mAutoCorrectionLanguage = new PimCommon::AutoCorrectionLanguage(tab);
566     label = new QLabel(i18n("Autocorrection language:"), tab);
567     label->setBuddy(mAutoCorrectionLanguage);
568     formLayout->addRow(label, mAutoCorrectionLanguage);
569 
570     // "default domain" input field:
571     auto hbox = new QHBoxLayout;
572     mDefaultDomainEdit = new QLineEdit(tab);
573     new LineEditCatchReturnKey(mDefaultDomainEdit, this);
574     mDefaultDomainEdit->setClearButtonEnabled(true);
575     hbox->addWidget(mDefaultDomainEdit);
576     auto restoreDefaultDomainName = new QToolButton;
577     restoreDefaultDomainName->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
578     restoreDefaultDomainName->setToolTip(i18n("Restore default domain name"));
579     hbox->addWidget(restoreDefaultDomainName);
580     connect(restoreDefaultDomainName, &QToolButton::clicked, this, &IdentityDialog::slotRefreshDefaultDomainName);
581     label = new QLabel(i18n("Defaul&t domain:"), tab);
582     label->setBuddy(mDefaultDomainEdit);
583     formLayout->addRow(label, mDefaultDomainEdit);
584 
585     // and now: add QWhatsThis:
586     msg = i18n(
587         "<qt><p>The default domain is used to complete email "
588         "addresses that only consist of the user's name."
589         "</p></qt>");
590     label->setWhatsThis(msg);
591     mDefaultDomainEdit->setWhatsThis(msg);
592 
593     // the last row is a spacer
594 
595     //
596     // Tab Widget: Templates
597     //
598     tab = new QWidget(mTabWidget);
599     vlay = new QVBoxLayout(tab);
600 
601     auto tlay = new QHBoxLayout();
602     vlay->addLayout(tlay);
603 
604     mCustom = new QCheckBox(i18n("&Use custom message templates for this identity"), tab);
605     tlay->addWidget(mCustom, Qt::AlignLeft);
606 
607     mWidget = new TemplateParser::TemplatesConfiguration(tab, QStringLiteral("identity-templates"));
608     mWidget->setEnabled(false);
609 
610     // Move the help label outside of the templates configuration widget,
611     // so that the help can be read even if the widget is not enabled.
612     tlay->addStretch(9);
613     tlay->addWidget(mWidget->helpLabel(), Qt::AlignRight);
614 
615     vlay->addWidget(mWidget);
616 
617     auto btns = new QHBoxLayout();
618     mCopyGlobal = new QPushButton(i18n("&Copy Global Templates"), tab);
619     mCopyGlobal->setEnabled(false);
620     btns->addWidget(mCopyGlobal);
621     vlay->addLayout(btns);
622     connect(mCustom, &QCheckBox::toggled, mWidget, &TemplateParser::TemplatesConfiguration::setEnabled);
623     connect(mCustom, &QCheckBox::toggled, mCopyGlobal, &QPushButton::setEnabled);
624     connect(mCopyGlobal, &QPushButton::clicked, this, &IdentityDialog::slotCopyGlobal);
625     mTabWidget->addTab(tab, i18n("Templates"));
626 
627     //
628     // Tab Widget: Signature
629     //
630     mSignatureConfigurator = new KIdentityManagement::SignatureConfigurator(mTabWidget);
631     mTabWidget->addTab(mSignatureConfigurator, i18n("Signature"));
632 
633     //
634     // Tab Widget: Picture
635     //
636 
637     mXFaceConfigurator = new XFaceConfigurator(mTabWidget);
638     mTabWidget->addTab(mXFaceConfigurator, i18n("Picture"));
639 
640     resize(KMailSettings::self()->identityDialogSize());
641     mNameEdit->setFocus();
642 
643     connect(mTabWidget, &QTabWidget::currentChanged, this, &IdentityDialog::slotAboutToShow);
644 }
645 
~IdentityDialog()646 IdentityDialog::~IdentityDialog()
647 {
648     KMailSettings::self()->setIdentityDialogSize(size());
649 }
650 
slotHelp()651 void IdentityDialog::slotHelp()
652 {
653     PimCommon::Util::invokeHelp(QStringLiteral("kmail2/configure-identity.html"));
654 }
655 
slotAboutToShow(int index)656 void IdentityDialog::slotAboutToShow(int index)
657 {
658     QWidget *w = mTabWidget->widget(index);
659     if (w == mCryptographyTab) {
660         // set the configured email address as initial query of the key
661         // requesters:
662         const QString name = mNameEdit->text().trimmed();
663         const QString email = mEmailEdit->text().trimmed();
664 
665         mPGPEncryptionKeyRequester->setIdentity(name, email);
666         mPGPSigningKeyRequester->setIdentity(name, email);
667         mSMIMEEncryptionKeyRequester->setIdentity(name, email);
668         mSMIMESigningKeyRequester->setIdentity(name, email);
669     }
670 }
671 
slotCopyGlobal()672 void IdentityDialog::slotCopyGlobal()
673 {
674     mWidget->loadFromGlobal();
675 }
676 
slotRefreshDefaultDomainName()677 void IdentityDialog::slotRefreshDefaultDomainName()
678 {
679     mDefaultDomainEdit->setText(QHostInfo::localHostName());
680 }
681 
slotAccepted()682 void IdentityDialog::slotAccepted()
683 {
684     const QStringList aliases = mAliasEdit->items();
685     for (const QString &alias : aliases) {
686         if (alias.trimmed().isEmpty()) {
687             continue;
688         }
689         if (!KEmailAddress::isValidSimpleAddress(alias)) {
690             const QString errorMsg(KEmailAddress::simpleEmailAddressErrorMsg());
691             KMessageBox::sorry(this, errorMsg, i18n("Invalid Email Alias \"%1\"", alias));
692             return;
693         }
694     }
695 
696     // Validate email addresses
697     const QString email = mEmailEdit->text().trimmed();
698     if (!KEmailAddress::isValidSimpleAddress(email)) {
699         const QString errorMsg(KEmailAddress::simpleEmailAddressErrorMsg());
700         KMessageBox::sorry(this, errorMsg, i18n("Invalid Email Address"));
701         return;
702     }
703 
704     // Check if the 'Reply to' and 'BCC' recipients are valid
705     const QString recipients =
706         mReplyToEdit->text().trimmed() + QLatin1String(", ") + mBccEdit->text().trimmed() + QLatin1String(", ") + mCcEdit->text().trimmed();
707     auto job = new AddressValidationJob(recipients, this, this);
708     // Use default Value
709     job->setDefaultDomain(mDefaultDomainEdit->text());
710     job->setProperty("email", email);
711     connect(job, &AddressValidationJob::result, this, &IdentityDialog::slotDelayedButtonClicked);
712     job->start();
713 }
714 
keyMatchesEmailAddress(const GpgME::Key & key,const QString & email_)715 bool IdentityDialog::keyMatchesEmailAddress(const GpgME::Key &key, const QString &email_)
716 {
717     if (key.isNull()) {
718         return true;
719     }
720     const QString email = email_.trimmed().toLower();
721     const auto uids = key.userIDs();
722     for (const auto &uid : uids) {
723         QString em = QString::fromUtf8(uid.email() ? uid.email() : uid.id());
724         if (em.isEmpty()) {
725             continue;
726         }
727         if (em[0] == QLatin1Char('<')) {
728             em = em.mid(1, em.length() - 2);
729         }
730         if (em.toLower() == email) {
731             return true;
732         }
733     }
734 
735     return false;
736 }
737 
slotDelayedButtonClicked(KJob * job)738 void IdentityDialog::slotDelayedButtonClicked(KJob *job)
739 {
740     const AddressValidationJob *validationJob = qobject_cast<AddressValidationJob *>(job);
741 
742     // Abort if one of the recipient addresses is invalid
743     if (!validationJob->isValid()) {
744         return;
745     }
746 
747     const QString email = validationJob->property("email").toString();
748 
749     const GpgME::Key &pgpSigningKey = mPGPSigningKeyRequester->currentKey();
750     const GpgME::Key &pgpEncryptionKey = mPGPEncryptionKeyRequester->currentKey();
751     const GpgME::Key &smimeSigningKey = mSMIMESigningKeyRequester->currentKey();
752     const GpgME::Key &smimeEncryptionKey = mSMIMEEncryptionKeyRequester->currentKey();
753 
754     QString msg;
755     bool err = false;
756     if (!keyMatchesEmailAddress(pgpSigningKey, email)) {
757         msg = i18n(
758             "One of the configured OpenPGP signing keys does not contain "
759             "any user ID with the configured email address for this "
760             "identity (%1).\n"
761             "This might result in warning messages on the receiving side "
762             "when trying to verify signatures made with this configuration.",
763             email);
764         err = true;
765     } else if (!keyMatchesEmailAddress(pgpEncryptionKey, email)) {
766         msg = i18n(
767             "One of the configured OpenPGP encryption keys does not contain "
768             "any user ID with the configured email address for this "
769             "identity (%1).",
770             email);
771         err = true;
772     } else if (!keyMatchesEmailAddress(smimeSigningKey, email)) {
773         msg = i18n(
774             "One of the configured S/MIME signing certificates does not contain "
775             "the configured email address for this "
776             "identity (%1).\n"
777             "This might result in warning messages on the receiving side "
778             "when trying to verify signatures made with this configuration.",
779             email);
780         err = true;
781     } else if (!keyMatchesEmailAddress(smimeEncryptionKey, email)) {
782         msg = i18n(
783             "One of the configured S/MIME encryption certificates does not contain "
784             "the configured email address for this "
785             "identity (%1).",
786             email);
787         err = true;
788     }
789 
790     if (err) {
791         if (KMessageBox::warningContinueCancel(this,
792                                                msg,
793                                                i18n("Email Address Not Found in Key/Certificates"),
794                                                KStandardGuiItem::cont(),
795                                                KStandardGuiItem::cancel(),
796                                                QStringLiteral("warn_email_not_in_certificate"))
797             != KMessageBox::Continue) {
798             return;
799         }
800     }
801 
802     if (mSignatureConfigurator->isSignatureEnabled() && mSignatureConfigurator->signatureType() == Signature::FromFile) {
803         QFileInfo file(mSignatureConfigurator->filePath());
804         if (!file.isReadable()) {
805             KMessageBox::error(this, i18n("The signature file is not valid"));
806             return;
807         }
808     }
809 
810     accept();
811 }
812 
checkFolderExists(const QString & folderID)813 bool IdentityDialog::checkFolderExists(const QString &folderID)
814 {
815     const Akonadi::Collection folder = CommonKernel->collectionFromId(folderID.toLongLong());
816     return folder.isValid();
817 }
818 
setIdentity(KIdentityManagement::Identity & ident)819 void IdentityDialog::setIdentity(KIdentityManagement::Identity &ident)
820 {
821     setWindowTitle(i18nc("@title:window", "Edit Identity \"%1\"", ident.identityName()));
822 
823     // "General" tab:
824     mNameEdit->setText(ident.fullName());
825     mOrganizationEdit->setText(ident.organization());
826     mEmailEdit->setText(ident.primaryEmailAddress());
827     mAliasEdit->insertStringList(ident.emailAliases());
828 
829     // "Cryptography" tab:
830     mPGPSigningKeyRequester->setDefaultKey(QLatin1String(ident.pgpSigningKey()));
831     mPGPEncryptionKeyRequester->setDefaultKey(QLatin1String(ident.pgpEncryptionKey()));
832     mSMIMESigningKeyRequester->setDefaultKey(QLatin1String(ident.smimeSigningKey()));
833     mSMIMEEncryptionKeyRequester->setDefaultKey(QLatin1String(ident.smimeEncryptionKey()));
834 
835     mPreferredCryptoMessageFormat->setCurrentIndex(format2cb(Kleo::stringToCryptoMessageFormat(ident.preferredCryptoMessageFormat())));
836     mAutoSign->setChecked(ident.pgpAutoSign());
837     mAutoEncrypt->setChecked(ident.pgpAutoEncrypt());
838 
839     // "Advanced" tab:
840     mReplyToEdit->setText(ident.replyToAddr());
841     mBccEdit->setText(ident.bcc());
842     mCcEdit->setText(ident.cc());
843     const int transportId = ident.transport().isEmpty() ? -1 : ident.transport().toInt();
844     const Transport *transport = TransportManager::self()->transportById(transportId, true);
845     mTransportCheck->setChecked(transportId != -1);
846     mTransportCombo->setEnabled(transportId != -1);
847     if (transport) {
848         mTransportCombo->setCurrentTransport(transport->id());
849     }
850     mDictionaryCombo->setCurrentByDictionaryName(ident.dictionary());
851 
852     mSentMailFolderCheck->setChecked(!ident.disabledFcc());
853     mFccFolderRequester->setEnabled(mSentMailFolderCheck->isChecked());
854     bool foundNoExistingFolder = false;
855     if (ident.fcc().isEmpty() || !checkFolderExists(ident.fcc())) {
856         foundNoExistingFolder = true;
857         mFccFolderRequester->setIsInvalidFolder(CommonKernel->sentCollectionFolder());
858     } else {
859         mFccFolderRequester->setCollection(Akonadi::Collection(ident.fcc().toLongLong()));
860     }
861     if (ident.drafts().isEmpty() || !checkFolderExists(ident.drafts())) {
862         foundNoExistingFolder = true;
863         mDraftsFolderRequester->setIsInvalidFolder(CommonKernel->draftsCollectionFolder());
864     } else {
865         mDraftsFolderRequester->setCollection(Akonadi::Collection(ident.drafts().toLongLong()));
866     }
867 
868     if (ident.templates().isEmpty() || !checkFolderExists(ident.templates())) {
869         foundNoExistingFolder = true;
870         mTemplatesFolderRequester->setIsInvalidFolder(CommonKernel->templatesCollectionFolder());
871     } else {
872         mTemplatesFolderRequester->setCollection(Akonadi::Collection(ident.templates().toLongLong()));
873     }
874     if (foundNoExistingFolder) {
875         mIdentityInvalidFolder->setErrorMessage(i18n("Some custom folder for identity does not exist (anymore); therefore, default folders will be used."));
876     }
877     mVcardFilename = ident.vCardFile();
878 
879     mAutoCorrectionLanguage->setLanguage(ident.autocorrectionLanguage());
880     updateVcardButton();
881     if (mVcardFilename.isEmpty()) {
882         mVcardFilename =
883             QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QLatin1Char('/') + ident.identityName() + QLatin1String(".vcf");
884         QFileInfo fileInfo(mVcardFilename);
885         QDir().mkpath(fileInfo.absolutePath());
886     } else {
887         // Convert path.
888         const QString path =
889             QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QLatin1Char('/') + ident.identityName() + QLatin1String(".vcf");
890         if (QFileInfo::exists(path) && (mVcardFilename != path)) {
891             mVcardFilename = path;
892         }
893     }
894     mAttachMyVCard->setChecked(ident.attachVcard());
895     QString defaultDomainName = ident.defaultDomainName();
896     if (defaultDomainName.isEmpty()) {
897         defaultDomainName = QHostInfo::localHostName();
898     }
899     mDefaultDomainEdit->setText(defaultDomainName);
900 
901     // "Templates" tab:
902     uint identity = ident.uoid();
903     QString iid = TemplateParser::TemplatesConfiguration::configIdString(identity);
904     TemplateParser::Templates t(iid);
905     mCustom->setChecked(t.useCustomTemplates());
906     mWidget->loadFromIdentity(identity);
907 
908     // "Signature" tab:
909     mSignatureConfigurator->setImageLocation(ident);
910     mSignatureConfigurator->setSignature(ident.signature());
911     mXFaceConfigurator->setXFace(ident.xface());
912     mXFaceConfigurator->setXFaceEnabled(ident.isXFaceEnabled());
913     mXFaceConfigurator->setFace(ident.face());
914     mXFaceConfigurator->setFaceEnabled(ident.isFaceEnabled());
915 }
916 
unregisterSpecialCollection(qint64 colId)917 void IdentityDialog::unregisterSpecialCollection(qint64 colId)
918 {
919     // ID is not enough to unregister a special collection, we need the
920     // resource set as well.
921     auto fetch = new Akonadi::CollectionFetchJob(Akonadi::Collection(colId), Akonadi::CollectionFetchJob::Base, this);
922     connect(fetch, &Akonadi::CollectionFetchJob::collectionsReceived, this, [](const Akonadi::Collection::List &cols) {
923         if (cols.count() != 1) {
924             return;
925         }
926         Akonadi::SpecialMailCollections::self()->unregisterCollection(cols.first());
927     });
928 }
929 
updateIdentity(KIdentityManagement::Identity & ident)930 void IdentityDialog::updateIdentity(KIdentityManagement::Identity &ident)
931 {
932     // "General" tab:
933     ident.setFullName(mNameEdit->text());
934     ident.setOrganization(mOrganizationEdit->text());
935     QString email = mEmailEdit->text().trimmed();
936     ident.setPrimaryEmailAddress(email);
937     const QStringList aliases = mAliasEdit->items();
938     QStringList result;
939     for (const QString &alias : aliases) {
940         const QString aliasTrimmed = alias.trimmed();
941         if (aliasTrimmed.isEmpty()) {
942             continue;
943         }
944         if (aliasTrimmed == email) {
945             continue;
946         }
947         result.append(alias);
948     }
949     ident.setEmailAliases(result);
950     // "Cryptography" tab:
951     ident.setPGPSigningKey(mPGPSigningKeyRequester->currentKey().primaryFingerprint());
952     ident.setPGPEncryptionKey(mPGPEncryptionKeyRequester->currentKey().primaryFingerprint());
953     ident.setSMIMESigningKey(mSMIMESigningKeyRequester->currentKey().primaryFingerprint());
954     ident.setSMIMEEncryptionKey(mSMIMEEncryptionKeyRequester->currentKey().primaryFingerprint());
955     ident.setPreferredCryptoMessageFormat(QLatin1String(Kleo::cryptoMessageFormatToString(cb2format(mPreferredCryptoMessageFormat->currentIndex()))));
956     ident.setPgpAutoSign(mAutoSign->isChecked());
957     ident.setPgpAutoEncrypt(mAutoEncrypt->isChecked());
958     // "Advanced" tab:
959     ident.setReplyToAddr(mReplyToEdit->text());
960     ident.setBcc(mBccEdit->text());
961     ident.setCc(mCcEdit->text());
962     ident.setTransport(mTransportCheck->isChecked() ? QString::number(mTransportCombo->currentTransportId()) : QString());
963     ident.setDictionary(mDictionaryCombo->currentDictionaryName());
964     ident.setDisabledFcc(!mSentMailFolderCheck->isChecked());
965     Akonadi::Collection collection = mFccFolderRequester->collection();
966     if (!ident.fcc().isEmpty()) {
967         unregisterSpecialCollection(ident.fcc().toLongLong());
968     }
969     if (collection.isValid()) {
970         ident.setFcc(QString::number(collection.id()));
971         auto *attribute = collection.attribute<Akonadi::EntityDisplayAttribute>(Akonadi::Collection::AddIfMissing);
972         attribute->setIconName(QStringLiteral("mail-folder-sent"));
973         // It will also start a CollectionModifyJob
974         Akonadi::SpecialMailCollections::self()->registerCollection(Akonadi::SpecialMailCollections::SentMail, collection);
975     } else {
976         ident.setFcc(QString());
977     }
978 
979     collection = mDraftsFolderRequester->collection();
980     if (!ident.drafts().isEmpty()) {
981         unregisterSpecialCollection(ident.drafts().toLongLong());
982     }
983     if (collection.isValid()) {
984         ident.setDrafts(QString::number(collection.id()));
985         auto *attribute = collection.attribute<Akonadi::EntityDisplayAttribute>(Akonadi::Collection::AddIfMissing);
986         attribute->setIconName(QStringLiteral("document-properties"));
987         // It will also start a CollectionModifyJob
988         Akonadi::SpecialMailCollections::self()->registerCollection(Akonadi::SpecialMailCollections::Drafts, collection);
989     } else {
990         ident.setDrafts(QString());
991     }
992 
993     collection = mTemplatesFolderRequester->collection();
994     if (ident.templates().isEmpty()) {
995         unregisterSpecialCollection(ident.templates().toLongLong());
996     }
997     if (collection.isValid()) {
998         ident.setTemplates(QString::number(collection.id()));
999         auto *attribute = collection.attribute<Akonadi::EntityDisplayAttribute>(Akonadi::Collection::AddIfMissing);
1000         attribute->setIconName(QStringLiteral("document-new"));
1001         // It will also start a CollectionModifyJob
1002         Akonadi::SpecialMailCollections::self()->registerCollection(Akonadi::SpecialMailCollections::Templates, collection);
1003         new Akonadi::CollectionModifyJob(collection);
1004     } else {
1005         ident.setTemplates(QString());
1006     }
1007     ident.setVCardFile(mVcardFilename);
1008     ident.setAutocorrectionLanguage(mAutoCorrectionLanguage->language());
1009     updateVcardButton();
1010     ident.setAttachVcard(mAttachMyVCard->isChecked());
1011     // Add default ?
1012     ident.setDefaultDomainName(mDefaultDomainEdit->text());
1013 
1014     // "Templates" tab:
1015     uint identity = ident.uoid();
1016     QString iid = TemplateParser::TemplatesConfiguration::configIdString(identity);
1017     TemplateParser::Templates t(iid);
1018     qCDebug(KMAIL_LOG) << "use custom templates for identity" << identity << ":" << mCustom->isChecked();
1019     t.setUseCustomTemplates(mCustom->isChecked());
1020     t.save();
1021     mWidget->saveToIdentity(identity);
1022 
1023     // "Signature" tab:
1024     ident.setSignature(mSignatureConfigurator->signature());
1025     ident.setXFace(mXFaceConfigurator->xface());
1026     ident.setXFaceEnabled(mXFaceConfigurator->isXFaceEnabled());
1027     ident.setFace(mXFaceConfigurator->face());
1028     ident.setFaceEnabled(mXFaceConfigurator->isFaceEnabled());
1029 }
1030 
slotEditVcard()1031 void IdentityDialog::slotEditVcard()
1032 {
1033     if (QFileInfo::exists(mVcardFilename)) {
1034         editVcard(mVcardFilename);
1035     } else {
1036         if (!MailCommon::Kernel::self()->kernelIsRegistered()) {
1037             return;
1038         }
1039         KIdentityManagement::IdentityManager *manager = KernelIf->identityManager();
1040 
1041         QPointer<IdentityAddVcardDialog> dlg = new IdentityAddVcardDialog(manager->shadowIdentities(), this);
1042         if (dlg->exec()) {
1043             IdentityAddVcardDialog::DuplicateMode mode = dlg->duplicateMode();
1044             switch (mode) {
1045             case IdentityAddVcardDialog::Empty:
1046                 editVcard(mVcardFilename);
1047                 break;
1048             case IdentityAddVcardDialog::ExistingEntry: {
1049                 KIdentityManagement::Identity ident = manager->modifyIdentityForName(dlg->duplicateVcardFromIdentity());
1050                 const QString filename = ident.vCardFile();
1051                 if (!filename.isEmpty()) {
1052                     QFile::copy(filename, mVcardFilename);
1053                 }
1054                 editVcard(mVcardFilename);
1055                 break;
1056             }
1057             case IdentityAddVcardDialog::FromExistingVCard: {
1058                 const QString filename = dlg->existingVCard().path();
1059                 if (!filename.isEmpty()) {
1060                     mVcardFilename = filename;
1061                 }
1062                 editVcard(mVcardFilename);
1063                 break;
1064             }
1065             }
1066         }
1067         delete dlg;
1068     }
1069 }
1070 
editVcard(const QString & filename)1071 void IdentityDialog::editVcard(const QString &filename)
1072 {
1073     QPointer<IdentityEditVcardDialog> dlg = new IdentityEditVcardDialog(filename, this);
1074     connect(dlg.data(), &IdentityEditVcardDialog::vcardRemoved, this, &IdentityDialog::slotVCardRemoved);
1075     if (dlg->exec()) {
1076         mVcardFilename = dlg->saveVcard();
1077     }
1078     updateVcardButton();
1079     delete dlg;
1080 }
1081 
slotVCardRemoved()1082 void IdentityDialog::slotVCardRemoved()
1083 {
1084     mVcardFilename.clear();
1085 }
1086 
updateVcardButton()1087 void IdentityDialog::updateVcardButton()
1088 {
1089     if (mVcardFilename.isEmpty() || !QFileInfo::exists(mVcardFilename)) {
1090         mEditVCard->setText(i18n("Create..."));
1091     } else {
1092         mEditVCard->setText(i18n("Edit..."));
1093     }
1094 }
1095 }
1096 
1097 #include "identitydialog.moc"
1098