File indexing completed on 2024-05-12 05:10:42
0001 /* 0002 SPDX-FileCopyrightText: 2012 Sérgio Martins <iamsergio@gmail.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "calendarclipboard_p.h" 0008 #include <KCalUtils/DndFactory> 0009 #include <KCalUtils/ICalDrag> 0010 0011 #include <KLocalizedString> 0012 #include <KMessageBox> 0013 0014 #include <QApplication> 0015 #include <QClipboard> 0016 0017 using namespace Akonadi; 0018 0019 CalendarClipboardPrivate::CalendarClipboardPrivate(const Akonadi::CalendarBase::Ptr &calendar, Akonadi::IncidenceChanger *changer, CalendarClipboard *qq) 0020 : QObject() 0021 , m_calendar(calendar) 0022 , m_changer(changer) 0023 , q(qq) 0024 { 0025 Q_ASSERT(m_calendar); 0026 if (!m_changer) { 0027 m_changer = new Akonadi::IncidenceChanger(this); 0028 m_changer->setHistoryEnabled(false); 0029 m_changer->setGroupwareCommunication(false); 0030 } 0031 0032 m_dndfactory = new KCalUtils::DndFactory(m_calendar); 0033 0034 connect(m_changer, &IncidenceChanger::modifyFinished, this, &CalendarClipboardPrivate::slotModifyFinished); 0035 0036 connect(m_changer, &IncidenceChanger::deleteFinished, this, &CalendarClipboardPrivate::slotDeleteFinished); 0037 } 0038 0039 CalendarClipboardPrivate::~CalendarClipboardPrivate() 0040 { 0041 delete m_dndfactory; 0042 } 0043 0044 void CalendarClipboardPrivate::getIncidenceHierarchy(const KCalendarCore::Incidence::Ptr &incidence, QStringList &uids) 0045 { 0046 // protection against looping hierarchies 0047 if (incidence && !uids.contains(incidence->uid())) { 0048 const KCalendarCore::Incidence::List immediateChildren = m_calendar->childIncidences(incidence->uid()); 0049 0050 for (const KCalendarCore::Incidence::Ptr &child : immediateChildren) { 0051 getIncidenceHierarchy(child, uids); 0052 } 0053 uids.append(incidence->uid()); 0054 } 0055 } 0056 0057 void CalendarClipboardPrivate::cut(const KCalendarCore::Incidence::List &incidences) 0058 { 0059 const bool result = m_dndfactory->copyIncidences(incidences); 0060 m_pendingChangeIds.clear(); 0061 // Note: Don't use DndFactory::cutIncidences(), it doesn't use IncidenceChanger for deletion 0062 // we would loose async error handling and redo/undo features 0063 if (result) { 0064 Akonadi::Item::List items = m_calendar->itemList(incidences); 0065 const int result = m_changer->deleteIncidences(items); 0066 if (result == -1) { 0067 Q_EMIT q->cutFinished(/**success=*/false, i18n("Error performing deletion.")); 0068 } else { 0069 m_pendingChangeIds << result; 0070 } 0071 } else { 0072 Q_EMIT q->cutFinished(/**success=*/false, i18n("Error performing copy.")); 0073 } 0074 } 0075 0076 void CalendarClipboardPrivate::cut(const KCalendarCore::Incidence::Ptr &incidence) 0077 { 0078 KCalendarCore::Incidence::List incidences; 0079 incidences << incidence; 0080 cut(incidences); 0081 } 0082 0083 void CalendarClipboardPrivate::makeChildsIndependent(const KCalendarCore::Incidence::Ptr &incidence) 0084 { 0085 Q_ASSERT(incidence); 0086 const KCalendarCore::Incidence::List children = m_calendar->childIncidences(incidence->uid()); 0087 0088 if (children.isEmpty()) { 0089 cut(incidence); 0090 } else { 0091 m_pendingChangeIds.clear(); 0092 m_abortCurrentOperation = false; 0093 for (const KCalendarCore::Incidence::Ptr &child : children) { 0094 Akonadi::Item childItem = m_calendar->item(incidence); 0095 if (!childItem.isValid()) { 0096 Q_EMIT q->cutFinished(/**success=*/false, i18n("Can't find item: %1", childItem.id())); 0097 return; 0098 } 0099 0100 KCalendarCore::Incidence::Ptr newIncidence(child->clone()); 0101 newIncidence->setRelatedTo(QString()); 0102 childItem.setPayload<KCalendarCore::Incidence::Ptr>(newIncidence); 0103 const int changeId = m_changer->modifyIncidence(childItem, /*originalPayload*/ child); 0104 if (changeId == -1) { 0105 m_abortCurrentOperation = true; 0106 break; 0107 } else { 0108 m_pendingChangeIds << changeId; 0109 } 0110 } 0111 if (m_pendingChangeIds.isEmpty() && m_abortCurrentOperation) { 0112 Q_EMIT q->cutFinished(/**success=*/false, i18n("Error while removing relations.")); 0113 } // if m_pendingChangeIds isn't empty, we wait for all jobs to finish first. 0114 } 0115 } 0116 0117 void CalendarClipboardPrivate::slotModifyFinished(int changeId, const Akonadi::Item &item, IncidenceChanger::ResultCode resultCode, const QString &errorMessage) 0118 { 0119 if (!m_pendingChangeIds.contains(changeId)) { 0120 return; // Not ours, someone else deleted something, not our business. 0121 } 0122 0123 m_pendingChangeIds.remove(changeId); 0124 const bool isLastChange = m_pendingChangeIds.isEmpty(); 0125 0126 Q_UNUSED(item) 0127 Q_UNUSED(errorMessage) 0128 if (m_abortCurrentOperation && isLastChange) { 0129 Q_EMIT q->cutFinished(/**success=*/false, i18n("Error while removing relations.")); 0130 } else if (!m_abortCurrentOperation) { 0131 if (resultCode == IncidenceChanger::ResultCodeSuccess) { 0132 if (isLastChange) { 0133 // All children are unparented, lets cut. 0134 Q_ASSERT(item.isValid() && item.hasPayload()); 0135 cut(item.payload<KCalendarCore::Incidence::Ptr>()); 0136 } 0137 } else { 0138 m_abortCurrentOperation = true; 0139 } 0140 } 0141 } 0142 0143 void CalendarClipboardPrivate::slotDeleteFinished(int changeId, 0144 const QList<Akonadi::Item::Id> &ids, 0145 Akonadi::IncidenceChanger::ResultCode result, 0146 const QString &errorMessage) 0147 { 0148 if (!m_pendingChangeIds.contains(changeId)) { 0149 return; // Not ours, someone else deleted something, not our business. 0150 } 0151 0152 m_pendingChangeIds.remove(changeId); 0153 0154 Q_UNUSED(ids) 0155 if (result == IncidenceChanger::ResultCodeSuccess) { 0156 Q_EMIT q->cutFinished(/**success=*/true, QString()); 0157 } else { 0158 Q_EMIT q->cutFinished(/**success=*/false, i18n("Error while deleting incidences: %1", errorMessage)); 0159 } 0160 } 0161 0162 CalendarClipboard::CalendarClipboard(const Akonadi::CalendarBase::Ptr &calendar, Akonadi::IncidenceChanger *changer, QObject *parent) 0163 : QObject(parent) 0164 , d(new CalendarClipboardPrivate(calendar, changer, this)) 0165 { 0166 } 0167 0168 CalendarClipboard::~CalendarClipboard() = default; 0169 0170 void CalendarClipboard::cutIncidence(const KCalendarCore::Incidence::Ptr &incidence, CalendarClipboard::Mode mode) 0171 { 0172 const bool hasChildren = !d->m_calendar->childIncidences(incidence->uid()).isEmpty(); 0173 if (mode == AskMode && hasChildren) { 0174 const int km = KMessageBox::questionTwoActionsCancel(nullptr, 0175 i18n("The item \"%1\" has sub-to-dos. " 0176 "Do you want to cut just this item and " 0177 "make all its sub-to-dos independent, or " 0178 "cut the to-do with all its sub-to-dos?", 0179 incidence->summary()), 0180 i18nc("@title:window", "KOrganizer Confirmation"), 0181 KGuiItem(i18n("Cut Only This")), 0182 KGuiItem(i18n("Cut All"))); 0183 0184 if (km == KMessageBox::Cancel) { 0185 Q_EMIT cutFinished(/*success=*/true, QString()); 0186 return; 0187 } 0188 mode = km == KMessageBox::ButtonCode::PrimaryAction ? SingleMode : RecursiveMode; 0189 } else if (mode == AskMode) { 0190 mode = SingleMode; // Doesn't have children, don't ask 0191 } 0192 0193 if (mode == SingleMode) { 0194 d->makeChildsIndependent(incidence); // Will call d->cut(incidence) when it finishes. 0195 } else { 0196 QStringList uids; 0197 d->getIncidenceHierarchy(incidence, uids); 0198 Q_ASSERT(!uids.isEmpty()); 0199 KCalendarCore::Incidence::List incidencesToCut; 0200 for (const QString &uid : std::as_const(uids)) { 0201 KCalendarCore::Incidence::Ptr child = d->m_calendar->incidence(uid); 0202 if (child) { 0203 incidencesToCut << child; 0204 } 0205 } 0206 d->cut(incidencesToCut); 0207 } 0208 } 0209 0210 bool CalendarClipboard::copyIncidence(const KCalendarCore::Incidence::Ptr &incidence, CalendarClipboard::Mode mode) 0211 { 0212 const bool hasChildren = !d->m_calendar->childIncidences(incidence->uid()).isEmpty(); 0213 if (mode == AskMode && hasChildren) { 0214 const int km = KMessageBox::questionTwoActionsCancel(nullptr, 0215 i18n("The item \"%1\" has sub-to-dos. " 0216 "Do you want to copy just this item or " 0217 "copy the to-do with all its sub-to-dos?", 0218 incidence->summary()), 0219 i18nc("@title:window", "KOrganizer Confirmation"), 0220 KGuiItem(i18n("Copy Only This")), 0221 KGuiItem(i18n("Copy All"))); 0222 if (km == KMessageBox::Cancel) { 0223 return true; 0224 } 0225 mode = km == KMessageBox::ButtonCode::PrimaryAction ? SingleMode : RecursiveMode; 0226 } else if (mode == AskMode) { 0227 mode = SingleMode; // Doesn't have children, don't ask 0228 } 0229 0230 KCalendarCore::Incidence::List incidencesToCopy; 0231 if (mode == SingleMode) { 0232 incidencesToCopy << incidence; 0233 } else { 0234 QStringList uids; 0235 d->getIncidenceHierarchy(incidence, uids); 0236 Q_ASSERT(!uids.isEmpty()); 0237 for (const QString &uid : std::as_const(uids)) { 0238 KCalendarCore::Incidence::Ptr child = d->m_calendar->incidence(uid); 0239 if (child) { 0240 incidencesToCopy << child; 0241 } 0242 } 0243 } 0244 0245 return d->m_dndfactory->copyIncidences(incidencesToCopy); 0246 } 0247 0248 bool CalendarClipboard::pasteAvailable() const 0249 { 0250 return KCalUtils::ICalDrag::canDecode(QApplication::clipboard()->mimeData()); 0251 } 0252 0253 #include "moc_calendarclipboard.cpp" 0254 0255 #include "moc_calendarclipboard_p.cpp"