1 /*
2 This file is part of Akonadi Contact.
3
4 SPDX-FileCopyrightText: 2009 Tobias Koenig <tokoe@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8
9 #include "contacteditor.h"
10
11 #include "abstractcontacteditorwidget_p.h"
12 #include "attributes/contactmetadataattribute_p.h"
13 #include "contactmetadataakonadi_p.h"
14 #include "editor/contacteditorwidget.h"
15
16 #include <Akonadi/CollectionDialog>
17 #include <Akonadi/CollectionFetchJob>
18 #include <Akonadi/ItemCreateJob>
19 #include <Akonadi/ItemFetchJob>
20 #include <Akonadi/ItemFetchScope>
21 #include <Akonadi/ItemModifyJob>
22 #include <Akonadi/Monitor>
23 #include <Akonadi/Session>
24 #include <KContacts/Addressee>
25 #include <KLocalizedString>
26
27 #include <QMessageBox>
28 #include <QPointer>
29 #include <QVBoxLayout>
30
31 using namespace Akonadi;
32
33 class Akonadi::AkonadiContactEditorPrivate
34 {
35 public:
AkonadiContactEditorPrivate(AkonadiContactEditor::Mode mode,AkonadiContactEditor::DisplayMode displayMode,ContactEditor::AbstractContactEditorWidget * editorWidget,AkonadiContactEditor * parent)36 AkonadiContactEditorPrivate(AkonadiContactEditor::Mode mode,
37 AkonadiContactEditor::DisplayMode displayMode,
38 ContactEditor::AbstractContactEditorWidget *editorWidget,
39 AkonadiContactEditor *parent)
40 : mParent(parent)
41 , mMode(mode)
42 {
43 if (editorWidget) {
44 mEditorWidget = editorWidget;
45 } else {
46 mEditorWidget =
47 new ContactEditorWidget(displayMode == AkonadiContactEditor::FullMode ? ContactEditorWidget::FullMode : ContactEditorWidget::VCardMode,
48 mParent);
49 }
50
51 auto layout = new QVBoxLayout(mParent);
52 layout->setContentsMargins({});
53 layout->setSpacing(0);
54 layout->addWidget(mEditorWidget);
55 }
56
~AkonadiContactEditorPrivate()57 ~AkonadiContactEditorPrivate()
58 {
59 delete mMonitor;
60 }
61
62 void itemFetchDone(KJob *job);
63 void parentCollectionFetchDone(KJob *job);
64 void storeDone(KJob *job);
65 void itemChanged(const Akonadi::Item &item, const QSet<QByteArray> &);
66
67 void loadContact(const KContacts::Addressee &addr, const ContactMetaDataAkonadi &metaData);
68 void storeContact(KContacts::Addressee &addr, ContactMetaDataAkonadi &metaData);
69 void setupMonitor();
70
71 AkonadiContactEditor *const mParent;
72 const AkonadiContactEditor::Mode mMode;
73 Akonadi::Item mItem;
74 Akonadi::ContactMetaDataAkonadi mContactMetaData;
75 Akonadi::Monitor *mMonitor = nullptr;
76 Akonadi::Collection mDefaultCollection;
77 ContactEditor::AbstractContactEditorWidget *mEditorWidget = nullptr;
78 bool mReadOnly = false;
79 };
80
itemFetchDone(KJob * job)81 void Akonadi::AkonadiContactEditorPrivate::itemFetchDone(KJob *job)
82 {
83 if (job->error() != KJob::NoError) {
84 Q_EMIT mParent->error(job->errorString());
85 Q_EMIT mParent->finished();
86 return;
87 }
88
89 auto fetchJob = qobject_cast<Akonadi::ItemFetchJob *>(job);
90 if (!fetchJob) {
91 return;
92 }
93
94 if (fetchJob->items().isEmpty()) {
95 return;
96 }
97
98 mItem = fetchJob->items().at(0);
99
100 mReadOnly = false;
101 if (mMode == AkonadiContactEditor::EditMode) {
102 // if in edit mode we have to fetch the parent collection to find out
103 // about the modify rights of the item
104
105 auto collectionFetchJob = new Akonadi::CollectionFetchJob(mItem.parentCollection(), Akonadi::CollectionFetchJob::Base);
106 mParent->connect(collectionFetchJob, &CollectionFetchJob::result, mParent, [this](KJob *job) {
107 parentCollectionFetchDone(job);
108 });
109 } else {
110 const auto addr = mItem.payload<KContacts::Addressee>();
111 mContactMetaData.load(mItem);
112 loadContact(addr, mContactMetaData);
113 mEditorWidget->setReadOnly(mReadOnly);
114 }
115 }
116
parentCollectionFetchDone(KJob * job)117 void Akonadi::AkonadiContactEditorPrivate::parentCollectionFetchDone(KJob *job)
118 {
119 if (job->error()) {
120 Q_EMIT mParent->error(job->errorString());
121 Q_EMIT mParent->finished();
122 return;
123 }
124
125 auto fetchJob = qobject_cast<Akonadi::CollectionFetchJob *>(job);
126 if (!fetchJob) {
127 return;
128 }
129
130 const Akonadi::Collection parentCollection = fetchJob->collections().at(0);
131 if (parentCollection.isValid()) {
132 mReadOnly = !(parentCollection.rights() & Collection::CanChangeItem);
133 }
134
135 const auto addr = mItem.payload<KContacts::Addressee>();
136 mContactMetaData.load(mItem);
137 loadContact(addr, mContactMetaData);
138 mEditorWidget->setReadOnly(mReadOnly);
139 }
140
storeDone(KJob * job)141 void Akonadi::AkonadiContactEditorPrivate::storeDone(KJob *job)
142 {
143 if (job->error() != KJob::NoError) {
144 Q_EMIT mParent->error(job->errorString());
145 Q_EMIT mParent->finished();
146 return;
147 }
148
149 if (mMode == AkonadiContactEditor::EditMode) {
150 Q_EMIT mParent->contactStored(mItem);
151 } else if (mMode == AkonadiContactEditor::CreateMode) {
152 Q_EMIT mParent->contactStored(static_cast<Akonadi::ItemCreateJob *>(job)->item());
153 }
154 Q_EMIT mParent->finished();
155 }
156
itemChanged(const Akonadi::Item & item,const QSet<QByteArray> &)157 void Akonadi::AkonadiContactEditorPrivate::itemChanged(const Akonadi::Item &item, const QSet<QByteArray> &)
158 {
159 Q_UNUSED(item)
160 QPointer<QMessageBox> dlg = new QMessageBox(mParent); // krazy:exclude=qclasses
161
162 dlg->setInformativeText(i18n("The contact has been changed by someone else.\nWhat should be done?"));
163 dlg->addButton(i18n("Take over changes"), QMessageBox::AcceptRole);
164 dlg->addButton(i18n("Ignore and Overwrite changes"), QMessageBox::RejectRole);
165
166 if (dlg->exec() == QMessageBox::AcceptRole) {
167 auto job = new Akonadi::ItemFetchJob(mItem);
168 job->fetchScope().fetchFullPayload();
169 job->fetchScope().fetchAttribute<ContactMetaDataAttribute>();
170 job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
171
172 mParent->connect(job, &ItemFetchJob::result, mParent, [this](KJob *job) {
173 itemFetchDone(job);
174 });
175 }
176
177 delete dlg;
178 }
179
loadContact(const KContacts::Addressee & addr,const ContactMetaDataAkonadi & metaData)180 void Akonadi::AkonadiContactEditorPrivate::loadContact(const KContacts::Addressee &addr, const ContactMetaDataAkonadi &metaData)
181 {
182 mEditorWidget->loadContact(addr, metaData);
183 }
184
storeContact(KContacts::Addressee & addr,ContactMetaDataAkonadi & metaData)185 void Akonadi::AkonadiContactEditorPrivate::storeContact(KContacts::Addressee &addr, ContactMetaDataAkonadi &metaData)
186 {
187 mEditorWidget->storeContact(addr, metaData);
188 }
189
setupMonitor()190 void Akonadi::AkonadiContactEditorPrivate::setupMonitor()
191 {
192 delete mMonitor;
193 mMonitor = new Akonadi::Monitor;
194 mMonitor->setObjectName(QStringLiteral("ContactEditorMonitor"));
195 mMonitor->ignoreSession(Akonadi::Session::defaultSession());
196
197 QObject::connect(mMonitor, &Monitor::itemChanged, mParent, [this](const Akonadi::Item &item, const QSet<QByteArray> &set) {
198 itemChanged(item, set);
199 });
200 }
201
AkonadiContactEditor(Mode mode,QWidget * parent)202 Akonadi::AkonadiContactEditor::AkonadiContactEditor(Mode mode, QWidget *parent)
203 : QWidget(parent)
204 , d(new AkonadiContactEditorPrivate(mode, FullMode, nullptr, this))
205 {
206 }
207
AkonadiContactEditor(Mode mode,ContactEditor::AbstractContactEditorWidget * editorWidget,QWidget * parent)208 Akonadi::AkonadiContactEditor::AkonadiContactEditor(Mode mode, ContactEditor::AbstractContactEditorWidget *editorWidget, QWidget *parent)
209 : QWidget(parent)
210 , d(new AkonadiContactEditorPrivate(mode, FullMode, editorWidget, this))
211 {
212 }
213
AkonadiContactEditor(Mode mode,DisplayMode displayMode,QWidget * parent)214 Akonadi::AkonadiContactEditor::AkonadiContactEditor(Mode mode, DisplayMode displayMode, QWidget *parent)
215 : QWidget(parent)
216 , d(new AkonadiContactEditorPrivate(mode, displayMode, nullptr, this))
217 {
218 }
219
220 Akonadi::AkonadiContactEditor::~AkonadiContactEditor() = default;
221
loadContact(const Akonadi::Item & item)222 void Akonadi::AkonadiContactEditor::loadContact(const Akonadi::Item &item)
223 {
224 if (d->mMode == CreateMode) {
225 Q_ASSERT_X(false, "ContactEditor::loadContact", "You are calling loadContact in CreateMode!");
226 }
227
228 auto job = new Akonadi::ItemFetchJob(item);
229 job->fetchScope().fetchFullPayload();
230 job->fetchScope().fetchAttribute<ContactMetaDataAttribute>();
231 job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
232
233 connect(job, &ItemFetchJob::result, this, [this](KJob *job) {
234 d->itemFetchDone(job);
235 });
236
237 d->setupMonitor();
238 d->mMonitor->setItemMonitored(item);
239 }
240
contact()241 KContacts::Addressee Akonadi::AkonadiContactEditor::contact()
242 {
243 KContacts::Addressee addr;
244 d->storeContact(addr, d->mContactMetaData);
245 return addr;
246 }
247
saveContactInAddressBook()248 void Akonadi::AkonadiContactEditor::saveContactInAddressBook()
249 {
250 if (d->mMode == EditMode) {
251 if (!d->mItem.isValid() || d->mReadOnly) {
252 Q_EMIT finished();
253 return;
254 }
255
256 auto addr = d->mItem.payload<KContacts::Addressee>();
257
258 d->storeContact(addr, d->mContactMetaData);
259
260 d->mContactMetaData.store(d->mItem);
261
262 d->mItem.setPayload<KContacts::Addressee>(addr);
263
264 auto job = new Akonadi::ItemModifyJob(d->mItem);
265 connect(job, &ItemModifyJob::result, this, [this](KJob *job) {
266 d->storeDone(job);
267 });
268 } else if (d->mMode == CreateMode) {
269 if (!d->mDefaultCollection.isValid()) {
270 const QStringList mimeTypeFilter(KContacts::Addressee::mimeType());
271
272 QPointer<CollectionDialog> dlg = new CollectionDialog(this);
273 dlg->setMimeTypeFilter(mimeTypeFilter);
274 dlg->setAccessRightsFilter(Collection::CanCreateItem);
275 dlg->setWindowTitle(i18nc("@title:window", "Select Address Book"));
276 dlg->setDescription(i18n("Select the address book the new contact shall be saved in:"));
277 if (dlg->exec() == QDialog::Accepted) {
278 setDefaultAddressBook(dlg->selectedCollection());
279 delete dlg;
280 } else {
281 delete dlg;
282 return;
283 }
284 }
285
286 KContacts::Addressee addr;
287 d->storeContact(addr, d->mContactMetaData);
288
289 Akonadi::Item item;
290 item.setPayload<KContacts::Addressee>(addr);
291 item.setMimeType(KContacts::Addressee::mimeType());
292
293 d->mContactMetaData.store(item);
294
295 auto job = new Akonadi::ItemCreateJob(item, d->mDefaultCollection);
296 connect(job, &ItemCreateJob::result, this, [this](KJob *job) {
297 d->storeDone(job);
298 });
299 }
300 }
301
setContactTemplate(const KContacts::Addressee & contact)302 void Akonadi::AkonadiContactEditor::setContactTemplate(const KContacts::Addressee &contact)
303 {
304 d->loadContact(contact, d->mContactMetaData);
305 }
306
setDefaultAddressBook(const Akonadi::Collection & collection)307 void Akonadi::AkonadiContactEditor::setDefaultAddressBook(const Akonadi::Collection &collection)
308 {
309 d->mDefaultCollection = collection;
310 }
311
hasNoSavedData() const312 bool Akonadi::AkonadiContactEditor::hasNoSavedData() const
313 {
314 return d->mEditorWidget->hasNoSavedData();
315 }
316
317 #include "moc_contacteditor.cpp"
318