[pim/korganizer] src: Move KCalUtils::DndFactory::pasteIncidences here

Allen Winter <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 9a46a6e02685c6313e275eacf5547baab32c4873 by Allen Winter, on behalf of Volker Krause.
Committed on 01/08/2026 at 12:06.
Pushed by winterz into branch 'master'.

Move KCalUtils::DndFactory::pasteIncidences here

This is the only user of it.

M  +1    -0    src/CMakeLists.txt
M  +7    -0    src/autotests/CMakeLists.txt
A  +156  -0    src/autotests/pastehelpertest.cpp     [License: LGPL(v2.0+)]
M  +5    -5    src/calendarview.cpp
A  +147  -0    src/pastehelper.cpp     [License: LGPL(v2.0+)]
A  +44   -0    src/pastehelper.h     [License: LGPL(v2.0+)]

https://invent.kde.org/pim/korganizer/-/commit/9a46a6e02685c6313e275eacf5547baab32c4873

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 5adcf3b64..58e1abfb8 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -379,6 +379,7 @@ set(korganizerprivate_LIB_SRCS
     korganizer_options.h
     collectionsortfilterproxymodel.h
     collectionsortfilterproxymodel.cpp
+    pastehelper.cpp
 )
 
 if(HAVE_ACTIVITY_SUPPORT)
diff --git a/src/autotests/CMakeLists.txt b/src/autotests/CMakeLists.txt
index eb1b6b9ea..998a732cd 100644
--- a/src/autotests/CMakeLists.txt
+++ b/src/autotests/CMakeLists.txt
@@ -69,3 +69,10 @@ ecm_add_test(testtoggletodo.cpp
     korganizer_core
     korganizerprivate
 )
