diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b31522f79739fbd3ceeb9c7e0ef2f0f88266b1d3..0334f3bf485c2af01e5fe27703ae3584eb01a9cb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: BSD-2-Clause -add_executable(kalendar about.cpp main.cpp agentconfiguration.cpp eventoccurrencemodel.cpp calendarmanager.cpp multidayeventmodel.cpp eventwrapper.cpp resources.qrc) +add_executable(kalendar about.cpp main.cpp agentconfiguration.cpp eventoccurrencemodel.cpp calendarmanager.cpp multidayeventmodel.cpp eventwrapper.cpp remindersmodel.cpp attendeesmodel.cpp resources.qrc) target_link_libraries(kalendar Qt5::Core diff --git a/src/attendeesmodel.cpp b/src/attendeesmodel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9460692f1cd904e63e4c07ad6162a8178a265836 --- /dev/null +++ b/src/attendeesmodel.cpp @@ -0,0 +1,273 @@ +// SPDX-FileCopyrightText: 2021 Claudio Cambra +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include +#include +#include +#include "attendeesmodel.h" + +AttendeeStatusModel::AttendeeStatusModel(QObject *parent) + : QAbstractListModel(parent) +{ + for(int i = 0; i < QMetaEnum::fromType().keyCount(); i++) { + int value = QMetaEnum::fromType().value(i); + + // QLatin1String is a workaround for QT_NO_CAST_FROM_ASCII. + // Regular expression adds space between every lowercase and Capitalised character then does the same + // for capitalised letters together, e.g. ThisIsATest. Not a problem right now, but best to be safe. + QString enumName = QLatin1String(QMetaEnum::fromType().key(i)); + QString displayName = enumName.replace(QRegularExpression(QLatin1String("Role$")), QLatin1String("")); + displayName.replace(QRegularExpression(QLatin1String("([a-z])([A-Z])")), QLatin1String("\\1 \\2")); + displayName.replace(QRegularExpression(QLatin1String("([A-Z])([A-Z])")), QLatin1String("\\1 \\2")); + displayName.replace(QRegularExpression(QLatin1String("([a-z])([A-Z])")), QLatin1String("\\1 \\2")); + + m_status[value] = i18n(displayName.toStdString().c_str()); + } + +} + +QVariant AttendeeStatusModel::data(const QModelIndex &idx, int role) const +{ + if (!idx.isValid()) { + return {}; + } + + int value = QMetaEnum::fromType().value(idx.row()); + + switch (role) { + case DisplayNameRole: + { + return m_status[value]; + } + case ValueRole: + return value; + default: + qWarning() << "Unknown role for event:" << QMetaEnum::fromType().valueToKey(role); + return {}; + } +} + +QHash AttendeeStatusModel::roleNames() const +{ + return { + { DisplayNameRole, QByteArrayLiteral("display") }, + { ValueRole, QByteArrayLiteral("value") } + }; + +} + +int AttendeeStatusModel::rowCount(const QModelIndex &) const +{ + return m_status.size(); +} + + + + + + + + +AttendeesModel::AttendeesModel(QObject* parent, KCalendarCore::Event::Ptr eventPtr) + : QAbstractListModel(parent) + , m_event(eventPtr) + , m_attendeeStatusModel(parent) +{ + for(int i = 0; i < QMetaEnum::fromType().keyCount(); i++) { + int value = QMetaEnum::fromType().value(i); + QString key = QLatin1String(roleNames()[value]); + m_dataRoles[key] = value; + } +} + +KCalendarCore::Event::Ptr AttendeesModel::eventPtr() +{ + return m_event; +} + +void AttendeesModel::setEventPtr(KCalendarCore::Event::Ptr event) +{ + if (m_event == event) { + return; + } + m_event = event; + Q_EMIT eventPtrChanged(); +} + +KCalendarCore::Attendee::List AttendeesModel::attendees() +{ + return m_event->attendees(); +} + +AttendeeStatusModel * AttendeesModel::attendeeStatusModel() +{ + return &m_attendeeStatusModel; +} + +QVariantMap AttendeesModel::dataroles() +{ + return m_dataRoles; +} + +QVariant AttendeesModel::data(const QModelIndex &idx, int role) const +{ + if (!hasIndex(idx.row(), idx.column())) { + return {}; + } + auto attendee = m_event->attendees()[idx.row()]; + switch (role) { + case CuTypeRole: + return attendee.cuType(); + case DelegateRole: + return attendee.delegate(); + case DelegatorRole: + return attendee.delegator(); + case EmailRole: + return attendee.email(); + case FullNameRole: + return attendee.fullName(); + case IsNullRole: + return attendee.isNull(); + case NameRole: + return attendee.name(); + case RoleRole: + return attendee.role(); + case RSVPRole: + return attendee.RSVP(); + case StatusRole: + return attendee.status(); + case UidRole: + return attendee.uid(); + default: + qWarning() << "Unknown role for event:" << QMetaEnum::fromType().valueToKey(role); + return {}; + } +} + +bool AttendeesModel::setData(const QModelIndex &idx, const QVariant &value, int role) +{ + if (!idx.isValid()) { + return false; + } + + // When modifying attendees, remember you cannot change them directly from m_event->attendees (is a const). + KCalendarCore::Attendee::List currentAttendees(m_event->attendees()); + + switch (role) { + case CuTypeRole: + { + KCalendarCore::Attendee::CuType cuType = static_cast(value.toInt()); + currentAttendees[idx.row()].setCuType(cuType); + break; + } + case DelegateRole: + { + QString delegate = value.toString(); + currentAttendees[idx.row()].setDelegate(delegate); + break; + } + case DelegatorRole: + { + QString delegator = value.toString(); + currentAttendees[idx.row()].setDelegator(delegator); + break; + } + case EmailRole: + { + QString email = value.toString(); + currentAttendees[idx.row()].setEmail(email); + break; + } + case FullNameRole: + { + // Not a writable property + return false; + } + case IsNullRole: + { + // Not an editable value + return false; + } + case NameRole: + { + QString name = value.toString(); + currentAttendees[idx.row()].setName(name); + break; + } + case RoleRole: + { + KCalendarCore::Attendee::Role role = static_cast(value.toInt()); + currentAttendees[idx.row()].setRole(role); + break; + } + case RSVPRole: + { + bool rsvp = value.toBool(); + currentAttendees[idx.row()].setRSVP(rsvp); + break; + } + case StatusRole: + { + KCalendarCore::Attendee::PartStat status = static_cast(value.toInt()); + currentAttendees[idx.row()].setStatus(status); + break; + } + case UidRole: + { + QString uid = value.toString(); + currentAttendees[idx.row()].setUid(uid); + break; + } + default: + qWarning() << "Unknown role for event:" << QMetaEnum::fromType().valueToKey(role); + return false; + } + m_event->setAttendees(currentAttendees); + emit dataChanged(idx, idx); + return true; +} + +QHash AttendeesModel::roleNames() const +{ + return { + { CuTypeRole, QByteArrayLiteral("cuType") }, + { DelegateRole, QByteArrayLiteral("delegate") }, + { DelegatorRole, QByteArrayLiteral("delegator") }, + { EmailRole, QByteArrayLiteral("email") }, + { FullNameRole, QByteArrayLiteral("fullName") }, + { IsNullRole, QByteArrayLiteral("isNull") }, + { NameRole, QByteArrayLiteral("name") }, + { RoleRole, QByteArrayLiteral("role") }, + { RSVPRole, QByteArrayLiteral("rsvp") }, + { StatusRole, QByteArrayLiteral("status") }, + { UidRole, QByteArrayLiteral("uid") } + }; +} + +int AttendeesModel::rowCount(const QModelIndex &) const +{ + return m_event->attendeeCount(); +} + +void AttendeesModel::addAttendee() +{ + // QLatin1String is a workaround for QT_NO_CAST_FROM_ASCII + KCalendarCore::Attendee attendee(QLatin1String(""), QLatin1String("")); + // addAttendee won't actually add any attendees without a set name + m_event->addAttendee(attendee); + Q_EMIT attendeesChanged(); + Q_EMIT layoutChanged(); +} + +void AttendeesModel::deleteAttendee(int row) +{ + if (!hasIndex(row, 0)) { + return; + } + KCalendarCore::Attendee::List currentAttendees(m_event->attendees()); + currentAttendees.removeAt(row); + m_event->setAttendees(currentAttendees); + rowCount(); + Q_EMIT attendeesChanged(); + Q_EMIT layoutChanged(); +} diff --git a/src/attendeesmodel.h b/src/attendeesmodel.h new file mode 100644 index 0000000000000000000000000000000000000000..9568ffc44186d02d194e99958260e433e17ff51d --- /dev/null +++ b/src/attendeesmodel.h @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2021 Claudio Cambra +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include +#include +#include +#include + +/** + * + */ +class AttendeeStatusModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + DisplayNameRole = Qt::UserRole + 1, + ValueRole + }; + Q_ENUM(Roles); + + AttendeeStatusModel(QObject *parent = nullptr); + ~AttendeeStatusModel() = default; + + QVariant data(const QModelIndex &idx, int role) const override; + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = {}) const override; + +private: + QHash m_status; +}; + + + + +class AttendeesModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(KCalendarCore::Event::Ptr eventPtr READ eventPtr WRITE setEventPtr NOTIFY eventPtrChanged) + Q_PROPERTY(KCalendarCore::Attendee::List attendees READ attendees NOTIFY attendeesChanged) + Q_PROPERTY(AttendeeStatusModel * attendeeStatusModel READ attendeeStatusModel NOTIFY attendeeStatusModelChanged) + Q_PROPERTY(QVariantMap dataroles READ dataroles CONSTANT) + +public: + enum Roles { + CuTypeRole = Qt::UserRole + 1, + DelegateRole, + DelegatorRole, + EmailRole, + FullNameRole, + IsNullRole, + NameRole, + RoleRole, + RSVPRole, + StatusRole, + UidRole + }; + Q_ENUM(Roles); + + explicit AttendeesModel(QObject *parent = nullptr, KCalendarCore::Event::Ptr eventPtr = nullptr); + ~AttendeesModel() = default; + + KCalendarCore::Event::Ptr eventPtr(); + void setEventPtr(KCalendarCore::Event::Ptr event); + KCalendarCore::Attendee::List attendees(); + AttendeeStatusModel * attendeeStatusModel(); + QVariantMap dataroles(); + + QVariant data(const QModelIndex &idx, int role) const override; + bool setData(const QModelIndex &idx, const QVariant &value, int role) override; + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = {}) const override; + + Q_INVOKABLE void addAttendee(); + Q_INVOKABLE void deleteAttendee(int row); + +Q_SIGNALS: + void eventPtrChanged(); + void attendeesChanged(); + void attendeeStatusModelChanged(); + +private: + KCalendarCore::Event::Ptr m_event; + AttendeeStatusModel m_attendeeStatusModel; + QVariantMap m_dataRoles; +}; diff --git a/src/contents/ui/EventEditor.qml b/src/contents/ui/EventEditor.qml index 870bec9f49a8934d74c974815a5be37981f062d3..d4c39c18acb563ec7e004b8d1c4f032d1928922d 100644 --- a/src/contents/ui/EventEditor.qml +++ b/src/contents/ui/EventEditor.qml @@ -31,7 +31,7 @@ Kirigami.OverlaySheet { QQC2.Button { text: editMode ? i18n("Done") : i18n("Add") - enabled: titleField.text && eventEditorSheet.validDates && calendarCombo.selectedCollectionId + enabled: titleField.text && eventEditorSheet.validDates && calendarCombo.currentValue QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole } @@ -56,12 +56,7 @@ Kirigami.OverlaySheet { event.eventEnd = endDate; } - // There is also a chance here to add a feature for the user to pick reminder type. - for (let reminderCombo of remindersColumn.reminderCombos) { - event.addAlarm(reminderCombo.beforeEventSeconds) - } - - added(calendarCombo.selectedCollectionId, event); + added(calendarCombo.currentValue, event); } eventEditorSheet.close(); } @@ -90,7 +85,8 @@ Kirigami.OverlaySheet { property int selectedCollectionId: null - displayText: i18n("Please select a calendar...") + textRole: "display" + valueRole: "collectionId" // Should default to default collection // Should also only show *calendars* @@ -99,7 +95,6 @@ Kirigami.OverlaySheet { leftPadding: Kirigami.Units.largeSpacing * kDescendantLevel label: display icon: decoration - onClicked: calendarCombo.displayText = display, calendarCombo.selectedCollectionId = collectionId } popup.z: 1000 } @@ -268,6 +263,7 @@ Kirigami.OverlaySheet { } } } + QQC2.ComboBox { id: repeatComboBox Kirigami.FormData.label: i18n("Repeat:") @@ -289,11 +285,36 @@ Kirigami.OverlaySheet { placeholderText: i18n("Optional") Layout.fillWidth: true } + ColumnLayout { Kirigami.FormData.label: i18n("Reminder:") Layout.fillWidth: true id: remindersColumn + function secondsToReminderLabel(seconds) { // Gives prettified time + + function numAndUnit(secs) { + if(secs >= 2 * 24 * 60 * 60) + return Math.round(secs / (24*60*60)) + " days"; // 2 days + + else if (secs >= 1 * 24 * 60 * 60) + return "1 day"; + else if (secs >= 2 * 60 * 60) + return Math.round(secs / (60*60)) + " hours"; // 2 hours + + else if (secs >= 1 * 60 * 60) + return "1 hour"; + else + return Math.round(secs / 60) + " minutes"; + } + + if (seconds < 0) { + return numAndUnit(seconds * -1) + " before"; + } else if (seconds < 0) { + return numAndUnit(seconds) + " after"; + } else { + return "On event start"; + } + } + property var reminderCombos: [] QQC2.Button { @@ -301,68 +322,56 @@ Kirigami.OverlaySheet { text: i18n("Add reminder") Layout.fillWidth: true - property int buttonIndex: 0 + onClicked: event.remindersModel.addAlarm(); + } - onClicked: { - var newReminder = Qt.createQmlObject(` - import QtQuick 2.15 - import QtQuick.Controls 2.15 as QQC2 - import QtQuick.Layouts 1.15 - import org.kde.kirigami 2.15 as Kirigami + Repeater { + id: remindersRepeater + Layout.fillWidth: true + model: event.remindersModel + // All of the alarms are handled within the delegates. + + delegate: RowLayout { + Layout.fillWidth: true + + Component.onCompleted: console.log(Object.keys(model)) + + QQC2.ComboBox { + // There is also a chance here to add a feature for the user to pick reminder type. + Layout.fillWidth: true + + property var beforeEventSeconds: 0 + + displayText: remindersColumn.secondsToReminderLabel(startOffset) + //textRole: "DisplayNameRole" + onCurrentValueChanged: event.remindersModel.setData(event.remindersModel.index(index, 0), + currentValue, + event.remindersModel.dataroles["startOffset"]) + + model: [0, // We times by -1 to make times be before event + -1 * 5 * 60, // 5 minutes + -1 * 10 * 60, + -1 * 15 * 60, + -1 * 30 * 60, + -1 * 45 * 60, + -1 * 1 * 60 * 60, // 1 hour + -1 * 2 * 60 * 60, + -1 * 1 * 24 * 60 * 60, // 1 day + -1 * 2 * 24 * 60 * 60, + -1 * 5 * 24 * 60 * 60] + // All these times are in seconds. + delegate: Kirigami.BasicListItem { + text: remindersColumn.secondsToReminderLabel(modelData) + } - RowLayout { - Layout.fillWidth: true + popup.z: 1000 + } - QQC2.ComboBox { - id: remindersComboBox${buttonIndex} - Layout.fillWidth: true - - function secondsToReminderLabel(seconds) { - if (seconds) { - var numAndUnit = ( - seconds >= 2 * 24 * 60 * 60 ? Math.round(seconds / (24*60*60)) + " days" : // 2 days + - seconds >= 1 * 24 * 60 * 60 ? "1 day" : - seconds >= 2 * 60 * 60 ? Math.round(seconds / (60*60)) + " hours" : // 2 hours + - seconds >= 1 * 60 * 60 ? "1 hour" : - Math.round(seconds / 60) + " minutes") - return numAndUnit + " before"; - } else { - return "On event start"; - } - } - - property var beforeEventSeconds: 0 - - displayText: secondsToReminderLabel(Number(currentText)) - - model: [0, - 5 * 60, // 5 minutes - 10 * 60, - 15 * 60, - 30 * 60, - 45 * 60, - 1 * 60 * 60, // 1 hour - 2 * 60 * 60, - 1 * 24 * 60 * 60, // 1 day - 2 * 24 * 60 * 60, - 5 * 24 * 60 * 60] - // All these times are in seconds. - delegate: Kirigami.BasicListItem { - label: remindersComboBox${buttonIndex}.secondsToReminderLabel(modelData) - onClicked: remindersComboBox${buttonIndex}.beforeEventSeconds = modelData - } - popup.z: 1000 - } - - QQC2.Button { - icon.name: "edit-delete-remove" - onClicked: parent.destroy() - } - } - `, this.parent, `remindersComboBox${buttonIndex}`) - remindersColumn.reminderCombos.push(newReminder) - buttonIndex += 1 + QQC2.Button { + icon.name: "edit-delete-remove" + onClicked: event.remindersModel.deleteAlarm(model.index); + } } } } @@ -377,29 +386,79 @@ Kirigami.OverlaySheet { text: i18n("Add attendee") Layout.fillWidth: true - property int buttonIndex: 0 + onClicked: event.attendeesModel.addAttendee(); + } + + Repeater { + model: event.attendeesModel + // All of the alarms are handled within the delegates. + + delegate: ColumnLayout { + Layout.leftMargin: Kirigami.Units.largeSpacing + + RowLayout { + QQC2.Label { + Layout.fillWidth: true + text: i18n("Attendee " + String(index + 1)) + } + QQC2.Button { + icon.name: "edit-delete-remove" + onClicked: event.attendeesModel.deleteAttendee(index); + } + } - onClicked: { - var newAttendee = Qt.createQmlObject(` - import QtQuick 2.15 - import QtQuick.Controls 2.15 as QQC2 - import QtQuick.Layouts 1.15 + GridLayout { + Layout.fillWidth: true + columns: 5 - RowLayout { + QQC2.Label{ + text: i18n("Name:") + } + QQC2.TextField { Layout.fillWidth: true + Layout.columnSpan: 4 + onTextChanged: event.attendeesModel.setData(event.attendeesModel.index(index, 0), + text, + event.attendeesModel.dataroles["name"]) + Component.onCompleted: text = model.name + } - QQC2.ComboBox { - id: attendeesComboBox${buttonIndex} - Layout.fillWidth: true - editable: true - } - QQC2.Button { - icon.name: "edit-delete-remove" - onClicked: parent.destroy() - } + QQC2.Label { + text: i18n("Email:") + } + QQC2.TextField { + Layout.fillWidth: true + Layout.columnSpan: 4 + //editText: Email + onTextChanged: event.attendeesModel.setData(event.attendeesModel.index(index, 0), + text, + event.attendeesModel.dataroles["email"]) + Component.onCompleted: text = model.email } - `, this.parent, `attendeesComboBox${buttonIndex}`) - buttonIndex += 1 + QQC2.Label { + text: i18n("Status:") + } + QQC2.ComboBox { + Layout.columnSpan: 2 + model: event.attendeesModel.attendeeStatusModel + textRole: "display" + valueRole: "value" + currentIndex: status // role of parent + onCurrentValueChanged: event.attendeesModel.setData(event.attendeesModel.index(index, 0), + currentValue, + event.attendeesModel.dataroles["status"]) + + popup.z: 1000 + } + QQC2.CheckBox { + Layout.columnSpan: 2 + text: i18n("Request RSVP") + checked: model.rsvp + onCheckedChanged: event.attendeesModel.setData(event.attendeesModel.index(index, 0), + checked, + event.attendeesModel.dataroles["rsvp"]) + } + } } } } diff --git a/src/eventwrapper.cpp b/src/eventwrapper.cpp index d368423e1f640c1a228d65639dbd09679c5f3190..2c41dd496582ce88b4587e45b79912bca278630b 100644 --- a/src/eventwrapper.cpp +++ b/src/eventwrapper.cpp @@ -6,13 +6,12 @@ EventWrapper::EventWrapper(QObject *parent) : QObject(parent) , m_event(new KCalendarCore::Event) + , m_remindersModel(parent, m_event) + , m_attendeesModel(parent, m_event) { - -} - -EventWrapper::~EventWrapper() -{ - + // Change event pointer in remindersmodel if changed here + connect(this, SIGNAL(eventPtrChanged(KCalendarCore::Event::Ptr)), + &m_remindersModel, SLOT(setEventPtr(KCalendarCore::Event::Ptr))); } KCalendarCore::Event::Ptr EventWrapper::eventPtr() const @@ -91,9 +90,14 @@ KCalendarCore::Attendee::List EventWrapper::attendees() const return m_event->attendees(); } -KCalendarCore::Alarm::List EventWrapper::alarms() const +RemindersModel * EventWrapper::remindersModel() { - return m_event->alarms(); + return &m_remindersModel; +} + +AttendeesModel * EventWrapper::attendeesModel() +{ + return &m_attendeesModel; } void EventWrapper::setAllDay(bool allDay) @@ -102,15 +106,10 @@ void EventWrapper::setAllDay(bool allDay) } -void EventWrapper::addAlarm(int startOffset, KCalendarCore::Alarm::Type alarmType) +void EventWrapper::addAlarms(KCalendarCore::Alarm::List alarms) { - KCalendarCore::Alarm::Ptr alarm (new KCalendarCore::Alarm(nullptr)); - // offset can be set in seconds or days, if we want it to be before the event, - // it has to be set to a negative value. - KCalendarCore::Duration offset(startOffset *= -1); - - m_event->addAlarm(alarm); - alarm ->setType(alarmType); - alarm->setStartOffset(offset); + for (int i = 0; i < alarms.size(); i++) { + m_event->addAlarm(alarms[i]); + } } diff --git a/src/eventwrapper.h b/src/eventwrapper.h index 6cf6ee8fbc8615b512666bc4949ba44349efd25a..d685aaf9fb8f75b2f2345e0fdcc60afc81dbc268 100644 --- a/src/eventwrapper.h +++ b/src/eventwrapper.h @@ -7,6 +7,8 @@ #include #include #include +#include "remindersmodel.h" +#include "attendeesmodel.h" /** * This class is a wrapper for a KCalendarCore::Event::Ptr object. @@ -27,11 +29,12 @@ class EventWrapper : public QObject Q_PROPERTY(QDateTime eventEnd READ eventEnd WRITE setEventEnd NOTIFY eventEndChanged) Q_PROPERTY(KCalendarCore::Recurrence * recurrence READ recurrence) Q_PROPERTY(KCalendarCore::Attendee::List attendees READ attendees) - Q_PROPERTY(KCalendarCore::Alarm::List alarms READ alarms) + Q_PROPERTY(RemindersModel * remindersModel READ remindersModel NOTIFY remindersModelChanged) + Q_PROPERTY(AttendeesModel * attendeesModel READ attendeesModel NOTIFY attendeesModelChanged) public: EventWrapper(QObject *parent = nullptr); - ~EventWrapper() override; + ~EventWrapper() = default; KCalendarCore::Event::Ptr eventPtr() const; void setEventPtr(KCalendarCore::Event::Ptr eventPtr); @@ -47,19 +50,24 @@ public: void setEventEnd(QDateTime eventEnd); KCalendarCore::Recurrence * recurrence() const; KCalendarCore::Attendee::List attendees() const; - KCalendarCore::Alarm::List alarms() const; + RemindersModel * remindersModel(); + AttendeesModel * attendeesModel(); Q_INVOKABLE void setAllDay(bool allDay); - Q_INVOKABLE void addAlarm(int startOffset, KCalendarCore::Alarm::Type alarmType = KCalendarCore::Alarm::Type::Display); + Q_INVOKABLE void addAlarms(KCalendarCore::Alarm::List alarms); Q_SIGNALS: - void eventPtrChanged(); + void eventPtrChanged(KCalendarCore::Event::Ptr eventPtr); void summaryChanged(); void descriptionChanged(); void locationChanged(); void eventStartChanged(); void eventEndChanged(); + void remindersModelChanged(); + void attendeesModelChanged(); private: KCalendarCore::Event::Ptr m_event; + RemindersModel m_remindersModel; + AttendeesModel m_attendeesModel; }; diff --git a/src/remindersmodel.cpp b/src/remindersmodel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..40c2005ca526136f7638664fd494558ba0aa8cf8 --- /dev/null +++ b/src/remindersmodel.cpp @@ -0,0 +1,136 @@ +// SPDX-FileCopyrightText: 2021 Claudio Cambra +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include +#include + +RemindersModel::RemindersModel(QObject *parent, KCalendarCore::Event::Ptr eventPtr) + : QAbstractListModel(parent) + , m_event(eventPtr) +{ + for(int i = 0; i < QMetaEnum::fromType().keyCount(); i++) { + int value = QMetaEnum::fromType().value(i); + QString key = QLatin1String(roleNames()[value]); + m_dataRoles[key] = value; + } +} + +KCalendarCore::Event::Ptr RemindersModel::eventPtr() +{ + return m_event; +} + +void RemindersModel::setEventPtr(KCalendarCore::Event::Ptr event) +{ + if (m_event == event) { + return; + } + m_event = event; + Q_EMIT eventPtrChanged(); +} + +KCalendarCore::Alarm::List RemindersModel::alarms() +{ + return m_event->alarms(); +} + +QVariantMap RemindersModel::dataroles() +{ + return m_dataRoles; +} + +QVariant RemindersModel::data(const QModelIndex &idx, int role) const +{ + if (!hasIndex(idx.row(), idx.column())) { + return {}; + } + auto alarm = m_event->alarms()[idx.row()]; + switch (role) { + case TypeRole: + return alarm->type(); + case TimeRole: + return alarm->time(); + case StartOffsetRole: + return alarm->startOffset().asSeconds(); + case EndOffsetRole: + return alarm->endOffset().asSeconds(); + default: + qWarning() << "Unknown role for event:" << QMetaEnum::fromType().valueToKey(role); + return {}; + } +} + +bool RemindersModel::setData(const QModelIndex &idx, const QVariant &value, int role) +{ + if (!idx.isValid()) { + return false; + } + + switch (role) { + case TypeRole: + { + KCalendarCore::Alarm::Type type = static_cast(value.toInt()); + m_event->alarms()[idx.row()]->setType(type); + break; + } + case TimeRole: + { + QDateTime time = value.toDateTime(); + m_event->alarms()[idx.row()]->setTime(time); + break; + } + case StartOffsetRole: + { + // offset can be set in seconds or days, if we want it to be before the event, + // it has to be set to a negative value. + KCalendarCore::Duration offset(value.toInt()); + m_event->alarms()[idx.row()]->setStartOffset(offset); + break; + } + case EndOffsetRole: + { + KCalendarCore::Duration offset(value.toInt()); + m_event->alarms()[idx.row()]->setEndOffset(offset); + break; + } + default: + qWarning() << "Unknown role for event:" << QMetaEnum::fromType().valueToKey(role); + return false; + } + emit dataChanged(idx, idx); + return true; +} + +QHash RemindersModel::roleNames() const +{ + return { + { TypeRole, QByteArrayLiteral("type") }, + { TimeRole, QByteArrayLiteral("time") }, + { StartOffsetRole, QByteArrayLiteral("startOffset") }, + { EndOffsetRole, QByteArrayLiteral("endOffset") } + }; +} + +int RemindersModel::rowCount(const QModelIndex &) const +{ + return m_event->alarms().size(); +} + +void RemindersModel::addAlarm() +{ + KCalendarCore::Alarm::Ptr alarm (new KCalendarCore::Alarm(nullptr)); + m_event->addAlarm(alarm); + Q_EMIT alarmsChanged(); + Q_EMIT layoutChanged(); +} + +void RemindersModel::deleteAlarm(int row) +{ + if (!hasIndex(row, 0)) { + return; + } + + m_event->removeAlarm(m_event->alarms()[row]); + Q_EMIT alarmsChanged(); + Q_EMIT layoutChanged(); +} diff --git a/src/remindersmodel.h b/src/remindersmodel.h new file mode 100644 index 0000000000000000000000000000000000000000..44062ea24b9e337d92126838a32d052af48a4b6e --- /dev/null +++ b/src/remindersmodel.h @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2021 Claudio Cambra +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include +#include +#include +#include + +/** + * This class provides a QAbstractItemModel for an events' reminders/alarms. + * This can be useful for letting users add, modify, or delete events on new or pre-existing events. + * It treats the event's list of alarms as the signle source of truth (and it should be kept this way!) + * + * The data for the model comes from m_event, which is set in the constructor. This is a pointer to the + * event this model is getting the alarm info from. All alarm pointers are then added to m_alarms, which + * is a list. Elements in this model are therefore accessed through row numbers, as the list is a one- + * dimensional data structure. + */ + +class RemindersModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(KCalendarCore::Event::Ptr eventPtr READ eventPtr WRITE setEventPtr NOTIFY eventPtrChanged) + Q_PROPERTY(KCalendarCore::Alarm::List alarms READ alarms NOTIFY alarmsChanged) + Q_PROPERTY(QVariantMap dataroles READ dataroles CONSTANT) + +public: + enum Roles { + TypeRole = Qt::UserRole + 1, + TimeRole, + StartOffsetRole, + EndOffsetRole + }; + Q_ENUM(Roles); + + explicit RemindersModel(QObject *parent = nullptr, KCalendarCore::Event::Ptr eventPtr = nullptr); + ~RemindersModel() = default; + + KCalendarCore::Event::Ptr eventPtr(); + void setEventPtr(KCalendarCore::Event::Ptr event); + KCalendarCore::Alarm::List alarms(); + QVariantMap dataroles(); + + QVariant data(const QModelIndex &idx, int role) const override; + bool setData(const QModelIndex &idx, const QVariant &value, int role) override; + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = {}) const override; + + Q_INVOKABLE void addAlarm(); + Q_INVOKABLE void deleteAlarm(int row); + +Q_SIGNALS: + void eventPtrChanged(); + void alarmsChanged(); + +private: + KCalendarCore::Event::Ptr m_event; + QVariantMap m_dataRoles; +};