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 CalendarClipboardPrivate::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 
39 CalendarClipboardPrivate::~CalendarClipboardPrivate()
40 {
41  delete m_dndfactory;
42 }
43 
44 void 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 
57 void 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 
76 void CalendarClipboardPrivate::cut(const KCalendarCore::Incidence::Ptr &incidence)
77 {
79  incidences << incidence;
80  cut(incidences);
81 }
82 
83 void 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 
117 void 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 
143 void CalendarClipboardPrivate::slotDeleteFinished(int changeId,
144  const QVector<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 
162 CalendarClipboard::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::questionYesNoCancel(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  i18n("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::Yes ? 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::questionYesNoCancel(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  i18n("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::Yes ? 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 
249 {
251 }
bool isValid() const
void append(const T &value)
bool isEmpty() const const
bool copyIncidence(const KCalendarCore::Incidence::Ptr &incidence, CalendarClipboard::Mode mode=RecursiveMode)
Copies the specified incidence into the clipboard.
Q_EMITQ_EMIT
@ RecursiveMode
The specified incidence's children are also cut/copied.
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
~CalendarClipboard() override
Destroys the CalendarClipboard instance.
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.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
bool hasPayload() 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.
bool pasteAvailable() const
Returns if there's any ical mime data available for pasting.
QClipboard * clipboard()
CalendarClipboard(const Akonadi::CalendarBase::Ptr &calendar, Akonadi::IncidenceChanger *changer=nullptr, QObject *parent=nullptr)
Constructs a new CalendarClipboard.
@ AskMode
The user is asked if he wants children to be cut/copied too.
void cutFinished(bool success, const QString &errorMessage)
Emitted after cutIncidences() finishes.
QString i18n(const char *text, const TYPE &arg...)
Class to copy or cut calendar incidences.
const QList< QKeySequence > & cut()
bool isEmpty() const const
KCALUTILS_EXPORT bool canDecode(const QMimeData *)
Id id() const
@ SingleMode
Only the specified incidence is cut/copied.
ButtonCode questionYesNoCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonYes=KStandardGuiItem::yes(), const KGuiItem &buttonNo=KStandardGuiItem::no(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
void setPayload(const T &p)
T payload() const
FreeBusyManager::Singleton.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Oct 6 2022 03:57:05 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.