Akonadi Calendar

calendarclipboard.cpp
1 /*
2  SPDX-FileCopyrightText: 2012 Sérgio Martins <[email protected]>
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 
17 using namespace Akonadi;
18 
19 CalendarClipboard::Private::Private(const Akonadi::CalendarBase::Ptr &calendar, Akonadi::IncidenceChanger *changer, CalendarClipboard *qq)
20  : QObject(qq)
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, &CalendarClipboard::Private::slotModifyFinished);
35 
36  connect(m_changer, &IncidenceChanger::deleteFinished, this, &CalendarClipboard::Private::slotDeleteFinished);
37 }
38 
39 CalendarClipboard::Private::~Private()
40 {
41  delete m_dndfactory;
42 }
43 
44 void CalendarClipboard::Private::getIncidenceHierarchy(const KCalendarCore::Incidence::Ptr &incidence, QStringList &uids)
45 {
46  // protecion 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 
57 void CalendarClipboard::Private::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 
76 void CalendarClipboard::Private::cut(const KCalendarCore::Incidence::Ptr &incidence)
77 {
79  incidences << incidence;
80  cut(incidences);
81 }
82 
83 void CalendarClipboard::Private::makeChildsIndependent(const KCalendarCore::Incidence::Ptr &incidence)
84 {
85  Q_ASSERT(incidence);
86  const KCalendarCore::Incidence::List childs = m_calendar->childIncidences(incidence->uid());
87 
88  if (childs.isEmpty()) {
89  cut(incidence);
90  } else {
91  m_pendingChangeIds.clear();
92  m_abortCurrentOperation = false;
93  for (const KCalendarCore::Incidence::Ptr &child : childs) {
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 
117 void CalendarClipboard::Private::slotModifyFinished(int changeId,
118  const Akonadi::Item &item,
119  IncidenceChanger::ResultCode resultCode,
120  const QString &errorMessage)
121 {
122  if (!m_pendingChangeIds.contains(changeId)) {
123  return; // Not ours, someone else deleted something, not our business.
124  }
125 
126  m_pendingChangeIds.remove(changeId);
127  const bool isLastChange = m_pendingChangeIds.isEmpty();
128 
129  Q_UNUSED(item)
130  Q_UNUSED(errorMessage)
131  if (m_abortCurrentOperation && isLastChange) {
132  Q_EMIT q->cutFinished(/**success=*/false, i18n("Error while removing relations."));
133  } else if (!m_abortCurrentOperation) {
134  if (resultCode == IncidenceChanger::ResultCodeSuccess) {
135  if (isLastChange) {
136  // All children are unparented, lets cut.
137  Q_ASSERT(item.isValid() && item.hasPayload());
139  }
140  } else {
141  m_abortCurrentOperation = true;
142  }
143  }
144 }
145 
146 void CalendarClipboard::Private::slotDeleteFinished(int changeId,
147  const QVector<Akonadi::Item::Id> &ids,
148  Akonadi::IncidenceChanger::ResultCode result,
149  const QString &errorMessage)
150 {
151  if (!m_pendingChangeIds.contains(changeId)) {
152  return; // Not ours, someone else deleted something, not our business.
153  }
154 
155  m_pendingChangeIds.remove(changeId);
156 
157  Q_UNUSED(ids)
158  if (result == IncidenceChanger::ResultCodeSuccess) {
159  Q_EMIT q->cutFinished(/**success=*/true, QString());
160  } else {
161  Q_EMIT q->cutFinished(/**success=*/false, i18n("Error while deleting incidences: %1", errorMessage));
162  }
163 }
164 
165 CalendarClipboard::CalendarClipboard(const Akonadi::CalendarBase::Ptr &calendar, Akonadi::IncidenceChanger *changer, QObject *parent)
166  : QObject(parent)
167  , d(new Private(calendar, changer, this))
168 {
169 }
170 
172 {
173 }
174 
176 {
177  const bool hasChildren = !d->m_calendar->childIncidences(incidence->uid()).isEmpty();
178  if (mode == AskMode && hasChildren) {
179  const int km = KMessageBox::questionYesNoCancel(nullptr,
180  i18n("The item \"%1\" has sub-to-dos. "
181  "Do you want to cut just this item and "
182  "make all its sub-to-dos independent, or "
183  "cut the to-do with all its sub-to-dos?",
184  incidence->summary()),
185  i18n("KOrganizer Confirmation"),
186  KGuiItem(i18n("Cut Only This")),
187  KGuiItem(i18n("Cut All")));
188 
189  if (km == KMessageBox::Cancel) {
190  Q_EMIT cutFinished(/*success=*/true, QString());
191  return;
192  }
193  mode = km == KMessageBox::Yes ? SingleMode : RecursiveMode;
194  } else if (mode == AskMode) {
195  mode = SingleMode; // Doesn't have children, don't ask
196  }
197 
198  if (mode == SingleMode) {
199  d->makeChildsIndependent(incidence); // Will call d->cut(incidence) when it finishes.
200  } else {
201  QStringList uids;
202  d->getIncidenceHierarchy(incidence, uids);
203  Q_ASSERT(!uids.isEmpty());
204  KCalendarCore::Incidence::List incidencesToCut;
205  for (const QString &uid : qAsConst(uids)) {
206  KCalendarCore::Incidence::Ptr child = d->m_calendar->incidence(uid);
207  if (child) {
208  incidencesToCut << child;
209  }
210  }
211  d->cut(incidencesToCut);
212  }
213 }
214 
216 {
217  const bool hasChildren = !d->m_calendar->childIncidences(incidence->uid()).isEmpty();
218  if (mode == AskMode && hasChildren) {
219  const int km = KMessageBox::questionYesNoCancel(nullptr,
220  i18n("The item \"%1\" has sub-to-dos. "
221  "Do you want to copy just this item or "
222  "copy the to-do with all its sub-to-dos?",
223  incidence->summary()),
224  i18n("KOrganizer Confirmation"),
225  KGuiItem(i18n("Copy Only This")),
226  KGuiItem(i18n("Copy All")));
227  if (km == KMessageBox::Cancel) {
228  return true;
229  }
230  mode = km == KMessageBox::Yes ? SingleMode : RecursiveMode;
231  } else if (mode == AskMode) {
232  mode = SingleMode; // Doesn't have children, don't ask
233  }
234 
235  KCalendarCore::Incidence::List incidencesToCopy;
236  if (mode == SingleMode) {
237  incidencesToCopy << incidence;
238  } else {
239  QStringList uids;
240  d->getIncidenceHierarchy(incidence, uids);
241  Q_ASSERT(!uids.isEmpty());
242  for (const QString &uid : qAsConst(uids)) {
243  KCalendarCore::Incidence::Ptr child = d->m_calendar->incidence(uid);
244  if (child) {
245  incidencesToCopy << child;
246  }
247  }
248  }
249 
250  return d->m_dndfactory->copyIncidences(incidencesToCopy);
251 }
252 
254 {
256 }
bool isValid() const
The specified incidence&#39;s children are also cut/copied.
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
Akonadi::Item item(const QString &uid) const
Returns the Item containing the incidence with uid uid or an invalid Item if the incidence isn&#39;t foun...
bool pasteAvailable() const
Returns if there&#39;s any ical mime data available for pasting.
Akonadi::Item::List items(Akonadi::Collection::Id=-1) const
Returns the list of items contained in this calendar that belong to the specified collection...
const QList< QKeySequence > & cut()
void setPayload(const T &p)
KCALUTILS_EXPORT bool canDecode(const QMimeData *)
T payload() const
Id id() const
void append(const T &value)
The user is asked if he wants children to be cut/copied too.
bool isEmpty() const const
Only the specified incidence is cut/copied.
bool copyIncidence(const KCalendarCore::Incidence::Ptr &incidence, CalendarClipboard::Mode mode=RecursiveMode)
Copies the specified incidence into the clipboard.
QString i18n(const char *text, const TYPE &arg...)
ButtonCode questionYesNoCancel(QWidget *parent, const QString &text, const QString &caption=QString(), const KGuiItem &buttonYes=KStandardGuiItem::yes(), const KGuiItem &buttonNo=KStandardGuiItem::no(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
bool isEmpty() const const
FreeBusyManager::Singleton.
~CalendarClipboard()
Destroys the CalendarClipboard instance.
Incidence::Ptr incidence(const QString &uid, const QDateTime &recurrenceId={}) const
void cutIncidence(const KCalendarCore::Incidence::Ptr &incidence, CalendarClipboard::Mode mode=RecursiveMode)
Copies the specified incidence into the clipboard and then deletes it from akonadi.
void cutFinished(bool success, const QString &errorMessage)
Emitted after cutIncidences() finishes.
bool hasPayload() const
Class to copy or cut calendar incidences.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
CalendarClipboard(const Akonadi::CalendarBase::Ptr &calendar, Akonadi::IncidenceChanger *changer=nullptr, QObject *parent=nullptr)
Constructs a new CalendarClipboard.
QClipboard * clipboard()
virtual Incidence::List incidences() const
Q_EMITQ_EMIT
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Jun 19 2021 23:12:24 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.