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 }