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