+
+ecm_add_test(pastehelpertest.cpp
+  LINK_LIBRARIES
+    Qt::Test
+    KF6::CalendarCore
+    korganizerprivate
+)
diff --git a/src/autotests/pastehelpertest.cpp b/src/autotests/pastehelpertest.cpp
new file mode 100644
index 000000000..51abbc98a
--- /dev/null
+++ b/src/autotests/pastehelpertest.cpp
@@ -0,0 +1,156 @@
+/*
+  SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
+  SPDX-FileContributor: Sérgio Martins <[email protected]>
+
+  SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include "../pastehelper.cpp"
+
+#include <KCalendarCore/MemoryCalendar>
+#if KCALENDARCORE_VERSION >= QT_VERSION_CHECK(6, 29, 0)
+#include <KCalendarCore/MimeData>
+#endif
+
+#include <QClipboard>
+#include <QGuiApplication>
+#include <QMimeData>
+#include <QTest>
+#include <QTimeZone>
+
+using namespace KCalendarCore;
+
+namespace
+{
+class PasteHelperTest : public QObject
+{
+    Q_OBJECT
+private Q_SLOTS:
+    void testPasteAllDayEvent()
+    {
+#if KCALENDARCORE_VERSION >= QT_VERSION_CHECK(6, 29, 0)
+        const Event::Ptr allDayEvent(new Event());
+        allDayEvent->setSummary(QStringLiteral("Summary 1"));
+        allDayEvent->setDtStart(QDateTime(QDate(2010, 8, 8), {}));
+        allDayEvent->setDtEnd(QDateTime(QDate(2010, 8, 9), {}));
+        allDayEvent->setAllDay(true);
+        const QString originalUid = allDayEvent->uid();
+        const bool originalIsAllDay = allDayEvent->allDay();
+
+        Incidence::List incidencesToPaste;
+        incidencesToPaste.append(allDayEvent);
+
+        auto mimeData = new QMimeData;
+        KCalendarCore::MimeData::populate(mimeData, incidencesToPaste);
+        qGuiApp->clipboard()->setMimeData(mimeData);
+
+        Incidence::List pastedIncidences = PasteHelper::pasteIncidences();
+        QVERIFY(pastedIncidences.size() == 1);
+
+        const Incidence::Ptr &incidence = pastedIncidences.first();
+
+        QVERIFY(incidence->type() == Incidence::TypeEvent);
+
+        // check if a new uid was generated.
+        QVERIFY(incidence->uid() != originalUid);
+
+        // we passed an invalid KDateTime to pasteIncidences() so dates don't change.
+        QVERIFY(incidence->allDay() == originalIsAllDay);
+
+        const Event::Ptr pastedEvent = incidence.staticCast<Event>();
+
+        QCOMPARE(pastedEvent->dtStart(), allDayEvent->dtStart());
+        QCOMPARE(pastedEvent->dtEnd(), allDayEvent->dtEnd());
+        QCOMPARE(pastedEvent->summary(), allDayEvent->summary());
+#endif
+    }
+
+    void testPasteAllDayEvent2()
+    {
+#if KCALENDARCORE_VERSION >= QT_VERSION_CHECK(6, 29, 0)
+        const Event::Ptr allDayEvent(new Event());
+        allDayEvent->setSummary(QStringLiteral("Summary 2"));
+        allDayEvent->setDtStart(QDateTime(QDate(2010, 8, 8), {}));
+        allDayEvent->setDtEnd(QDateTime(QDate(2010, 8, 9), {}));
+        allDayEvent->setAllDay(true);
+        const QString originalUid = allDayEvent->uid();
+
+        Incidence::List incidencesToPaste;
+        incidencesToPaste.append(allDayEvent);
+
+        auto mimeData = new QMimeData;
+        KCalendarCore::MimeData::populate(mimeData, incidencesToPaste);
+        qGuiApp->clipboard()->setMimeData(mimeData);
+
+        const QDateTime newDateTime(QDate(2011, 1, 1).startOfDay());
+        const uint originalLength = allDayEvent->dtStart().secsTo(allDayEvent->dtEnd());
+
+        // paste at the new time
+        Incidence::List pastedIncidences = PasteHelper::pasteIncidences(newDateTime);
+
+        // we only copied one incidence
+        QVERIFY(pastedIncidences.size() == 1);
+
+        const Incidence::Ptr &incidence = pastedIncidences.first();
+
+        QVERIFY(incidence->type() == Incidence::TypeEvent);
+
+        // check if a new uid was generated.
+        QVERIFY(incidence->uid() != originalUid);
+
+        // the new dateTime didn't have time component
+        QVERIFY(incidence->allDay());
+
+        const Event::Ptr pastedEvent = incidence.staticCast<Event>();
+        const uint newLength = pastedEvent->dtStart().secsTo(pastedEvent->dtEnd());
+        /*
+            qDebug() << "originalLength was " << originalLength << "; and newLength is "
+                    << newLength << "; old dtStart was " << allDayEvent->dtStart()
+                    << " and old dtEnd was " << allDayEvent->dtEnd() << endl
+                    << "; new dtStart is " << pastedEvent->dtStart()
+                    << " and new dtEnd is " << pastedEvent->dtEnd();
+        */
+        QCOMPARE(newLength, originalLength);
+        QCOMPARE(newDateTime, pastedEvent->dtStart());
+        QCOMPARE(allDayEvent->summary(), pastedEvent->summary());
+#endif
+    }
+
+    void testPasteTodo()
+    {
+#if KCALENDARCORE_VERSION >= QT_VERSION_CHECK(6, 29, 0)
+        const Todo::Ptr todo(new Todo());
+        todo->setSummary(QStringLiteral("Summary 1"));
+        todo->setDtDue(QDateTime(QDate(2010, 8, 9), {}));
+
+        Incidence::List incidencesToPaste;
+        incidencesToPaste.append(todo);
+
+        auto mimeData = new QMimeData;
+        KCalendarCore::MimeData::populate(mimeData, incidencesToPaste);
+        qGuiApp->clipboard()->setMimeData(mimeData);
+
+        const QDateTime newDateTime(QDate(2011, 1, 1), QTime(10, 10));
+
+        Incidence::List pastedIncidences = PasteHelper::pasteIncidences(newDateTime);
+        QVERIFY(pastedIncidences.size() == 1);
+
+        const Incidence::Ptr &incidence = pastedIncidences.first();
+
+        QVERIFY(incidence->type() == Incidence::TypeTodo);
+
+        // check if a new uid was generated.
+        QVERIFY(incidence->uid() != todo->uid());
+
+        const Todo::Ptr pastedTodo = incidence.staticCast<Todo>();
+
+        QCOMPARE(newDateTime, pastedTodo->dtDue());
+        QCOMPARE(todo->summary(), pastedTodo->summary());
+#endif
+    }
+};
+}
+
+QTEST_MAIN(PasteHelperTest) // clipboard() needs GUI
+
+#include "pastehelpertest.moc"
diff --git a/src/calendarview.cpp b/src/calendarview.cpp
index 35731870c..3dd574236 100644
--- a/src/calendarview.cpp
+++ b/src/calendarview.cpp
@@ -26,6 +26,7 @@
 #include "kodialogmanager.h"
 #include "koglobals.h"
 #include "koviewmanager.h"
