File indexing completed on 2024-05-12 05:10:44

0001 /*
0002   SPDX-FileCopyrightText: 2010-2012 Sérgio Martins <iamsergio@gmail.com>
0003 
0004   SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #pragma once
0008 
0009 #include "akonadi-calendar_export.h"
0010 #include "incidencechanger.h"
0011 
0012 #include <Akonadi/Item>
0013 #include <KCalendarCore/Incidence>
0014 #include <QWidget>
0015 
0016 #include <memory>
0017 
0018 class HistoryTest;
0019 
0020 namespace Akonadi
0021 {
0022 class IncidenceChanger;
0023 class HistoryPrivate;
0024 
0025 /**
0026    @short History class for implementing undo/redo of calendar operations
0027 
0028    Whenever you use IncidenceChanger to create, delete or modify incidences,
0029    this class is used to record those changes onto a stack, so they can be
0030    undone or redone.
0031 
0032    If needed, groupware invitations will be sent to attendees and organizers when
0033    undoing or redoing changes.
0034 
0035    This class can't be instantiated directly, use it through IncidenceChanger:
0036 
0037    @code
0038       Akonadi::IncidenceChanger *myIncidenceChanger = new Akonadi::IncidenceChanger();
0039       connect(undoAction, &QAction::triggered, myIncidenceChanger->history(), &Akonadi::IncidenceChanger::undo);
0040       connect(redoAction, &QAction::triggered, myIncidenceChanger->history(), &Akonadi::IncidenceChanger::redo);
0041    @endcode
0042 
0043    @author Sérgio Martins <iamsergio@gmail.com>
0044    @since 4.11
0045 */
0046 
0047 class AKONADI_CALENDAR_EXPORT History : public QObject
0048 {
0049     Q_OBJECT
0050 public:
0051     /**
0052      * This enum describes the possible result codes (success/error values) for an
0053      * undo or redo operation.
0054      * @see undone()
0055      * @see redone()
0056      */
0057     enum ResultCode {
0058         ResultCodeSuccess = 0, ///< Success
0059         ResultCodeError ///< An error occurred. Call lastErrorString() for the error message. This isn't very verbose because IncidenceChanger hasn't been
0060                         ///< refactored yet.
0061     };
0062 
0063     /**
0064      * Destroys the History instance.
0065      */
0066     ~History() override;
0067 
0068     /**
0069      * Pushes an incidence creation onto the undo stack. The creation can be
0070      * undone calling undo().
0071      *
0072      * @param item the item that was created. Must be valid and have a Incidence::Ptr payload
0073      * @param description text that can be used in the undo/redo menu item to describe the operation
0074      *        If empty, a default one will be provided.
0075      * @param atomicOperationId if not 0, specifies which group of changes this change belongs to.
0076      *        When a change is undone/redone, all other changes which are in the same group are also
0077      *        undone/redone
0078      */
0079     void recordCreation(const Akonadi::Item &item, const QString &description, const uint atomicOperationId = 0);
0080 
0081     /**
0082      * Pushes an incidence modification onto the undo stack. The modification can be undone calling
0083      * undo().
0084      *
0085      * @param oldItem item containing the payload before the change. Must be valid
0086      *        and contain an Incidence::Ptr payload.
0087      * @param newItem item containing the new payload. Must be valid and contain
0088      *        an Incidence::Ptr payload.
0089      * @param description text that can be used in the undo/redo menu item to describe the operation
0090      *        If empty, a default one will be provided.
0091      * @param atomicOperationId if not 0, specifies which group of changes this change belongs to.
0092      *        When a change is undone/redone, all other changes which are in the same group are also
0093      *        undone/redone
0094      */
0095     void recordModification(const Akonadi::Item &oldItem, const Akonadi::Item &newItem, const QString &description, const uint atomicOperationId = 0);
0096 
0097     /**
0098      * Pushes an incidence deletion onto the undo stack. The deletion can be
0099      * undone calling undo().
0100      *
0101      * @param item The item to delete. Must be valid, doesn't need to contain a
0102      *        payload.
0103      * @param description text that can be used in the undo/redo menu item to describe the operation
0104      *        If empty, a default one will be provided.
0105      * @param atomicOperationId if not 0, specifies which group of changes this change belongs to.
0106      *        When a change is undone/redone, all other changes which are in the same group are also
0107      *        undone/redone
0108      */
0109     void recordDeletion(const Akonadi::Item &item, const QString &description, const uint atomicOperationId = 0);
0110 
0111     /**
0112      * Pushes a list of incidence deletions onto the undo stack. The deletions can
0113      * be undone calling undo() once.
0114      *
0115      * @param items The list of items to delete. All items must be valid.
0116      * @param description text that can be used in the undo/redo menu item to describe the operation
0117      *        If empty, a default one will be provided.
0118      * @param atomicOperationId If != 0, specifies which group of changes thischange belongs to.
0119      *        Will be useful for atomic undoing/redoing, not implemented yet.
0120      */
0121     void recordDeletions(const Akonadi::Item::List &items, const QString &description, const uint atomicOperationId = 0);
0122 
0123     /**
0124      * Returns the last error message.
0125      *
0126      * Call this immediately after catching the undone()/redone() signal
0127      * with an ResultCode != ResultCodeSuccess.
0128      *
0129      * The message is translated.
0130      */
0131     [[nodiscard]] QString lastErrorString() const;
0132 
0133     /**
0134      * Reverts every change in the undo stack.
0135      *
0136      * @param parent will be passed to dialogs created by IncidenceChanger,
0137      *        for example those asking if you want to send invitations.
0138      */
0139     void undoAll(QWidget *parent = nullptr);
0140 
0141     /**
0142      * Returns true if there are changes that can be undone.
0143      */
0144     [[nodiscard]] bool undoAvailable() const;
0145 
0146     /**
0147      * Returns true if there are changes that can be redone.
0148      */
0149     [[nodiscard]] bool redoAvailable() const;
0150 
0151     /**
0152      * Returns the description of the next undo.
0153      *
0154      * This is the description that was passed when calling recordCreation(),
0155      * recordDeletion() or recordModification().
0156      *
0157      * @see nextRedoDescription()
0158      */
0159     [[nodiscard]] QString nextUndoDescription() const;
0160 
0161     /**
0162      * Returns the description of the next redo.
0163      *
0164      * This is the description that was passed when calling recordCreation(),
0165      * recordDeletion() or recordModification().
0166      *
0167      * @see nextUndoDescription()
0168      */
0169     [[nodiscard]] QString nextRedoDescription() const;
0170 
0171 public Q_SLOTS:
0172     /**
0173      * Clears the undo and redo stacks.
0174      * Won't do anything if there's a undo/redo job currently running.
0175      *
0176      * @return true if the stacks were cleared, false if there was a job running
0177      */
0178     bool clear();
0179 
0180     /**
0181      * Reverts the change that's on top of the undo stack.
0182      * Can't be called if there's an undo/redo operation running, asserts.
0183      * Can be called if the stack is empty, in this case, nothing happens.
0184      * This function is async, catch signal undone() to know when the operation
0185      * finishes.
0186      *
0187      * @param parent will be passed to dialogs created by IncidenceChanger,
0188      *        for example those asking if you want to send invitations.
0189      *
0190      * @see redo()
0191      * @see undone()
0192      */
0193     void undo(QWidget *parent = nullptr);
0194 
0195     /**
0196      * Reverts the change that's on top of the redo stack.
0197      * Can't be called if there's an undo/redo operation running, asserts.
0198      * Can be called if the stack is empty, in this case, nothing happens.
0199      * This function is async, catch signal redone() to know when the operation
0200      * finishes.
0201      *
0202      * @param parent will be passed to dialogs created by IncidenceChanger,
0203      *        for example those asking if you want to send invitations.
0204      *
0205      * @see undo()
0206      * @see redone()
0207      */
0208     void redo(QWidget *parent = nullptr);
0209 
0210 Q_SIGNALS:
0211     /**
0212      * This signal is emitted when an undo operation finishes.
0213      * @param resultCode History::ResultCodeSuccess on success.
0214      * @see lastErrorString()
0215      */
0216     void undone(Akonadi::History::ResultCode resultCode);
0217 
0218     /**
0219      * This signal is emitted when an redo operation finishes.
0220      * @param resultCode History::ResultCodeSuccess on success.
0221      * @see lastErrorString()
0222      */
0223     void redone(Akonadi::History::ResultCode resultCode);
0224 
0225     /**
0226      * The redo/undo stacks have changed.
0227      */
0228     void changed();
0229 
0230 private:
0231     friend class ::HistoryTest;
0232     friend class IncidenceChanger;
0233     friend class IncidenceChangerPrivate;
0234     friend class Entry;
0235 
0236     // Only IncidenceChanger can create History classes
0237     explicit History(QObject *parent = nullptr);
0238 
0239     // Used by unit-tests
0240     Akonadi::IncidenceChanger *incidenceChanger() const;
0241 
0242 private:
0243     //@cond PRIVATE
0244     std::unique_ptr<HistoryPrivate> const d;
0245     //@endcond
0246 };
0247 }