1 /*
2   SPDX-FileCopyrightText: 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
3   SPDX-FileCopyrightText: 2010-2012 Sérgio Martins <iamsergio@gmail.com>
4 
5   SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 #pragma once
8 
9 #include "akonadi-calendar_export.h"
10 
11 #include "itiphandler.h"
12 #include <Akonadi/Collection>
13 #include <Akonadi/Item>
14 
15 #include <KCalendarCore/Incidence>
16 
17 #include <QWidget>
18 
19 #include <memory>
20 
21 namespace Akonadi
22 {
23 class EntityTreeModel;
24 class IncidenceChangerPrivate;
25 
26 /**
27  * @short IncidenceChanger is the preferred way to easily create, modify and delete incidences.
28  *
29  * It hides the communication with akonadi from the library user.
30  *
31  * It provides the following features that ItemCreateJob, ItemModifyJob and
32  * ItemDeleteJob do not:
33  * - Sending groupware ( iTip ) messages to attendees and organizers.
34  * - Aware of recurrences, allowing to only change one occurrence.
35  * - Undo/Redo
36  * - Group operations which are executed in an atomic manner.
37  * - Collection ACLs
38  * - Error dialogs with calendaring lingo
39  *
40  * In the context of this API, "change", means "creation", "deletion" or incidence "modification".
41  *
42  * @code
43  * IncidenceChanger *changer = new IncidenceChanger( parent );
44  * connect( changer,
45  *          SIGNAL(createFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode,QString)),
46  *          SLOT(slotCreateFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode,QString)) );
47  *
48  * connect( changer,
49  *          SIGNAL(deleteFinished(int,QVector<Akonadi::Item::Id>,Akonadi::IncidenceChanger::ResultCode,QString)),
50  *          SLOT(slotDeleteFinished(int,QVector<Akonadi::Item::Id>,Akonadi::IncidenceChanger::ResultCode,QString)) );
51  *
52  * connect( changer,SIGNAL(modifyFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode,QString)),
53  *          SLOT(slotModifyFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode,QString)) );
54  *
55  * changer->setDestinationPolicy( IncidenceChanger::DestinationPolicyAsk );
56  *
57  * KCalendarCore::Incidence::Ptr incidence = (...);
58  * int changeId = changer->createIncidence( incidence, Akonadi::Collection() );
59  *
60  *
61  * if ( changeId == -1 )
62  * {
63  *  // Invalid parameters, incidence is null.
64  * }
65  *
66  * @endcode
67  *
68  * @author Sérgio Martins <iamsergio@gmail.com>
69  * @since 4.11
70  */
71 
72 class History;
73 
74 class AKONADI_CALENDAR_EXPORT IncidenceChanger : public QObject
75 {
76     Q_OBJECT
77 public:
78     /**
79      * This enum describes result codes which are returned by createFinished(),
80      * modifyfinished() and deleteFinished() signals.
81      */
82     enum ResultCode {
83         ResultCodeSuccess = 0,
84         ResultCodeJobError, ///< ItemCreateJob, ItemModifyJob or ItemDeleteJob weren't successful
85         ResultCodeAlreadyDeleted, ///< That incidence was already deleted, or currently being deleted.
86         ResultCodeInvalidDefaultCollection, ///< Default collection is invalid and DestinationPolicyNeverAsk was used
87         ResultCodeRolledback, ///< One change belonging to an atomic operation failed. All other changes were rolled back.
88         ResultCodePermissions, ///< The parent collection doesn't have ACLs for this operation
89         ResultCodeUserCanceled, ///< User canceled the operation
90         ResultCodeInvalidUserCollection, ///< User somehow chose an invalid collection in the collection dialog ( should not happen )
91         ResultCodeModificationDiscarded, ///< A new modification came in, the old one is discarded
92         ResultCodeDuplicateId ///< Duplicate Akonadi::Item::Ids must be unique in group operations
93     };
94 
95     /**
96      * This enum describes destination policies.
97      * Destination policies control how the createIncidence() method chooses the
98      * collection where the item will be created.
99      */
100     enum DestinationPolicy {
101         DestinationPolicyDefault, ///< The default collection is used, if it's invalid, the user is prompted. @see setDefaultCollection().
102         DestinationPolicyAsk, ///< User is always asked which collection to use.
103         DestinationPolicyNeverAsk ///< The default collection is used, if it's invalid, an error is returned, and the incidence isn't added.
104     };
105 
106     /**
107      * Enum for controlling "Do you want to e-mail attendees" type of dialogs.
108      * This is only honoured if groupware communication is active.
109      *
110      * @see groupwareCommunication()
111      * @since 4.12
112      */
113     enum InvitationPolicy {
114         InvitationPolicySend = 0, ///< Invitation e-mails are sent without asking the user if he wants to.
115         InvitationPolicyAsk, ///< The user is asked if an e-mail should be sent. This is the default.
116         InvitationPolicyDontSend ///< E-mails aren't sent
117     };
118 
119     /**
120      * This enum describes change types.
121      */
122     enum ChangeType {
123         ChangeTypeCreate, ///> Represents an incidence creation.
124         ChangeTypeModify, ///> Represents an incidence modification.
125         ChangeTypeDelete ///> Represents an incidence deletion.
126     };
127 
128     /**
129      * Creates a new IncidenceChanger instance.
130      * creates a default ITIPHandlerComponentFactory object.
131      * @param parent parent QObject
132      */
133     explicit IncidenceChanger(QObject *parent = nullptr);
134 
135     /**
136      * Creates a new IncidenceChanger instance.
137      * @param factory factory for creating dialogs and the mail transport job. To create a default
138      * factory set factory == 0
139      * @param parent parent QObject
140      * @since 4.15
141      */
142     explicit IncidenceChanger(ITIPHandlerComponentFactory *factory, QObject *parent);
143 
144     /**
145      * Destroys this IncidenceChanger instance.
146      */
147     ~IncidenceChanger() override;
148 
149     /**
150      * Creates a new incidence.
151      *
152      * @param incidence Incidence to create, must be valid.
153      * @param collection Collection where the incidence will be created. If invalid, one according
154      *                   to the DestinationPolicy will be used. You can know which collection was
155      *                   used by calling lastCollectionUsed();
156      * @param parent widget parent to be used in dialogs.
157      *
158      * @return Returns an integer which identifies this change. This identifier is useful
159      *         to correlate this operation with the IncidenceChanger::createFinished() signal.
160      *
161      *         Returns -1 if @p incidence is invalid. The createFinished() signal
162      *         won't be emitted in this case.
163      */
164     int createIncidence(const KCalendarCore::Incidence::Ptr &incidence, const Akonadi::Collection &collection = Akonadi::Collection(), QWidget *parent = nullptr);
165 
166     /**
167       * Creates a new incidence.
168       *
169       * @param item Item containing the incidence to create and metadata, such as tags.
170       * @param collection Collection where the incidence will be created. If invalid, one according
171       *                   to the DestinationPolicy will be used. You can know which collection was
172       *                   used by calling lastCollectionUsed();
173       * @param parent widget parent to be used in dialogs.
174       *
175       * @return Returns an integer which identifies this change. This identifier is useful
176       *         to correlate this operation with the IncidenceChanger::createFinished() signal.
177       *
178       *         Returns -1 if @p item is invalid. The createFinished() signal
179       *         won't be emitted in this case.
180       */
181     int createFromItem(const Akonadi::Item &item, const Akonadi::Collection &collection = Akonadi::Collection(), QWidget *parent = nullptr);
182 
183     /**
184      * Deletes an incidence. If it's recurring, all occurrences are deleted.
185      *
186      * @param item Item to delete. Item must be valid.
187      * @param parent Parent to be used in dialogs.
188      *
189      * @return Returns an integer which identifies this deletion. This identifier is useful
190      *         to correlate this deletion with the IncidenceChanger::deleteFinished() signal.
191      *
192      *         Returns -1 if item is invalid. The deleteFinished() signal won't be emitted in this
193      *         case.
194      */
195     int deleteIncidence(const Akonadi::Item &item, QWidget *parent = nullptr);
196 
197     /**
198      * Deletes a list of Items.
199      *
200      * @param items List of items do delete. They must be valid.
201      * @param parent Parent to be used in dialogs.
202      * @return Returns an integer which identifies this deletion. This identifier is useful
203      *         to correlate this operation with the IncidenceChanger::deleteFinished() signal.
204      *
205      *         Returns -1 if any item is invalid or if @p items is empty. The deleteFinished() signal
206      *         won't be emitted in this case.
207      */
208     int deleteIncidences(const Akonadi::Item::List &items, QWidget *parent = nullptr);
209 
210     /**
211      * Modifies an incidence.
212      *
213      * @param item A valid item, with the new payload.
214      * @param originalPayload The payload before the modification. If invalid it won't be recorded
215      *                        to the undo stack and groupware functionality won't be used for this
216      *                        deletion.
217      * @param parent Parent to be used in dialogs.
218      *
219      * @return Returns an integer which identifies this modification. This identifier is useful
220      *         to correlate this operation with the IncidenceChanger::modifyFinished() signal.
221      *
222      *         Returns -1 if the item doesn't have a valid payload. The modifyFinished() signal
223      *         won't be emitted in this case.
224      */
225     int modifyIncidence(const Akonadi::Item &item,
226                                           const KCalendarCore::Incidence::Ptr &originalPayload = KCalendarCore::Incidence::Ptr(),
227                                           QWidget *parent = nullptr);
228 
229     /**
230      * Some incidence operations require more than one change. Like dissociating
231      * occurrences, which needs an incidence add and an incidence change.
232      *
233      * If you want to prevent that the same dialogs are presented multiple times
234      * use this function to start a batch operation.
235      *
236      * If one change belonging to a batch operation fails, all other changes
237      * are rolled back.
238      *
239      * @param operationDescription Describes what the atomic operation does.
240      *        This will be what incidenceChanger->history()->descriptionForNextUndo()
241      *        if you have history enabled.
242      *
243      * @see endAtomicOperation()
244      */
245     void startAtomicOperation(const QString &operationDescription = QString());
246 
247     /**
248      * Tells IncidenceChanger you finished doing changes that belong to a
249      * batch operation.
250      *
251      * @see startAtomicOperation()
252      */
253     void endAtomicOperation();
254 
255     /**
256      * Sets the base ETM tree model
257      * Used by the editor dialog's collection combobox, for instance.
258      */
259     void setEntityTreeModel(Akonadi::EntityTreeModel *model);
260 
261     /**
262      * Returns the base ETM tree model
263      */
264     Akonadi::EntityTreeModel *entityTreeModel() const;
265 
266     /**
267      * Sets the default collection.
268      * @param collection The collection to be used in createIncidence() if the
269      *        proper destination policy is set.
270      * @see createIncidence()
271      * @see destinationPolicy()
272      * @see defaultCollection()
273      */
274     void setDefaultCollection(const Akonadi::Collection &collection);
275 
276     /**
277      * Returns the defaultCollection.
278      * If none is set, an invalid Collection is returned.
279      * @see setDefaultCollection()
280      * @see DestinationPolicy
281      */
282     Q_REQUIRED_RESULT Akonadi::Collection defaultCollection() const;
283 
284     /**
285      * Sets the destination policy to use. The destination policy determines the
286      * collection to use in createIncidence()
287      *
288      * @see createIncidence()
289      * @see destinationPolicy()
290      */
291     void setDestinationPolicy(DestinationPolicy destinationPolicy);
292 
293     /**
294      * Returns the current destination policy.
295      * If none is set, DestinationPolicyDefault is returned.
296      * @see setDestinationPolicy()
297      * @see DestinationPolicy
298      */
299     Q_REQUIRED_RESULT DestinationPolicy destinationPolicy() const;
300 
301     /**
302      * Sets if IncidenceChanger should show error dialogs.
303      */
304     void setShowDialogsOnError(bool enable);
305 
306     /**
307      * Returns true if error dialogs are shown by IncidenceChanger.
308      * The default is true.
309      *
310      * @see setShowDialogsOnError()
311      */
312     Q_REQUIRED_RESULT bool showDialogsOnError() const;
313 
314     /**
315      * Sets if IncidenceChanger should honour collection's ACLs by disallowing changes if
316      * necessary.
317      */
318     void setRespectsCollectionRights(bool respect);
319 
320     /**
321      * Returns true if IncidenceChanger honors collection's ACLs by disallowing
322      * changes if necessary.
323      *
324      * The default is true.
325      * @see setRespectsCollectionRights()
326      * @see ResultCode::ResultCodePermissions
327      */
328     Q_REQUIRED_RESULT bool respectsCollectionRights() const;
329 
330     /**
331      * Enable or disable history.
332      * With history enabled all changes are recorded into the undo/redo stack.
333      *
334      * @see history()
335      * @see historyEnabled()
336      */
337     void setHistoryEnabled(bool enable);
338 
339     /**
340      * Returns true if changes are added into the undo stack.
341      * Default is true.
342      *
343      * @see history()
344      * @see historyEnabled()
345      */
346     Q_REQUIRED_RESULT bool historyEnabled() const;
347 
348     /**
349      * Returns a pointer to the history object.
350      * It's always valid.
351      * Ownership remains with IncidenceChanger.
352      */
353     History *history() const;
354 
355     /**
356      * For performance reasons, IncidenceChanger internally caches the ids of the last deleted items,
357      * to avoid creating useless delete jobs.
358      *
359      * This function exposes that functionality so it can be used in other scenarios.
360      * One popular scenario is when you're using an ETM and the user is deleting items very fast,
361      * ETM doesn't know about the deletions immediately, so it can happen that some items are
362      * deleted more than once, resulting in an error.
363      *
364      * @return true if the item was deleted recently, false otherwise.
365      */
366     Q_REQUIRED_RESULT bool deletedRecently(Akonadi::Item::Id) const;
367 
368     /**
369      * Enables or disabled groupware communication.
370      * With groupware communication enabled, invitations and update e-mails will be sent to each
371      * attendee.
372      */
373     void setGroupwareCommunication(bool enabled);
374 
375     /**
376      * Returns if we're using groupware communication.
377      * Default is false.
378      * @see setGroupwareCommuniation()
379      */
380     Q_REQUIRED_RESULT bool groupwareCommunication() const;
381 
382     /**
383      * Makes modifyIncidence() adjust recurrence parameters when modifying DTSTART.
384      */
385     void setAutoAdjustRecurrence(bool enable);
386 
387     /**
388      * True if recurrence parameters are adjusted when modifying DTSTART.
389      * Default is true.
390      */
391     Q_REQUIRED_RESULT bool autoAdjustRecurrence() const;
392 
393     /**
394      * Sets the invitation policy.
395      *
396      * @since 4.12
397      */
398     void setInvitationPolicy(InvitationPolicy policy);
399 
400     /**
401      * Returns the invitation policy.
402      * The default is InvitationPolicyAsk.
403      *
404      * @since 4.12
405      */
406     Q_REQUIRED_RESULT InvitationPolicy invitationPolicy() const;
407 
408     /**
409      * Returns the collection that the last createIncidence() used.
410      * Will be invalid if no incidences were created yet.
411      *
412      * @see createIncidence().
413      */
414     Q_REQUIRED_RESULT Akonadi::Collection lastCollectionUsed() const;
415 
416 Q_SIGNALS:
417     /**
418      * Emitted when IncidenceChanger creates an Incidence in akonadi.
419      *
420      * @param changeId the unique identifier of this change, returned by createIncidence().
421      * @param item the created item, might be invalid if the @p resultCode is not ResultCodeSuccess
422      * @param resultCode success/error code
423      * @param errorString if @p resultCode is not ResultCodeSuccess, contains an i18n'ed error
424      *        message. If you enabled error dialogs, this string was already presented to the user.
425      */
426     void createFinished(int changeId, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString);
427     /**
428      * Emitted when IncidenceChanger modifies an Incidence in akonadi.
429      *
430      * @param changeId the unique identifier of this change, returned by modifyIncidence().
431      * @param item the modified item, might be invalid if the @p resultCode is not ResultCodeSuccess
432      * @param resultCode success/error code
433      * @param errorString if @p resultCode is not ResultCodeSuccess, contains an i18n'ed error
434      *        message. If you enabled error dialogs, this string was already presented to the user.
435      */
436     void modifyFinished(int changeId, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString);
437     /**
438      * Emitted when IncidenceChanger deletes an Incidence in akonadi.
439      *
440      * @param changeId the unique identifier of this change, returned by deletedIncidence().
441      * @param itemIdList list of deleted item ids, might be emptu if the @p resultCode is not
442      *                   ResultCodeSuccess
443      * @param resultCode success/error code
444      * @param errorString if @p resultCode is not ResultCodeSuccess, contains an i18n'ed error
445      *        message. If you enabled error dialogs, this string was already presented to the user.
446      */
447     void
448     deleteFinished(int changeId, const QVector<Akonadi::Item::Id> &itemIdList, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString);
449 
450 private:
451     //@cond PRIVATE
452     friend class HistoryPrivate;
453     friend class AtomicOperation;
454     // used internally by the History class
455     explicit IncidenceChanger(bool enableHistory, QObject *parent = nullptr);
456 
457     std::unique_ptr<IncidenceChangerPrivate> const d;
458     //@endcond
459 };
460 }
461 
462 Q_DECLARE_METATYPE(Akonadi::IncidenceChanger::DestinationPolicy)
463 Q_DECLARE_METATYPE(Akonadi::IncidenceChanger::ResultCode)
464 Q_DECLARE_METATYPE(Akonadi::IncidenceChanger::ChangeType)
465 
466