[frameworks/kcalendarcore] /: Add methods for encoding/decoding iCal objects in QMimeData

Volker Krause <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit ff8f452e7779228bbce5e8be2158c02b12b706e6 by Volker Krause.
Committed on 21/07/2026 at 16:04.
Pushed by vkrause into branch 'master'.

Add methods for encoding/decoding iCal objects in QMimeData

This is moving code from kcalutils and calendarsupport that is used in
several PIM repositories for implementing copy/paste and drag&drop of
calendar data.

M  +1    -0    autotests/CMakeLists.txt
A  +55   -0    autotests/mimedatatest.cpp     [License: LGPL(v2.0+)]
M  +2    -0    src/CMakeLists.txt
A  +130  -0    src/mimedata.cpp     [License: LGPL(v2.0+)]
A  +82   -0    src/mimedata.h     [License: LGPL(v2.0+)]

https://invent.kde.org/frameworks/kcalendarcore/-/commit/ff8f452e7779228bbce5e8be2158c02b12b706e6

diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
index 421983bbc..5fe3276e4 100644
--- a/autotests/CMakeLists.txt
+++ b/autotests/CMakeLists.txt
@@ -63,6 +63,7 @@ macro_unit_tests(
   testvcalexport
   testcalendarobserver
   calendarlistmodeltest
+  mimedatatest
 )
 
 set_target_properties(
diff --git a/autotests/mimedatatest.cpp b/autotests/mimedatatest.cpp
new file mode 100644
index 000000000..2813be930
--- /dev/null
+++ b/autotests/mimedatatest.cpp
@@ -0,0 +1,55 @@
+/*
+  SPDX-FileCopyrightText: 2026 Volker Krause <[email protected]>
+  SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include <KCalendarCore/Incidence>
+#include <KCalendarCore/MimeData>
+
+#include <QDebug>
+#include <QMimeData>
+#include <QTest>
+
+using namespace Qt::Literals;
+using namespace KCalendarCore;
+
+class MimeDataTest : public QObject
+{
+    Q_OBJECT
+private Q_SLOTS:
+    static void testMimeData()
+    {
+        const Event::Ptr ev(new Event());
+        ev->setSummary(u"Summary"_s);
+        ev->setDtStart(QDateTime(QDate(2010, 8, 8), {}));
+        ev->setDtEnd(QDateTime(QDate(2010, 8, 9), {}));
+        ev->setAllDay(true);
+        const auto originalUid = ev->uid();
+        QVERIFY(!originalUid.isEmpty());
+
+        Incidence::List incidencesToCopy;
+        incidencesToCopy.append(ev);
+
+        QMimeData mimeData;
+        QVERIFY(!MimeData::canDecode(&mimeData));
+
+        MimeData::populate(&mimeData, incidencesToCopy);
+        QVERIFY(MimeData::canDecode(&mimeData));
+
+        const auto &cal = MimeData::decodeCalendar(&mimeData);
+        QVERIFY(cal);
+        QCOMPARE(cal->incidences().size(), 1);
+        QCOMPARE(cal->events().size(), 1);
+        QVERIFY(!cal->events()[0]->uid().isEmpty());
+        QCOMPARE(cal->events()[0]->uid(), originalUid);
+
+        QVERIFY(MimeData::decodeEvent(&mimeData));
+        QVERIFY(!MimeData::decodeTodo(&mimeData));
+
+        QCOMPARE(MimeData::decodeIncidences(&mimeData).size(), 1);
+    }
+};
+
+QTEST_APPLESS_MAIN(MimeDataTest)
+
+#include "mimedatatest.moc"
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 44afb626d..b50dafae9 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -73,6 +73,7 @@ target_sources(
         journal.h
         memorycalendar.cpp
         memorycalendar.h
+        mimedata.cpp
         occurrenceiterator.cpp
         occurrenceiterator.h
         period.cpp
@@ -172,6 +173,7 @@ set(kcalendarcore_headers
     IncidenceBase
     Journal
     MemoryCalendar
+    MimeData
     OccurrenceIterator
     Period
     Person
diff --git a/src/mimedata.cpp b/src/mimedata.cpp
new file mode 100644
index 000000000..4ffae9729
--- /dev/null
+++ b/src/mimedata.cpp
@@ -0,0 +1,130 @@
+/*
+  SPDX-FileCopyrightText: 2026 Volker Krause <[email protected]>
+  SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include "mimedata.h"
+
+#include "icalformat.h"
+#include "memorycalendar.h"
+#include "vcalformat.h"
+
+#include <QMimeData>
+
+using namespace Qt::Literals;
+using namespace KCalendarCore;
+
+QString MimeData::mimeType()
+{
+    return u"text/calendar"_s;
+}
+
+[[nodiscard]] static QString vcalMimeType()
+{
+    return u"text/x-vCalendar"_s;
+}
+
+QStringList MimeData::supportedMimeTypes()
+{
+    static const QStringList l{mimeType(), vcalMimeType()};
+    return l;
+}
+
+bool MimeData::canDecode(const QMimeData *mimeData)
+{
+    if (!mimeData) {
+        return false;
+    }
+    const auto supportedTypes = supportedMimeTypes();
+    return std::ranges::any_of(supportedTypes, [mimeData](const QString &mt) {
+        return mimeData->hasFormat(mt);
+    });
+}
+
+void MimeData::populate(QMimeData *mimeData, const KCalendarCore::Calendar::Ptr &calendar)
+{
+    Q_ASSERT(mimeData);
+    ICalFormat format;
+    const auto encoded = format.toString(calendar);
+    if (!encoded.isEmpty()) {
+        mimeData->setData(mimeType(), encoded.toUtf8());
+    }
+}
+
+void MimeData::populate(QMimeData *mimeData, const KCalendarCore::Incidence::List &incidences)
+{
+    if (incidences.isEmpty()) {
+        return;
+    }
+
+    const Calendar::Ptr calendar(new MemoryCalendar(QTimeZone::systemTimeZone()));
+    std::ranges::for_each(incidences, [&calendar](const auto &i) {
+        calendar->addIncidence(Incidence::Ptr(i->clone()));
+    });
+    populate(mimeData, calendar);
+}
+
+Calendar::Ptr MimeData::decodeCalendar(const QMimeData *mimeData)
+{
+    if (!mimeData) {
+        return {};
+    }
+
+    Calendar::Ptr calendar(new MemoryCalendar(QTimeZone::systemTimeZone()));
+    if (mimeData->hasFormat(mimeType())) {
+        const QByteArray payload = mimeData->data(mimeType());
+        ICalFormat format;
+        if (!format.fromRawString(calendar, payload)) {
+            return {};
+        }
+        return calendar;
+    }
+
+    if (mimeData->hasFormat(vcalMimeType())) {
+        const QByteArray payload = mimeData->data(mimeType());
+        VCalFormat format;
+        if (!format.fromRawString(calendar, payload)) {
+            return {};
+        }
+        return calendar;
+    }
+
+    return {};
+}
+
+Incidence::List MimeData::decodeIncidences(const QMimeData *mimeData)
+{
+    const auto cal = decodeCalendar(mimeData);
+    if (!cal) {
+        return {};
+    }
+
+    KCalendarCore::Incidence::List result;
+    const KCalendarCore::Incidence::List decodedIncidences = cal->incidences();
+    result.reserve(decodedIncidences.size());
+    for (const KCalendarCore::Incidence::Ptr &i : decodedIncidences) {
+        result.push_back(KCalendarCore::Incidence::Ptr(i->clone()));
+    }
+
+    return result;
+}
+
+Event::Ptr MimeData::decodeEvent(const QMimeData *mimeData)
+{
+    const auto cal = decodeCalendar(mimeData);
+    if (!cal) {
+        return nullptr;
+    }
+    const auto events = cal->events();
+    return events.size() == 1 ? Event::Ptr(events.front()->clone()) : nullptr;
+}
+
+Todo::Ptr MimeData::decodeTodo(const QMimeData *mimeData)
+{
+    const auto cal = decodeCalendar(mimeData);
+    if (!cal) {
+        return nullptr;
+    }
+    const auto todos = cal->todos();
+    return todos.size() == 1 ? Todo::Ptr(todos.front()->clone()) : nullptr;
+}
diff --git a/src/mimedata.h b/src/mimedata.h
new file mode 100644
index 000000000..3f4d05ffb
--- /dev/null
+++ b/src/mimedata.h
@@ -0,0 +1,82 @@
+/*
+  SPDX-FileCopyrightText: 2026 Volker Krause <[email protected]>
+  SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#ifndef KCALENDARCORE_MIMEDATA_H
+#define KCALENDARCORE_MIMEDATA_H
+
+#include "kcalendarcore_export.h"
+
+#include "calendar.h"
+
+#include <QStringList>
+
+class QMimeData;
+
+namespace KCalendarCore
+{
+/*!
+ * \namespace MimeData
+ *
+ * Methods to interface between iCal data and QMimeData.
+ *
+ * \since 6.29
+ */
+namespace MimeData
+{
+
+/*!
+ * Mimetype for generated QMimeData objects with iCal content.
+ */
+[[nodiscard]] KCALENDARCORE_EXPORT QString mimeType();
+
+/*!
+ * Mimetypes for iCal content that can be decoded.
+ * For use in QAbstractItemModel::mimeTypes() for example.
+ */
+[[nodiscard]] KCALENDARCORE_EXPORT QStringList supportedMimeTypes();
+
+/*!
+ * Returns whether \p mimeData contains iCal content we can decode.
+ */
+[[nodiscard]] KCALENDARCORE_EXPORT bool canDecode(const QMimeData *mimeData);
+
+/*!
+ * Populate \p mimeData with the content of \p calendar.
+ */
+KCALENDARCORE_EXPORT void populate(QMimeData *mimeData, const KCalendarCore::Calendar::Ptr &cal);
+
+/*!
+ * Populate \p mimeData with \p incidences.
+ */
+KCALENDARCORE_EXPORT void populate(QMimeData *mimeData, const KCalendarCore::Incidence::List &incidences);
+
+/*!
+ * Create a calendar from \p mimeData.
+ * If \p mimeData cannot be decoded, \c nullptr is returned.
+ */
+[[nodiscard]] KCALENDARCORE_EXPORT Calendar::Ptr decodeCalendar(const QMimeData *mimeData);
+
+/*!
+ * Create a list of incidences from \p mimeData.
+ * If \p mimeData cannot be decoded an empty list is returned.
+ */
+[[nodiscard]] KCALENDARCORE_EXPORT Incidence::List decodeIncidences(const QMimeData *mimeData);
+
+/*!
+ * Create an event from \p mimeData.
+ * If \p mimeData contains iCal data other than exactly one event, \c nullptr is returned.
+ */
+[[nodiscard]] KCALENDARCORE_EXPORT Event::Ptr decodeEvent(const QMimeData *mimeData);
+
+/*!
+ * Create a todo from \p mimeData.
+ * If \p mimeData contains iCal data other than exactly one todo, \c nullptr is returned.
+ */
+[[nodiscard]] KCALENDARCORE_EXPORT Todo::Ptr decodeTodo(const QMimeData *mimeData);
+
+}
+}
+
+#endif
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.