+#include "pastehelper.h"
 #include "pimmessagebox.h"
 #include "prefs/koprefs.h"
 #include "views/agendaview/koagendaview.h"
@@ -63,7 +64,6 @@
 #include <KCalendarCore/FileStorage>
 #include <KCalendarCore/ICalFormat>
 
-#include <KCalUtils/DndFactory>
 #include <KCalUtils/Stringify>
 
 #include <TextAddonsWidgets/WhatsNewNgDialog>
@@ -884,7 +884,7 @@ void CalendarView::edit_paste()
     QDateTime endDT;
     QDateTime finalDateTime;
     bool useEndTime = false;
-    KCalUtils::DndFactory::PasteFlags pasteFlags = {};
+    PasteHelper::PasteFlags pasteFlags = {};
 
     const KOrg::BaseView *curView = mViewManager->currentView();
     KOAgendaView *agendaView = mViewManager->agendaView();
@@ -906,13 +906,13 @@ void CalendarView::edit_paste()
         }
     } else if (curView == monthView && monthView->selectionStart().isValid()) {
         finalDateTime = QDateTime(monthView->selectionStart().date().startOfDay());
-        pasteFlags = KCalUtils::DndFactory::FlagPasteAtOriginalTime;
+        pasteFlags = PasteHelper::FlagPasteAtOriginalTime;
     } else if (!mDateNavigator->selectedDates().isEmpty() && curView->supportsDateNavigation()) {
         // default to the selected date from the navigator
         const KCalendarCore::DateList dates = mDateNavigator->selectedDates();
         if (!dates.isEmpty()) {
             finalDateTime = QDateTime(dates.first().startOfDay());
-            pasteFlags = KCalUtils::DndFactory::FlagPasteAtOriginalTime;
+            pasteFlags = PasteHelper::FlagPasteAtOriginalTime;
         }
     }
 
@@ -921,7 +921,7 @@ void CalendarView::edit_paste()
         return;
     }
 
