File indexing completed on 2024-05-12 05:15:01

0001 /*
0002   This file is part of the kcalutils library.
0003 
0004   SPDX-FileCopyrightText: 1998 Preston Brown <pbrown@kde.org>
0005   SPDX-FileCopyrightText: 2001, 2002 Cornelius Schumacher <schumacher@kde.org>
0006   SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
0007   SPDX-FileCopyrightText: 2005 Rafal Rzepecki <divide@users.sourceforge.net>
0008   SPDX-FileCopyrightText: 2008 Thomas Thrainer <tom_t@gmx.at>
0009 
0010   SPDX-License-Identifier: LGPL-2.0-or-later
0011 */
0012 /**
0013   @file
0014   This file is part of the API for handling calendar data and
0015   defines the DndFactory class.
0016 
0017   @brief
0018   vCalendar/iCalendar Drag-and-Drop object factory.
0019 
0020   @author Preston Brown \<pbrown@kde.org\>
0021   @author Cornelius Schumacher \<schumacher@kde.org\>
0022   @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
0023 */
0024 #include "dndfactory.h"
0025 #include "icaldrag.h"
0026 #include "vcaldrag.h"
0027 
0028 #include "kcalutils_debug.h"
0029 #include <KCalendarCore/MemoryCalendar>
0030 #include <KIconLoader>
0031 #include <KUrlMimeData>
0032 #include <QUrl>
0033 
0034 #include <QApplication>
0035 #include <QClipboard>
0036 #include <QDate>
0037 #include <QDrag>
0038 #include <QDropEvent>
0039 #include <QIcon>
0040 #include <QMimeData>
0041 #include <QTimeZone>
0042 
0043 using namespace KCalendarCore;
0044 using namespace KCalUtils;
0045 
0046 static QDateTime copyTimeSpec(QDateTime dt, const QDateTime &source)
0047 {
0048     switch (source.timeSpec()) {
0049     case Qt::TimeZone:
0050         return dt.toTimeZone(source.timeZone());
0051     case Qt::LocalTime:
0052     case Qt::UTC:
0053         return dt.toTimeSpec(source.timeSpec());
0054     case Qt::OffsetFromUTC:
0055         return dt.toOffsetFromUtc(source.offsetFromUtc());
0056     }
0057 
0058     Q_UNREACHABLE();
0059 }
0060 
0061 /**
0062   DndFactoryPrivate class that helps to provide binary compatibility between releases.
0063   @internal
0064 */
0065 //@cond PRIVATE
0066 class KCalUtils::DndFactoryPrivate
0067 {
0068 public:
0069     DndFactoryPrivate(const Calendar::Ptr &calendar)
0070         : mCalendar(calendar)
0071     {
0072     }
0073 
0074     Incidence::Ptr pasteIncidence(const Incidence::Ptr &incidence, QDateTime newDateTime, DndFactory::PasteFlags pasteOptions)
0075     {
0076         Incidence::Ptr inc(incidence);
0077 
0078         if (inc) {
0079             inc = Incidence::Ptr(inc->clone());
0080             inc->recreate();
0081         }
0082 
0083         if (inc && newDateTime.isValid()) {
0084             if (inc->type() == Incidence::TypeEvent) {
0085                 Event::Ptr event = inc.staticCast<Event>();
0086                 if (pasteOptions & DndFactory::FlagPasteAtOriginalTime) {
0087                     // Set date and preserve time and timezone stuff
0088                     const QDate date = newDateTime.date();
0089                     newDateTime = event->dtStart();
0090                     newDateTime.setDate(date);
0091                 }
0092 
0093                 // in seconds
0094                 const qint64 durationInSeconds = event->dtStart().secsTo(event->dtEnd());
0095                 const qint64 durationInDays = event->dtStart().daysTo(event->dtEnd());
0096 
0097                 if (incidence->allDay()) {
0098                     event->setDtStart(QDateTime(newDateTime.date(), {}));
0099                     event->setDtEnd(newDateTime.addDays(durationInDays));
0100                 } else {
0101                     event->setDtStart(copyTimeSpec(newDateTime, event->dtStart()));
0102                     event->setDtEnd(copyTimeSpec(newDateTime.addSecs(durationInSeconds), event->dtEnd()));
0103                 }
0104             } else if (inc->type() == Incidence::TypeTodo) {
0105                 Todo::Ptr aTodo = inc.staticCast<Todo>();
0106                 const bool pasteAtDtStart = (pasteOptions & DndFactory::FlagTodosPasteAtDtStart);
0107                 if (pasteOptions & DndFactory::FlagPasteAtOriginalTime) {
0108                     // Set date and preserve time and timezone stuff
0109                     const QDate date = newDateTime.date();
0110                     newDateTime = pasteAtDtStart ? aTodo->dtStart() : aTodo->dtDue();
0111                     newDateTime.setDate(date);
0112                 }
0113                 if (pasteAtDtStart) {
0114                     aTodo->setDtStart(copyTimeSpec(newDateTime, aTodo->dtStart()));
0115                 } else {
0116                     aTodo->setDtDue(copyTimeSpec(newDateTime, aTodo->dtDue()));
0117                 }
0118             } else if (inc->type() == Incidence::TypeJournal) {
0119                 if (pasteOptions & DndFactory::FlagPasteAtOriginalTime) {
0120                     // Set date and preserve time and timezone stuff
0121                     const QDate date = newDateTime.date();
0122                     newDateTime = inc->dtStart();
0123                     newDateTime.setDate(date);
0124                 }
0125                 inc->setDtStart(copyTimeSpec(newDateTime, inc->dtStart()));
0126             } else {
0127                 qCDebug(KCALUTILS_LOG) << "Trying to paste unknown incidence of type" << int(inc->type());
0128             }
0129         }
0130 
0131         return inc;
0132     }
0133 
0134     const Calendar::Ptr mCalendar;
0135 };
0136 //@endcond
0137 
0138 DndFactory::DndFactory(const Calendar::Ptr &calendar)
0139     : d(new KCalUtils::DndFactoryPrivate(calendar))
0140 {
0141 }
0142 
0143 DndFactory::~DndFactory() = default;
0144 
0145 QMimeData *DndFactory::createMimeData()
0146 {
0147     auto mimeData = new QMimeData;
0148 
0149     ICalDrag::populateMimeData(mimeData, d->mCalendar);
0150 
0151     return mimeData;
0152 }
0153 
0154 QDrag *DndFactory::createDrag(QObject *owner)
0155 {
0156     auto drag = new QDrag(owner);
0157     drag->setMimeData(createMimeData());
0158 
0159     return drag;
0160 }
0161 
0162 QMimeData *DndFactory::createMimeData(const Incidence::Ptr &incidence)
0163 {
0164     Calendar::Ptr cal(new MemoryCalendar(d->mCalendar->timeZone()));
0165     Incidence::Ptr i(incidence->clone());
0166     // strip recurrence id's, We don't want to drag the exception but the occurrence.
0167     i->setRecurrenceId({});
0168     cal->addIncidence(i);
0169 
0170     auto mimeData = new QMimeData;
0171 
0172     ICalDrag::populateMimeData(mimeData, cal);
0173 
0174     QUrl uri = i->uri();
0175     if (uri.isValid()) {
0176         QMap<QString, QString> metadata;
0177         metadata[QStringLiteral("labels")] = QLatin1StringView(QUrl::toPercentEncoding(i->summary()));
0178         mimeData->setUrls(QList<QUrl>() << uri);
0179         KUrlMimeData::setMetaData(metadata, mimeData);
0180     }
0181 
0182     return mimeData;
0183 }
0184 
0185 QDrag *DndFactory::createDrag(const Incidence::Ptr &incidence, QObject *owner)
0186 {
0187     auto drag = new QDrag(owner);
0188     drag->setMimeData(createMimeData(incidence));
0189     drag->setPixmap(QIcon::fromTheme(incidence->iconName()).pixmap(KIconLoader::SizeSmallMedium));
0190 
0191     return drag;
0192 }
0193 
0194 Calendar::Ptr DndFactory::createDropCalendar(const QMimeData *mimeData)
0195 {
0196     if (mimeData) {
0197         Calendar::Ptr calendar(new MemoryCalendar(QTimeZone::systemTimeZone()));
0198 
0199         if (ICalDrag::fromMimeData(mimeData, calendar) || VCalDrag::fromMimeData(mimeData, calendar)) {
0200             return calendar;
0201         }
0202     }
0203 
0204     return Calendar::Ptr();
0205 }
0206 
0207 Calendar::Ptr DndFactory::createDropCalendar(QDropEvent *dropEvent)
0208 {
0209     Calendar::Ptr calendar(createDropCalendar(dropEvent->mimeData()));
0210     if (calendar) {
0211         dropEvent->accept();
0212         return calendar;
0213     }
0214     return MemoryCalendar::Ptr();
0215 }
0216 
0217 Event::Ptr DndFactory::createDropEvent(const QMimeData *mimeData)
0218 {
0219     // qCDebug(KCALUTILS_LOG);
0220     Event::Ptr event;
0221     Calendar::Ptr calendar(createDropCalendar(mimeData));
0222 
0223     if (calendar) {
0224         Event::List events = calendar->events();
0225         if (!events.isEmpty()) {
0226             event = Event::Ptr(new Event(*events.first()));
0227         }
0228     }
0229     return event;
0230 }
0231 
0232 Event::Ptr DndFactory::createDropEvent(QDropEvent *dropEvent)
0233 {
0234     Event::Ptr event = createDropEvent(dropEvent->mimeData());
0235 
0236     if (event) {
0237         dropEvent->accept();
0238     }
0239 
0240     return event;
0241 }
0242 
0243 Todo::Ptr DndFactory::createDropTodo(const QMimeData *mimeData)
0244 {
0245     // qCDebug(KCALUTILS_LOG);
0246     Todo::Ptr todo;
0247     Calendar::Ptr calendar(createDropCalendar(mimeData));
0248 
0249     if (calendar) {
0250         Todo::List todos = calendar->todos();
0251         if (!todos.isEmpty()) {
0252             todo = Todo::Ptr(new Todo(*todos.first()));
0253         }
0254     }
0255 
0256     return todo;
0257 }
0258 
0259 Todo::Ptr DndFactory::createDropTodo(QDropEvent *dropEvent)
0260 {
0261     Todo::Ptr todo = createDropTodo(dropEvent->mimeData());
0262 
0263     if (todo) {
0264         dropEvent->accept();
0265     }
0266 
0267     return todo;
0268 }
0269 
0270 void DndFactory::cutIncidence(const Incidence::Ptr &selectedIncidence)
0271 {
0272     Incidence::List list;
0273     list.append(selectedIncidence);
0274     cutIncidences(list);
0275 }
0276 
0277 bool DndFactory::cutIncidences(const Incidence::List &incidences)
0278 {
0279     if (copyIncidences(incidences)) {
0280         Incidence::List::ConstIterator it;
0281         const Incidence::List::ConstIterator end(incidences.constEnd());
0282         for (it = incidences.constBegin(); it != end; ++it) {
0283             d->mCalendar->deleteIncidence(*it);
0284         }
0285         return true;
0286     } else {
0287         return false;
0288     }
0289 }
0290 
0291 bool DndFactory::copyIncidences(const Incidence::List &incidences)
0292 {
0293     QClipboard *clipboard = QApplication::clipboard();
0294     Q_ASSERT(clipboard);
0295     Calendar::Ptr calendar(new MemoryCalendar(d->mCalendar->timeZone()));
0296 
0297     Incidence::List::ConstIterator it;
0298     const Incidence::List::ConstIterator end(incidences.constEnd());
0299     for (it = incidences.constBegin(); it != end; ++it) {
0300         if (*it) {
0301             calendar->addIncidence(Incidence::Ptr((*it)->clone()));
0302         }
0303     }
0304 
0305     auto mimeData = new QMimeData;
0306 
0307     ICalDrag::populateMimeData(mimeData, calendar);
0308 
0309     if (calendar->incidences().isEmpty()) {
0310         return false;
0311     } else {
0312         clipboard->setMimeData(mimeData);
0313         return true;
0314     }
0315 }
0316 
0317 bool DndFactory::copyIncidence(const Incidence::Ptr &selectedInc)
0318 {
0319     Incidence::List list;
0320     list.append(selectedInc);
0321     return copyIncidences(list);
0322 }
0323 
0324 Incidence::List DndFactory::pasteIncidences(const QDateTime &newDateTime, PasteFlags pasteOptions)
0325 {
0326     QClipboard *clipboard = QApplication::clipboard();
0327     Q_ASSERT(clipboard);
0328     Calendar::Ptr calendar(createDropCalendar(clipboard->mimeData()));
0329     Incidence::List list;
0330 
0331     if (!calendar) {
0332         qCDebug(KCALUTILS_LOG) << "Can't parse clipboard";
0333         return list;
0334     }
0335 
0336     // All pasted incidences get new uids, must keep track of old uids,
0337     // so we can update child's parents
0338     QHash<QString, Incidence::Ptr> oldUidToNewInc;
0339 
0340     Incidence::List::ConstIterator it;
0341     const Incidence::List incidences = calendar->incidences();
0342     Incidence::List::ConstIterator end(incidences.constEnd());
0343     for (it = incidences.constBegin(); it != end; ++it) {
0344         Incidence::Ptr incidence = d->pasteIncidence(*it, newDateTime, pasteOptions);
0345         if (incidence) {
0346             list.append(incidence);
0347             oldUidToNewInc[(*it)->uid()] = *it;
0348         }
0349     }
0350 
0351     // update relations
0352     end = list.constEnd();
0353     for (it = list.constBegin(); it != end; ++it) {
0354         Incidence::Ptr incidence = *it;
0355         if (oldUidToNewInc.contains(incidence->relatedTo())) {
0356             Incidence::Ptr parentInc = oldUidToNewInc[incidence->relatedTo()];
0357             incidence->setRelatedTo(parentInc->uid());
0358         } else {
0359             // not related to anything in the clipboard
0360             incidence->setRelatedTo(QString());
0361         }
0362     }
0363 
0364     return list;
0365 }
0366 
0367 Incidence::Ptr DndFactory::pasteIncidence(const QDateTime &newDateTime, PasteFlags pasteOptions)
0368 {
0369     QClipboard *clipboard = QApplication::clipboard();
0370     Calendar::Ptr calendar(createDropCalendar(clipboard->mimeData()));
0371 
0372     if (!calendar) {
0373         qCDebug(KCALUTILS_LOG) << "Can't parse clipboard";
0374         return Incidence::Ptr();
0375     }
0376 
0377     Incidence::List incidenceList = calendar->incidences();
0378     Incidence::Ptr incidence = incidenceList.isEmpty() ? Incidence::Ptr() : incidenceList.first();
0379 
0380     return d->pasteIncidence(incidence, newDateTime, pasteOptions);
0381 }