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