-    KCalendarCore::Incidence::List pastedIncidences = KCalUtils::DndFactory::pasteIncidences(finalDateTime, pasteFlags);
+    KCalendarCore::Incidence::List pastedIncidences = PasteHelper::pasteIncidences(finalDateTime, pasteFlags);
     KCalendarCore::Incidence::List::Iterator it;
 
     for (it = pastedIncidences.begin(); it != pastedIncidences.end(); ++it) {
diff --git a/src/pastehelper.cpp b/src/pastehelper.cpp
new file mode 100644
index 000000000..37c8101f7
--- /dev/null
+++ b/src/pastehelper.cpp
@@ -0,0 +1,147 @@
+/*
+  SPDX-FileCopyrightText: 1998 Preston Brown <[email protected]>
+  SPDX-FileCopyrightText: 2001, 2002 Cornelius Schumacher <[email protected]>
+  SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <[email protected]>
+  SPDX-FileCopyrightText: 2005 Rafal Rzepecki <[email protected]>
+  SPDX-FileCopyrightText: 2008 Thomas Thrainer <[email protected]>
+
+  SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include "pastehelper.h"
+
+#include <KCalendarCore/MemoryCalendar>
+#if KCALENDARCORE_VERSION >= QT_VERSION_CHECK(6, 29, 0)
+#include <KCalendarCore/MimeData>
+#else
+#include <KCalUtils/DndFactory>
+#endif
+
+#include <QClipboard>
+#include <QDate>
+#include <QGuiApplication>
+#include <QMimeData>
+#include <QTimeZone>
+
+using namespace KCalendarCore;
+
+static QDateTime copyTimeSpec(const QDateTime &dt, const QDateTime &source)
+{
+    switch (source.timeSpec()) {
+    case Qt::TimeZone:
+    case Qt::LocalTime:
+    case Qt::UTC:
+        return dt.toTimeZone(source.timeZone());
+    case Qt::OffsetFromUTC:
+        return dt.toOffsetFromUtc(source.offsetFromUtc());
+    }
+
+    Q_UNREACHABLE();
+}
+
+static Incidence::Ptr pasteIncidence(const Incidence::Ptr &incidence, QDateTime newDateTime, PasteHelper::PasteFlags pasteOptions)
+{
+    Incidence::Ptr inc(incidence);
+
+    if (inc) {
+        inc = Incidence::Ptr(inc->clone());
+        inc->recreate();
+    }
+
+    if (inc && newDateTime.isValid()) {
+        if (inc->type() == Incidence::TypeEvent) {
+            Event::Ptr const event = inc.staticCast<Event>();
+            if (pasteOptions & PasteHelper::FlagPasteAtOriginalTime) {
+                // Set date and preserve time and timezone stuff
+                const QDate date = newDateTime.date();
+                newDateTime = event->dtStart();
+                newDateTime.setDate(date);
+            }
+
+            // in seconds
+            const qint64 durationInSeconds = event->dtStart().secsTo(event->dtEnd());
+            const qint64 durationInDays = event->dtStart().daysTo(event->dtEnd());
+
+            if (incidence->allDay()) {
+                event->setDtStart(QDateTime(newDateTime.date(), {}));
+                event->setDtEnd(newDateTime.addDays(durationInDays));
+            } else {
+                event->setDtStart(copyTimeSpec(newDateTime, event->dtStart()));
+                event->setDtEnd(copyTimeSpec(newDateTime.addSecs(durationInSeconds), event->dtEnd()));
+            }
+        } else if (inc->type() == Incidence::TypeTodo) {
+            Todo::Ptr const aTodo = inc.staticCast<Todo>();
+            const bool pasteAtDtStart = (pasteOptions & PasteHelper::FlagTodosPasteAtDtStart);
+            if (pasteOptions & PasteHelper::FlagPasteAtOriginalTime) {
+                // Set date and preserve time and timezone stuff
+                const QDate date = newDateTime.date();
+                newDateTime = pasteAtDtStart ? aTodo->dtStart() : aTodo->dtDue();
+                newDateTime.setDate(date);
+            }
+            if (pasteAtDtStart) {
+                aTodo->setDtStart(copyTimeSpec(newDateTime, aTodo->dtStart()));
+            } else {
+                aTodo->setDtDue(copyTimeSpec(newDateTime, aTodo->dtDue()));
+            }
+        } else if (inc->type() == Incidence::TypeJournal) {
+            if (pasteOptions & PasteHelper::FlagPasteAtOriginalTime) {
+                // Set date and preserve time and timezone stuff
+                const QDate date = newDateTime.date();
+                newDateTime = inc->dtStart();
+                newDateTime.setDate(date);
+            }
+            inc->setDtStart(copyTimeSpec(newDateTime, inc->dtStart()));
+        } else {
+            qWarning() << "Trying to paste unknown incidence of type" << inc->type();
+        }
+    }
+
+    return inc;
+}
+
+Incidence::List PasteHelper::pasteIncidences(const QDateTime &newDateTime, PasteFlags pasteOptions)
+{
+    QClipboard const *clipboard = QGuiApplication::clipboard();
+    Q_ASSERT(clipboard);
+#if KCALENDARCORE_VERSION < QT_VERSION_CHECK(6, 29, 0)
+    Calendar::Ptr const calendar(KCalUtils::DndFactory::createDropCalendar(clipboard->mimeData()));
+#else
+    Calendar::Ptr const calendar(KCalendarCore::MimeData::decodeCalendar(clipboard->mimeData()));
+#endif
+    Incidence::List list;
+
+    if (!calendar) {
+        qWarning() << "Can't parse clipboard";
+        return list;
+    }
+
+    // All pasted incidences get new uids, must keep track of old uids,
+    // so we can update child's parents
+    QHash<QString, Incidence::Ptr> oldUidToNewInc;
+
+    Incidence::List::ConstIterator it;
+    const Incidence::List incidences = calendar->incidences();
+    Incidence::List::ConstIterator end(incidences.constEnd());
+    for (it = incidences.constBegin(); it != end; ++it) {
+        Incidence::Ptr const incidence = pasteIncidence(*it, newDateTime, pasteOptions);
+        if (incidence) {
+            list.append(incidence);
+            oldUidToNewInc[(*it)->uid()] = *it;
+        }
+    }
+
+    // update relations
+    end = list.constEnd();
+    for (it = list.constBegin(); it != end; ++it) {
+        const Incidence::Ptr &incidence = *it;
+        if (oldUidToNewInc.contains(incidence->relatedTo())) {
+            Incidence::Ptr const parentInc = oldUidToNewInc[incidence->relatedTo()];
+            incidence->setRelatedTo(parentInc->uid());
+        } else {
+            // not related to anything in the clipboard
+            incidence->setRelatedTo(QString());
+        }
+    }
+
+    return list;
+}
diff --git a/src/pastehelper.h b/src/pastehelper.h
new file mode 100644
index 000000000..181e281b1
--- /dev/null
+++ b/src/pastehelper.h
@@ -0,0 +1,44 @@
+/*
+  SPDX-FileCopyrightText: 1998 Preston Brown <[email protected]>
+  SPDX-FileCopyrightText: 2001, 2002, 2003 Cornelius Schumacher <[email protected]>
+  SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <[email protected]>
+  SPDX-FileCopyrightText: 2008 Thomas Thrainer <[email protected]>
+
+  SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#pragma once
+
+#include <KCalendarCore/Calendar>
+#include <KCalendarCore/Incidence>
+
+#include <QDateTime>
+
+namespace PasteHelper
+{
+enum PasteFlag {
+    FlagTodosPasteAtDtStart = 1, /*!< If the cloned incidence is a to-do, the date/time passed
+                                    to DndFactory::pasteIncidence() will change dtStart if this
+                                    flag is on, changes dtDue otherwise. */
+    FlagPasteAtOriginalTime = 2 /*!< If set, incidences will be pasted at the specified date
+                                   but will preserve their original time */
+};
+
+Q_DECLARE_FLAGS(PasteFlags, PasteFlag)
+
+/*!
+  This function clones the incidences that are in the clipboard and sets the clone's
+  date/time to the specified \a newDateTime.
+
+  \a newDateTime The new date/time that the incidence will have. If it's an event
+  or journal, DTSTART will be set. If it's a to-do, DTDUE is set.
+  If you wish another behaviour, like changing DTSTART on to-dos, specify
+  \a pasteOptions. If newDateTime is invalid the original incidence's dateTime
+  will be used, regardless of \a pasteOptions.
+
+  \a pasteOptions Control how \a newDateTime changes the incidence's dates. \sa PasteFlag.
+
+  Returns the cloned incidence.
+*/
+KCalendarCore::Incidence::List pasteIncidences(const QDateTime &newDateTime = QDateTime(), PasteFlags pasteOptions = PasteFlags());
+}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.