Akonadi Calendar

calendarclipboard.cpp
1/*
2 SPDX-FileCopyrightText: 2012 Sérgio Martins <iamsergio@gmail.com>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "calendarclipboard_p.h"
8#include <KCalUtils/DndFactory>
9#include <KCalUtils/ICalDrag>
10
11#include <KLocalizedString>
12#include <KMessageBox>
13
14#include <QApplication>
15#include <QClipboard>
16
17using namespace Akonadi;
18
19CalendarClipboardPrivate::CalendarClipboardPrivate(const Akonadi::CalendarBase::Ptr &calendar, Akonadi::IncidenceChanger *changer, CalendarClipboard *qq)
20 : QObject()
21 , m_calendar(calendar)
22 , m_changer(changer)
23 , q(qq)
24{
25 Q_ASSERT(m_calendar);
26 if (!m_changer) {
27 m_changer = new Akonadi::IncidenceChanger(this);
28 m_changer->setHistoryEnabled(false);
29 m_changer->setGroupwareCommunication(false);
30 }
31
32 m_dndfactory = new KCalUtils::DndFactory(m_calendar);
33
34 connect(m_changer, &IncidenceChanger::modifyFinished, this, &CalendarClipboardPrivate::slotModifyFinished);
35
36 connect(m_changer, &IncidenceChanger::deleteFinished, this, &CalendarClipboardPrivate::slotDeleteFinished);
37}
38
39CalendarClipboardPrivate::~CalendarClipboardPrivate()
40{
41 delete m_dndfactory;
42}
43
44void CalendarClipboardPrivate::getIncidenceHierarchy(const KCalendarCore::Incidence::Ptr &incidence, QStringList &uids)
45{
46 // protection against looping hierarchies
47 if (incidence && !uids.contains(incidence->uid())) {
48 const KCalendarCore::Incidence::List immediateChildren = m_calendar->childIncidences(incidence->uid());
49
50 for (const KCalendarCore::Incidence::Ptr &child : immediateChildren) {
51 getIncidenceHierarchy(child, uids);
52 }
53 uids.append(incidence->uid());
54 }
55}
56
57void CalendarClipboardPrivate::cut(const KCalendarCore::Incidence::List &incidences)
58{
59 const bool result = m_dndfactory->copyIncidences(incidences);
60 m_pendingChangeIds.clear();
61 // Note: Don't use DndFactory::cutIncidences(), it doesn't use IncidenceChanger for deletion
62 // we would loose async error handling and redo/undo features
63 if (result) {
64 Akonadi::Item::List items = m_calendar->itemList(incidences);
65 const int result = m_changer->deleteIncidences(items);
66 if (result == -1) {
67 Q_EMIT q->cutFinished(/**success=*/false, i18n("Error performing deletion."));
68 } else {
69 m_pendingChangeIds << result;
70 }
71 } else {
72 Q_EMIT q->cutFinished(/**success=*/false, i18n("Error performing copy."));
73 }
74}
75
76void CalendarClipboardPrivate::cut(const KCalendarCore::Incidence::Ptr &incidence)
77{
79 incidences << incidence;
80 cut(incidences);
81}
82
83void CalendarClipboardPrivate::makeChildsIndependent(const KCalendarCore::Incidence::Ptr &incidence)
84{
85 Q_ASSERT(incidence);
86 const KCalendarCore::Incidence::List children = m_calendar->childIncidences(incidence->uid());
87
88 if (children.isEmpty()) {
89 cut(incidence);
90 } else {
91 m_pendingChangeIds.clear();
92 m_abortCurrentOperation = false;
93 for (const KCalendarCore::Incidence::Ptr &child : children) {
94 Akonadi::Item childItem = m_calendar->item(incidence);
95 if (!childItem.isValid()) {
96 Q_EMIT q->cutFinished(/**success=*/false, i18n("Can't find item: %1", childItem.id()));
97 return;
98 }
99
100 KCalendarCore::Incidence::Ptr newIncidence(child->clone());
101 newIncidence->setRelatedTo(QString());
102 childItem.setPayload<KCalendarCore::Incidence::Ptr>(newIncidence);
103 const int changeId = m_changer->modifyIncidence(childItem, /*originalPayload*/ child);
104 if (changeId == -1) {
105 m_abortCurrentOperation = true;
106 break;
107 } else {
108 m_pendingChangeIds << changeId;
109 }
110 }
111 if (m_pendingChangeIds.isEmpty() && m_abortCurrentOperation) {
112 Q_EMIT q->cutFinished(/**success=*/false, i18n("Error while removing relations."));
113 } // if m_pendingChangeIds isn't empty, we wait for all jobs to finish first.
114 }
115}
116
117void CalendarClipboardPrivate::slotModifyFinished(int changeId, const Akonadi::Item &item, IncidenceChanger::ResultCode resultCode, const QString &errorMessage)
118{
119 if (!m_pendingChangeIds.contains(changeId)) {
120 return; // Not ours, someone else deleted something, not our business.
121 }
122
123 m_pendingChangeIds.remove(changeId);
124 const bool isLastChange = m_pendingChangeIds.isEmpty();
125
126 Q_UNUSED(item)
127 Q_UNUSED(errorMessage)
128 if (m_abortCurrentOperation && isLastChange) {
129 Q_EMIT q->cutFinished(/**success=*/false, i18n("Error while removing relations."));
130 } else if (!m_abortCurrentOperation) {
131 if (resultCode == IncidenceChanger::ResultCodeSuccess) {
132 if (isLastChange) {
133 // All children are unparented, lets cut.
134 Q_ASSERT(item.isValid() && item.hasPayload());
136 }
137 } else {
138 m_abortCurrentOperation = true;
139 }
140 }
141}
142
143void CalendarClipboardPrivate::slotDeleteFinished(int changeId,
144 const QList<Akonadi::Item::Id> &ids,
145 Akonadi::IncidenceChanger::ResultCode result,
146 const QString &errorMessage)
147{
148 if (!m_pendingChangeIds.contains(changeId)) {
149 return; // Not ours, someone else deleted something, not our business.
150 }
151
152 m_pendingChangeIds.remove(changeId);
153
154 Q_UNUSED(ids)
155 if (result == IncidenceChanger::ResultCodeSuccess) {
156 Q_EMIT q->cutFinished(/**success=*/true, QString());
157 } else {
158 Q_EMIT q->cutFinished(/**success=*/false, i18n("Error while deleting incidences: %1", errorMessage));
159 }
160}
161
162CalendarClipboard::CalendarClipboard(const Akonadi::CalendarBase::Ptr &calendar, Akonadi::IncidenceChanger *changer, QObject *parent)
163 : QObject(parent)
164 , d(new CalendarClipboardPrivate(calendar, changer, this))
165{
166}
167
169
171{
172 const bool hasChildren = !d->m_calendar->childIncidences(incidence->uid()).isEmpty();
173 if (mode == AskMode && hasChildren) {
174 const int km = KMessageBox::questionTwoActionsCancel(nullptr,
175 i18n("The item \"%1\" has sub-to-dos. "
176 "Do you want to cut just this item and "
177 "make all its sub-to-dos independent, or "
178 "cut the to-do with all its sub-to-dos?",
179 incidence->summary()),
180 i18nc("@title:window", "KOrganizer Confirmation"),
181 KGuiItem(i18n("Cut Only This")),
182 KGuiItem(i18n("Cut All")));
183
184 if (km == KMessageBox::Cancel) {
185 Q_EMIT cutFinished(/*success=*/true, QString());
186 return;
187 }
188 mode = km == KMessageBox::ButtonCode::PrimaryAction ? SingleMode : RecursiveMode;
189 } else if (mode == AskMode) {
190 mode = SingleMode; // Doesn't have children, don't ask
191 }
192
193 if (mode == SingleMode) {
194 d->makeChildsIndependent(incidence); // Will call d->cut(incidence) when it finishes.
195 } else {
196 QStringList uids;
197 d->getIncidenceHierarchy(incidence, uids);
198 Q_ASSERT(!uids.isEmpty());
199 KCalendarCore::Incidence::List incidencesToCut;
200 for (const QString &uid : std::as_const(uids)) {
201 KCalendarCore::Incidence::Ptr child = d->m_calendar->incidence(uid);
202 if (child) {
203 incidencesToCut << child;
204 }
205 }
206 d->cut(incidencesToCut);
207 }
208}
209
211{
212 const bool hasChildren = !d->m_calendar->childIncidences(incidence->uid()).isEmpty();
213 if (mode == AskMode && hasChildren) {
214 const int km = KMessageBox::questionTwoActionsCancel(nullptr,
215 i18n("The item \"%1\" has sub-to-dos. "
216 "Do you want to copy just this item or "
217 "copy the to-do with all its sub-to-dos?",
218 incidence->summary()),
219 i18nc("@title:window", "KOrganizer Confirmation"),
220 KGuiItem(i18n("Copy Only This")),
221 KGuiItem(i18n("Copy All")));
222 if (km == KMessageBox::Cancel) {
223 return true;
224 }
225 mode = km == KMessageBox::ButtonCode::PrimaryAction ? SingleMode : RecursiveMode;
226 } else if (mode == AskMode) {
227 mode = SingleMode; // Doesn't have children, don't ask
228 }
229
230 KCalendarCore::Incidence::List incidencesToCopy;
231 if (mode == SingleMode) {
232 incidencesToCopy << incidence;
233 } else {
234 QStringList uids;
235 d->getIncidenceHierarchy(incidence, uids);
236 Q_ASSERT(!uids.isEmpty());
237 for (const QString &uid : std::as_const(uids)) {
238 KCalendarCore::Incidence::Ptr child = d->m_calendar->incidence(uid);
239 if (child) {
240 incidencesToCopy << child;
241 }
242 }
243 }
244
245 return d->m_dndfactory->copyIncidences(incidencesToCopy);
246}
247
252
253#include "moc_calendarclipboard.cpp"
254
255#include "moc_calendarclipboard_p.cpp"
Class to copy or cut calendar incidences.
@ AskMode
The user is asked if he wants children to be cut/copied too.
@ SingleMode
Only the specified incidence is cut/copied.
@ RecursiveMode
The specified incidence's children are also cut/copied.
~CalendarClipboard() override
Destroys the CalendarClipboard instance.
void cutFinished(bool success, const QString &errorMessage)
Emitted after cutIncidences() finishes.
CalendarClipboard(const Akonadi::CalendarBase::Ptr &calendar, Akonadi::IncidenceChanger *changer=nullptr, QObject *parent=nullptr)
Constructs a new CalendarClipboard.
void cutIncidence(const KCalendarCore::Incidence::Ptr &incidence, CalendarClipboard::Mode mode=RecursiveMode)
Copies the specified incidence into the clipboard and then deletes it from akonadi.
bool pasteAvailable() const
Returns if there's any ical mime data available for pasting.
bool copyIncidence(const KCalendarCore::Incidence::Ptr &incidence, CalendarClipboard::Mode mode=RecursiveMode)
Copies the specified incidence into the clipboard.
void setPayload(const T &p)
bool hasPayload() const
Id id() const
T payload() const
bool isValid() const
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Incidence::Ptr incidence(const Akonadi::Item &item)
Returns the incidence from an Akonadi item, or a null pointer if the item has no such payload.
FreeBusyManager::Singleton.
KCALUTILS_EXPORT bool canDecode(const QMimeData *)
ButtonCode questionTwoActionsCancel(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const KGuiItem &cancelAction=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
const QList< QKeySequence > & cut()
QClipboard * clipboard()
void append(QList< T > &&value)
bool isEmpty() const const
Q_EMITQ_EMIT
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:17:16 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.