diff --git a/CMakeLists.txt b/CMakeLists.txt index 0dbedb311d88467738f30b8762611dfbf804ed3f..c0225518502b25e3b5f2af7b2e8a453c1c741187 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,13 +29,17 @@ include(KDECMakeSettings) include(ECMPoQmTools) include(KDECompilerSettings NO_POLICY_SCOPE) -find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Core Quick Gui Svg Test Qml QuickControls2) + +find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS DBus Core Quick Gui Svg Test Qml QuickControls2) find_package(KF5Config) find_package(KF5Kirigami2) find_package(KF5Plasma) find_package(KF5I18n) find_package(KF5CalendarCore REQUIRED) +find_package(KF5DBusAddons CONFIG REQUIRED) +find_package(KF5Notifications CONFIG REQUIRED) +find_package(KF5Service CONFIG REQUIRED) ################# Enable C++11 features for clang and gcc ################# @@ -49,6 +53,7 @@ find_package(PkgConfig) ######################################################################### add_subdirectory(src) +add_subdirectory(calindac) if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ecm_install_po_files_as_qm(po) diff --git a/calindac/CMakeLists.txt b/calindac/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..cc8dd773fedf164d67619886cf983cd8fce30c09 --- /dev/null +++ b/calindac/CMakeLists.txt @@ -0,0 +1,45 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"calindac\") + +set(calindac_SRCS + calalarmclient.cpp + ) + +set(calindac_SRCS ${calindac_SRCS} calindacmain.cpp) + +set(calindac_SRCS + ${calindac_SRCS} + alarmsmodel.cpp + alarmnotification.cpp + notificationhandler.cpp + ) + +qt5_add_dbus_adaptor(calindac_SRCS org.kde.phone.calindac.xml calalarmclient.h CalAlarmClient) + +add_executable(calindac ${calindac_SRCS} ${RESOURCES}) + +target_link_libraries(calindac + KF5::CalendarCore + KF5::DBusAddons + KF5::Notifications + KF5::Service + KF5::I18n + Qt5::DBus + ) + +install(TARGETS + calindac ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} + ) + +install(FILES + org.kde.phone.calindac.desktop + DESTINATION ${KDE_INSTALL_AUTOSTARTDIR} + ) + + +install(FILES + org.kde.phone.calindac.xml + DESTINATION ${DBUS_INTERFACES_INSTALL_DIR} + ) + +install(FILES calindac.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR}) + diff --git a/calindac/Messages.sh b/calindac/Messages.sh new file mode 100644 index 0000000000000000000000000000000000000000..153bd983dc3ba1f1ab3b3a6b97a4fe85680735fb --- /dev/null +++ b/calindac/Messages.sh @@ -0,0 +1,2 @@ +#! /bin/sh +$XGETTEXT `find . -name "*.cpp" -o -name "*.h" | grep -v '/tests/'` -o $podir/calindac.pot diff --git a/calindac/alarmnotification.cpp b/calindac/alarmnotification.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7d06cfc1fffdd2bc2ee7349fe639faec4c62b07c --- /dev/null +++ b/calindac/alarmnotification.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019 Dimitris Kardarakos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * As a special exception, permission is given to link this program + * with any edition of Qt, and distribute the resulting executable, + * without including the source code for Qt in the source distribution. + */ + +#include "alarmnotification.h" +#include +#include +#include "notificationhandler.h" + +AlarmNotification::AlarmNotification(NotificationHandler* handler, const QString & uid) : mUid(uid), mRemindAt(QDateTime()), mNotificationHandler(handler) +{ + mNotification = new KNotification("alarm"); + mNotification->setActions({i18n("Suspend"),i18n("Dismiss")}); + + connect(mNotification, &KNotification::action1Activated, this, &AlarmNotification::suspend); + connect(mNotification, &KNotification::action2Activated, this, &AlarmNotification::dismiss); + connect(this, &AlarmNotification::suspend, mNotificationHandler, [=](){ mNotificationHandler->suspend(this);}); + connect(this, &AlarmNotification::dismiss, mNotificationHandler, [=](){ mNotificationHandler->dismiss(this);}); +} + +AlarmNotification::~AlarmNotification() +{ + delete mNotification; +} + +void AlarmNotification::send() const +{ + mNotification->sendEvent(); +} + +QString AlarmNotification::uid() const +{ + return mUid; +} + +QString AlarmNotification::text() const +{ + return mNotification->text(); +} + +void AlarmNotification::setText(const QString& alarmText) +{ + mNotification->setText(alarmText); +} + +QDateTime AlarmNotification::remindAt() const +{ + return mRemindAt; +} + +void AlarmNotification::setRemindAt(const QDateTime& remindAtDt) +{ + mRemindAt = remindAtDt; +} + diff --git a/calindac/alarmnotification.h b/calindac/alarmnotification.h new file mode 100644 index 0000000000000000000000000000000000000000..e287fd2ceeeff85e00e264ab126b07a309233fa3 --- /dev/null +++ b/calindac/alarmnotification.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019 Dimitris Kardarakos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * As a special exception, permission is given to link this program + * with any edition of Qt, and distribute the resulting executable, + * without including the source code for Qt in the source distribution. + */ + +#ifndef ALARMNOTIFICATION_H +#define ALARMNOTIFICATION_H + +#include +#include + +class NotificationHandler; + +/** + * @brief The alarm notification that should be displayed. It is a wrapper of a KNotification enhanced with alarm properties, like uid and remind time + * + */ +class AlarmNotification : public QObject +{ + Q_OBJECT +public: + explicit AlarmNotification(NotificationHandler* handler, const QString& uid); + ~AlarmNotification() override; + + /** + * @brief Sends the notification so as to be displayed + */ + void send() const; + /** + * @return The uid of the Incidence of the alarm of the notification + */ + QString uid() const; + /** + * @brief The text of the notification that should be displayed + */ + QString text() const; + /** + * @brief Sets the to-be-displayed text of the notification + */ + void setText(const QString& alarmText); + /** + * @return In case of a suspended notification, the time that the notification should be displayed. Otherwise, it is empty. + */ + QDateTime remindAt() const; + /** + * @brief Sets the time that should be displayed a suspended notification + */ + void setRemindAt(const QDateTime & remindAtDt); + +Q_SIGNALS: + /** + * @brief Signal that should be emitted when the user clicks to the Dismiss action button of the KNotification displayed + * + */ + void dismiss(); + /** + * @brief Signal that should be emitted when the user clicks to the Suspend action button of the KNotification displayed + * + */ + void suspend(); + +private: + KNotification* mNotification; + QString mUid; + QDateTime mRemindAt; + NotificationHandler* mNotificationHandler; +}; +#endif diff --git a/calindac/alarmsmodel.cpp b/calindac/alarmsmodel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e231494eca07d14f78a98004af43fa60fc5b956e --- /dev/null +++ b/calindac/alarmsmodel.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2019 Dimitris Kardarakos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * As a special exception, permission is given to link this program + * with any edition of Qt, and distribute the resulting executable, + * without including the source code for Qt in the source distribution. + */ + +#include "alarmsmodel.h" +#include +#include +#include +#include + +AlarmsModel::AlarmsModel(QObject* parent) : QAbstractListModel(parent), mMemoryCalendars(QVector()), mFileStorages(QVector()), mAlarms(Alarm::List()), mCalendarFiles(QStringList()), mParams(QHash()) +{ + connect(this, &AlarmsModel::paramsChanged, this, &AlarmsModel::loadAlarms); +} + +AlarmsModel::~AlarmsModel() = default; + +QHash AlarmsModel::roleNames() const +{ + QHash roles = QAbstractListModel::roleNames(); + roles.insert(Uid, "uid"); + roles.insert(Text, "text"); + roles.insert(Time, "time"); + roles.insert(IncidenceStartDt, "incidenceStartDt"); + + return roles; +} + +QVariant AlarmsModel::data(const QModelIndex& index, int role) const +{ + if(!index.isValid()) + { + return QVariant(); + } + + switch(role) + { + case Qt::DisplayRole: + return mAlarms.at(index.row())->parentUid(); + case Uid: + return mAlarms.at(index.row())->parentUid(); + case Time: + return mAlarms.at(index.row())->time(); + case Text: + return mAlarms.at(index.row())->text(); + case IncidenceStartDt: + return parentStartDt(index.row()); + } + + return QVariant(); +} + +int AlarmsModel::rowCount(const QModelIndex& parent) const +{ + if(parent.isValid()) + { + return 0; + } + + return mAlarms.count(); +} + +void AlarmsModel::loadAlarms() +{ + qDebug() << "\nloadAlarms"; + + beginResetModel(); + mAlarms.clear(); + openLoadStorages(); + + int cnt = 0; + QVector::const_iterator itr = mMemoryCalendars.constBegin(); + while(itr != mMemoryCalendars.constEnd()) + { + QDateTime from = mPeriod["from"].value(); + QDateTime to = mPeriod["to"].value(); + qDebug() << "loadAlarms:\tLooking for alarms in calendar #" << cnt << ", from" << from.toString("dd.MM.yyyy hh:mm:ss") << "to" << to.toString("dd.MM.yyyy hh:mm:ss"); + + Alarm::List calendarAlarms; + + if(from.isValid() && to.isValid()) + { + calendarAlarms = (*itr)->alarms(from, to, true); + } + else if(!(from.isValid()) && to.isValid()) + { + calendarAlarms = (*itr)->alarmsTo(to); + } + + qDebug() << "loadAlarms:\t" << calendarAlarms.count() << "alarms found in calendar #" << cnt; + if(!(calendarAlarms.empty())) + { + mAlarms.append(calendarAlarms); + } + + ++cnt; + ++itr; + } + + closeStorages(); + endResetModel(); +} + +void AlarmsModel::setCalendars() +{ + mFileStorages.clear(); + mMemoryCalendars.clear(); + + qDebug() << "\nsetCalendars"; + QStringList::const_iterator itr = mCalendarFiles.constBegin(); + while(itr != mCalendarFiles.constEnd()) + { + MemoryCalendar::Ptr calendar(new MemoryCalendar(QTimeZone::systemTimeZoneId())); + FileStorage::Ptr storage(new FileStorage(calendar)); + storage->setFileName(*itr); + if(!(storage->fileName().isNull())) + { + qDebug() << "setCalendars:\t"<< "Appending calendar" << *itr; + mFileStorages.append(storage); + mMemoryCalendars.append(calendar); + } + ++itr; + } +} + + +QHash AlarmsModel::params() const +{ + return mParams; +} + +void AlarmsModel::setParams(const QHash& parameters) +{ + mParams = parameters; + + QStringList calendarFiles = mParams["calendarFiles"].value(); + QVariantMap period = (mParams["period"].value()).value(); + + mCalendarFiles = calendarFiles; + setCalendars(); + mPeriod = period; + + emit paramsChanged(); +} + +void AlarmsModel::openLoadStorages() +{ + QVector::const_iterator itr = mFileStorages.constBegin(); + while(itr != mFileStorages.constEnd()) + { + if((*itr)->open()) + { + qDebug() << "loadAlarms:\t" << (*itr)->fileName() << "opened"; + } + + if((*itr)->load()) + { + qDebug() << "loadAlarms:\t" << (*itr)->fileName() << "loaded"; + } + ++itr; + } +} + +void AlarmsModel::closeStorages() +{ + QVector::const_iterator itr = mFileStorages.constBegin(); + while(itr != mFileStorages.constEnd()) + { + if((*itr)->close()) + { + qDebug() << "loadAlarms:\t" << (*itr)->fileName() << "closed"; + } + ++itr; + } +} + + +QDateTime AlarmsModel::parentStartDt(const int idx) const +{ + Alarm::Ptr alarm = mAlarms.at(idx); + Duration offsetDuration; + QDateTime alarmTime = mAlarms.at(idx)->time(); + if(alarm->hasStartOffset()) + { + offsetDuration = alarm->startOffset(); + } + + if(!(offsetDuration.isNull())) + { + int secondsFromStart = offsetDuration.asSeconds(); + + return alarmTime.addSecs(-1*secondsFromStart); + } + + return alarmTime; +} + diff --git a/calindac/alarmsmodel.h b/calindac/alarmsmodel.h new file mode 100644 index 0000000000000000000000000000000000000000..0e7faecc80eeae2df067a069370e8ea1a3e5ef52 --- /dev/null +++ b/calindac/alarmsmodel.h @@ -0,0 +1,92 @@ +/* + Copyright (c) 2019 Dimitris Kardarakos + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ + +#ifndef ALARMSMODEL_H +#define ALARMSMODEL_H + +#include +#include +#include +#include +#include + +using namespace KCalCore; + +/** + * @brief Model that serves the alarms found in a set of calendar files for a specific time period, as set in the model input parameters + * + */ +class AlarmsModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(QHash params READ params WRITE setParams NOTIFY paramsChanged); +public: + enum Roles + { + Uid = Qt::UserRole+1, + Time, + Text, + IncidenceStartDt + }; + + explicit AlarmsModel(QObject *parent = nullptr); + ~AlarmsModel() override; + + QHash roleNames() const override; + QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex & parent = QModelIndex()) const override; + + /** + * @return A QHash< QString, QVariant > of the input parameters of the model + */ + QHash params() const; + /** + * @brief Sets the input parameters for the model to be populated + * + * @param parameters A QHash< QString, QVariant > that should contain two members: 1) calendarFiles: a QStringList of the calendar files 2) period: a QVariantMap that represents the time period. This QVariantMap expects two QDateTimes (from, to) + */ + void setParams(const QHash & parameters); + + +Q_SIGNALS: + void periodChanged(); + void calendarFilesChanged(); + void uidsChanged(); + void paramsChanged(); + +private: + void loadAlarms(); + void setCalendars(); + void openLoadStorages(); + void closeStorages(); + QDateTime parentStartDt(const int idx) const; + + QVariantMap mPeriod; + QVector mMemoryCalendars; + QVector mFileStorages; + Alarm::List mAlarms; + QStringList mCalendarFiles; + QHash mParams; + +}; +#endif diff --git a/calindac/calalarmclient.cpp b/calindac/calalarmclient.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c85e65995b9c924bfee26684c4d3ca447e11befd --- /dev/null +++ b/calindac/calalarmclient.cpp @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2019 Dimitris Kardarakos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * As a special exception, permission is given to link this program + * with any edition of Qt, and distribute the resulting executable, + * without including the source code for Qt in the source distribution. + */ + +#include "calalarmclient.h" +#include "alarmnotification.h" +#include "alarmsmodel.h" +#include "notificationhandler.h" +#include "calindacadaptor.h" +#include +#include +#include +#include +#include + +using namespace KCalCore; + +CalAlarmClient::CalAlarmClient(QObject* parent) +: QObject(parent), mAlarmsModel(new AlarmsModel()), mNotificationHandler(new NotificationHandler()) +{ + new CalindacAdaptor(this); + QDBusConnection dbus = QDBusConnection::sessionBus(); + dbus.registerObject("/calindac", this ); + + KConfigGroup generalGroup(KSharedConfig::openConfig(), "General"); + mCheckInterval = generalGroup.readEntry("CheckInterval", 15); + mSuspendSeconds = generalGroup.readEntry("SuspendSeconds", 60); + mLastChecked = generalGroup.readEntry("CalendarsLastChecked", QDateTime()); + + qDebug() << "\nCalAlarmClient:\tcheck interval:" << mCheckInterval << "seconds."; + qDebug() << "CalAlarmClient:\tLastChecked:" << mLastChecked; + + restoreSuspendedFromConfig(); + saveCheckInterval(); + saveSuspendSeconds(); + connect(&mCheckTimer, &QTimer::timeout, this, &CalAlarmClient::checkAlarms); + checkAlarms(); + mCheckTimer.start(1000 * mCheckInterval); +} + +CalAlarmClient::~CalAlarmClient() = default; + +QStringList CalAlarmClient::calendarFileList() const +{ + QStringList filesList = QStringList(); + KConfigGroup calindoriCfgGeneral(KSharedConfig::openConfig("calindorirc"), "general"); + QString calendars = calindoriCfgGeneral.readEntry("calendars",QString()); + QStringList calendarList = calendars.split(";"); + + QStringList::const_iterator itr = calendarList.constBegin(); + while(itr != calendarList.constEnd()) + { + QString fileName = KSharedConfig::openConfig("calindorirc")->group(*itr).readEntry("file"); + + if(!(fileName.isNull())) + { + filesList.append(fileName); + } + itr++; + } + + qDebug() << "\ncalendarFileList:\tCalindori calendars:" << filesList.join(","); + + return filesList; +} + +void CalAlarmClient::checkAlarms() +{ + KConfigGroup cfg(KSharedConfig::openConfig(), "General"); + + if (!cfg.readEntry("Enabled", true)) return; + + QDateTime from = mLastChecked.addSecs(1); + mLastChecked = QDateTime::currentDateTime(); + + qDebug() << "\ncheckAlarms:\tCheck:" << from.toString() << " -" << mLastChecked.toString(); + + QVariantMap checkPeriod; + checkPeriod["from"] = from; + checkPeriod["to"] = mLastChecked; + + QHash modelProperties; + modelProperties["calendarFiles"] = calendarFileList(); + modelProperties["period"] = checkPeriod; + mAlarmsModel->setParams(modelProperties); + mNotificationHandler->setPeriod(checkPeriod); + + qDebug() << "checkAlarms:\tModel Alarms:" << mAlarmsModel->rowCount(); + + for(int i=0; irowCount(); ++i) + { + QModelIndex index = mAlarmsModel->index(i, 0, QModelIndex()); + mNotificationHandler->addActiveNotification(mAlarmsModel->data(index, AlarmsModel::Roles::Uid).value(), QString("%1\n%2").arg(mAlarmsModel->data(index, AlarmsModel::Roles::IncidenceStartDt).value().toString("hh:mm"), mAlarmsModel->data(index, AlarmsModel::Roles::Text).value())); + } + + mNotificationHandler->sendNotifications(); + saveLastCheckTime(); + flushSuspendedToConfig(); + + qDebug() << "\ncheckAlarms:\tWaiting for" << mCheckInterval << " seconds"; +} + +void CalAlarmClient::quitCalindac() +{ + flushSuspendedToConfig(); + saveLastCheckTime(); + quit(); +} + +void CalAlarmClient::saveLastCheckTime() +{ + KConfigGroup generalGroup(KSharedConfig::openConfig(), "General"); + generalGroup.writeEntry("CalendarsLastChecked", mLastChecked); + KSharedConfig::openConfig()->sync(); +} + +void CalAlarmClient::saveCheckInterval() +{ + KConfigGroup generalGroup(KSharedConfig::openConfig(), "General"); + generalGroup.writeEntry("CheckInterval", mCheckInterval); + KSharedConfig::openConfig()->sync(); +} + +void CalAlarmClient::saveSuspendSeconds() +{ + KConfigGroup generalGroup(KSharedConfig::openConfig(), "General"); + generalGroup.writeEntry("SuspendSeconds", mSuspendSeconds); + KSharedConfig::openConfig()->sync(); +} + +void CalAlarmClient::quit() +{ + qDebug("\nquit"); + qApp->quit(); +} + +void CalAlarmClient::forceAlarmCheck() +{ + checkAlarms(); + saveLastCheckTime(); +} + +QString CalAlarmClient::dumpLastCheck() const +{ + KConfigGroup cfg(KSharedConfig::openConfig(), "General"); + const QDateTime lastChecked = cfg.readEntry("CalendarsLastChecked", QDateTime()); + + return QStringLiteral("Last Check: %1").arg(lastChecked.toString()); +} + +QStringList CalAlarmClient::dumpAlarms() const +{ + const QDateTime start = QDateTime(QDate::currentDate(), QTime(0, 0), Qt::LocalTime); + const QDateTime end = start.addDays(1).addSecs(-1); + + QVariantMap checkPeriod; + checkPeriod["from"] = start; + checkPeriod["to"] = end; + + AlarmsModel* model = new AlarmsModel(); + + QHash modelProperties; + modelProperties["calendarFiles"] = calendarFileList(); + modelProperties["period"] = checkPeriod; + model->setParams(modelProperties); + + QStringList lst = QStringList(); + + for(int i=0; irowCount(); ++i) + { + QModelIndex index = model->index(i, 0, QModelIndex()); + lst << QStringLiteral("%1: \"%2\"").arg(model->data(index, AlarmsModel::Roles::Time).value(), model->data(index, AlarmsModel::Roles::Uid).value()); + } + + return lst; +} + +void CalAlarmClient::restoreSuspendedFromConfig() +{ + qDebug() << "\nrestoreSuspendedFromConfig:\tRestore suspended alarms from config"; + KConfigGroup suspendedGroup(KSharedConfig::openConfig(), "Suspended"); + QStringList suspendedAlarms = suspendedGroup.groupList(); + + QStringList::const_iterator cfgSuspItr = suspendedAlarms.constBegin(); + while(cfgSuspItr != suspendedAlarms.constEnd()) + { + KConfigGroup suspendedAlarm(&suspendedGroup, *cfgSuspItr); + QString uid = suspendedAlarm.readEntry("UID"); + QString txt = alarmText(uid); + QDateTime remindAt = QDateTime::fromString(suspendedAlarm.readEntry("RemindAt"), "yyyy,M,d,HH,m,s"); + qDebug() << "restoreSuspendedFromConfig:\tRestoring alarm" << uid << "," << txt << "," << remindAt.toString(); + + if(!(uid.isEmpty() && remindAt.isValid() && !(txt.isEmpty()))) + { + mNotificationHandler->addSuspendedNotification(uid, txt, remindAt); + } + ++cfgSuspItr; + } +} + +QString CalAlarmClient::alarmText(const QString& uid) const +{ + QVariantMap checkPeriod; + checkPeriod["to"] = QDateTime::currentDateTime(); + + AlarmsModel* model = new AlarmsModel(); + QHash modelProperties; + modelProperties["calendarFiles"] = calendarFileList(); + modelProperties["period"] = checkPeriod; + model->setParams(modelProperties); + + for(int i=0; i < model->rowCount(); ++i) + { + QModelIndex index = model->index(i, 0, QModelIndex()); + if(model->data(index, AlarmsModel::Roles::Uid).value() == uid) + { + qDebug() << "alarmText: text of" << model->data(index, AlarmsModel::Roles::Uid).value() << " is" << model->data(index, AlarmsModel::Roles::Text).value(); + return model->data(index, AlarmsModel::Roles::Text).value(); + } + } + + return QString(); +} + +void CalAlarmClient::flushSuspendedToConfig() +{ + qDebug("\nflushSuspendedToConfig"); + KConfigGroup suspendedGroup(KSharedConfig::openConfig(), "Suspended"); + suspendedGroup.deleteGroup(); + + QHash suspendedNotifications = mNotificationHandler->suspendedNotifications(); + + if(suspendedNotifications.isEmpty()) + { + qDebug() << "flushSuspendedToConfig:\tNo suspended notification exists, nothing to write to config"; + KSharedConfig::openConfig()->sync(); + + return; + } + + QHash::const_iterator suspItr = suspendedNotifications.constBegin(); + while(suspItr != suspendedNotifications.constEnd()) + { + qDebug() << "flushSuspendedToConfig:\tFlushing suspended alarm" << suspItr.value()->uid() << " to config"; + KConfigGroup notificationGroup(&suspendedGroup, suspItr.value()->uid()); + notificationGroup.writeEntry("UID", suspItr.value()->uid()); + notificationGroup.writeEntry("RemindAt", suspItr.value()->remindAt()); + suspItr++; + } + KSharedConfig::openConfig()->sync(); +} diff --git a/calindac/calalarmclient.h b/calindac/calalarmclient.h new file mode 100644 index 0000000000000000000000000000000000000000..3002115100ba732d919778f20d649b902bbae809 --- /dev/null +++ b/calindac/calalarmclient.h @@ -0,0 +1,88 @@ +/* + This program used korgac as a starting point. korgac can be found here: https://cgit.kde.org/korganizer.git/tree/korgac. It has been created by Cornelius Schumacher. + + Copyright (c) 2019 Dimitris Kardarakos + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ +#ifndef CALALARMCLIENT_H +#define CALALARMCLIENT_H + +#include +#include + +class AlarmsModel; +class NotificationHandler; +/** + * @brief Client that orchestrates the parsing of calendars and the display of notifications for event alarms. It exposes a D-Bus Interface containing a set of callable methods. + * + */ +class CalAlarmClient : public QObject +{ + Q_OBJECT + Q_CLASSINFO( "D-Bus Interface", "org.kde.phone.calindac" ) + +public: + explicit CalAlarmClient(QObject* parent = nullptr); + ~CalAlarmClient() override; + +public Q_SLOTS: + // DBUS interface + /** + * @brief Quits the application. It is included in the DBUS interface methods. + * + */ + void quit(); + + /** + * @brief Checks the calendars for event alarms. It is included in the DBUS interface methods. + * + */ + void forceAlarmCheck(); + + /** + * @return The date time of the last check done for event alarms. It is included in the DBUS interface methods. + */ + QString dumpLastCheck() const; + + /** + * @return The list of today's event alarms + */ + QStringList dumpAlarms() const; + +private: + void quitCalindac(); + QString alarmText(const QString& uid) const; + void checkAlarms(); + void saveLastCheckTime(); + void saveCheckInterval(); + void saveSuspendSeconds(); + void restoreSuspendedFromConfig(); + void flushSuspendedToConfig(); + QStringList calendarFileList() const; + + QStringList mCalendarFiles; + AlarmsModel* mAlarmsModel; + QDateTime mLastChecked; + QTimer mCheckTimer; + NotificationHandler* mNotificationHandler; + int mCheckInterval; + int mSuspendSeconds; +}; +#endif diff --git a/calindac/calindac.notifyrc b/calindac/calindac.notifyrc new file mode 100644 index 0000000000000000000000000000000000000000..69714ff1cc5caf25d73441552ba49bb40d4b0991 --- /dev/null +++ b/calindac/calindac.notifyrc @@ -0,0 +1,17 @@ +[Global] +IconName=kalarm +Comment=Plasma Mobile Calendar +Name=Calindori + +[Context/uid] +Name=Incidence uid +Comment=The uid of the incidence of the alarm + +[Event/alarm] +Name=Alarm +Contexts=uid +Comment=Alarm Notification +Action=Popup +Urgency=Critical + + diff --git a/calindac/calindacmain.cpp b/calindac/calindacmain.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a7da0a0ce652046f17380b65ee74f7670ade297e --- /dev/null +++ b/calindac/calindacmain.cpp @@ -0,0 +1,55 @@ +/* + Copyright (c) 2019 Dimitris Kardarakos + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ + +#include "calalarmclient.h" +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication app(argc, argv); + app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); + + KAboutData aboutData(QStringLiteral("calindac"), i18n("Calindori Alarm Check Daemon"), + QStringLiteral(), i18n("Calindori Alarm Check Daemon"), + KAboutLicense::GPL, + i18n("(c) 2019 Dimitris Kardarakos"), + QString(), QStringLiteral("https://invent.kde.org/kde/calindori")); + aboutData.addAuthor(i18n("Dimitris Kardarakos"), i18n("Maintainer"), + QStringLiteral("dimkard@posteo.net")); + + QCommandLineParser parser; + KAboutData::setApplicationData(aboutData); + aboutData.setupCommandLine(&parser); + parser.process(app); + aboutData.processCommandLine(&parser); + + KDBusService service(KDBusService::Unique); + + CalAlarmClient client; + + return app.exec(); +} diff --git a/calindac/notificationhandler.cpp b/calindac/notificationhandler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2d91df1d3b56ca0f4bc6db5cfe002d41aec07b19 --- /dev/null +++ b/calindac/notificationhandler.cpp @@ -0,0 +1,132 @@ +/* + Copyright (c) 2019 Dimitris Kardarakos + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ + +#include "notificationhandler.h" +#include "alarmnotification.h" +#include +#include +#include +#include + +NotificationHandler::NotificationHandler() : mActiveNotifications(QHash()), mSuspendedNotifications(QHash()) +{ + KConfigGroup generalGroup(KSharedConfig::openConfig(), "General"); + mSuspendSeconds = generalGroup.readEntry("SuspendSeconds", 60); +} + +NotificationHandler::~NotificationHandler() = default; + +void NotificationHandler::addActiveNotification(const QString& uid, const QString& text) +{ + AlarmNotification* notification = new AlarmNotification(this, uid); + notification->setText(text); + mActiveNotifications[notification->uid()] = notification; +} + +void NotificationHandler::addSuspendedNotification(const QString& uid, const QString& txt, const QDateTime& remindTime) +{ + qDebug() << "addSuspendedNotification:\tAdding notification to suspended list, uid:" << uid << "text:" << txt << "remindTime:" << remindTime; + AlarmNotification* notification = new AlarmNotification(this, uid); + notification->setText(txt); + notification->setRemindAt(remindTime); + mSuspendedNotifications[notification->uid()] = notification; +} + +void NotificationHandler::sendSuspendedNotifications() +{ + QHash::iterator suspItr = mSuspendedNotifications.begin(); + while(suspItr != mSuspendedNotifications.end()) + { + if(suspItr.value()->remindAt() < mPeriod["to"].value()) + { + qDebug() << "sendNotifications:\tSending notification for suspended alarm" << suspItr.value()->uid() << ", text is" << suspItr.value()->text(); + + suspItr.value()->send(); + suspItr = mSuspendedNotifications.erase(suspItr); + } + else + { + suspItr++; + } + } +} + +void NotificationHandler::sendActiveNotifications() +{ + QHash::const_iterator activeItr = mActiveNotifications.constBegin(); + while(activeItr != mActiveNotifications.constEnd()) + { + qDebug() << "sendNotifications:\tSending notification for alarm" << activeItr.value()->uid(); + + activeItr.value()->send(); + activeItr++; + } +} + +void NotificationHandler::sendNotifications() +{ + qDebug() << "\nsendNotifications:\tLooking for notifications, total Active:" << mActiveNotifications.count() << ", total Suspended:" << mSuspendedNotifications.count(); + + sendSuspendedNotifications(); + sendActiveNotifications(); +} + +void NotificationHandler::dismiss(AlarmNotification* const notification) +{ + mActiveNotifications.remove(notification->uid()); + + qDebug() << "\ndismiss:\tAlarm" << notification->uid() << "dismissed"; +} + +void NotificationHandler::suspend(AlarmNotification* const notification) +{ + AlarmNotification* suspendedNotification = new AlarmNotification(this, notification->uid()); + suspendedNotification->setText(notification->text()); + suspendedNotification->setRemindAt(QDateTime(QDateTime::currentDateTime()).addSecs(mSuspendSeconds)); + + mSuspendedNotifications[notification->uid()] = suspendedNotification; + mActiveNotifications.remove(notification->uid()); + + qDebug() << "\nsuspend\t:Alarm " << notification->uid() << "suspended"; +} + +QVariantMap NotificationHandler::period() const +{ + return mPeriod; +} + +void NotificationHandler::setPeriod(const QVariantMap & checkPeriod) +{ + mPeriod = checkPeriod; +} + +QHash NotificationHandler::activeNotifications() const +{ + return mActiveNotifications; +} + +QHash NotificationHandler::suspendedNotifications() const +{ + return mSuspendedNotifications; +} + + diff --git a/calindac/notificationhandler.h b/calindac/notificationhandler.h new file mode 100644 index 0000000000000000000000000000000000000000..a7610f6952945c69ca4c2df83e8400b8e0ce0dbb --- /dev/null +++ b/calindac/notificationhandler.h @@ -0,0 +1,91 @@ +/* + Copyright (c) 2019 Dimitris Kardarakos + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ + +#ifndef NOTIFICATIONHANDLER_H +#define NOTIFICATIONHANDLER_H + +#include + +class AlarmNotification; + +/** + * @brief Manages the creation and triggering of event alarm notifications + * + */ +class NotificationHandler : public QObject +{ + Q_OBJECT +public: + explicit NotificationHandler(); + ~NotificationHandler() override; + + /** + * @brief Parses the internal list of active and suspended notifications and triggers their sending + * + */ + void sendNotifications(); + /** + * @brief Creates an alarm notification object for the Incidence with \p uid. It sets the text to be displayed according to \p text. It adds this alarm notification to the internal list of active notifications (the list of notifications that should be sent at the next check). + */ + void addActiveNotification(const QString& uid, const QString& text); + /** + * @brief Creates an alarm notification object for the Incidence with \p uid. It sets the text to be displayed according to \p text. It adds this alarm notification to the internal list of suspended notifications. + * + */ + void addSuspendedNotification(const QString& uid, const QString& text, const QDateTime& remindTime); + /** + * @brief Sets the time period to check for alarms. \p checkPeriod should contain two QDateTime members: from, to + * + */ + void setPeriod(const QVariantMap& checkPeriod); + /** + * @return The list of active notifications. It is the set of notification that should be sent at the next check + */ + QHash activeNotifications() const; + /** + * @return The list of suspended notifications + */ + QHash suspendedNotifications() const; + /** + * @return The time period to check for alarms + */ + QVariantMap period() const; +public Q_SLOTS: + /** + * @brief Dismisses any further notification display for the alarm \p notification. + * + */ + void dismiss(AlarmNotification* const notification); + /** + * @brief Suspends the display of the alarm \p notification, by removing it from the list of active and putting it to the list of suspended notifications. Remind time is set according to configuration. + */ + void suspend(AlarmNotification* const notification); +private: + void sendActiveNotifications(); + void sendSuspendedNotifications(); + + QHash mActiveNotifications; + QHash mSuspendedNotifications; + QVariantMap mPeriod; + int mSuspendSeconds; +}; +#endif diff --git a/calindac/org.kde.phone.calindac.desktop b/calindac/org.kde.phone.calindac.desktop new file mode 100644 index 0000000000000000000000000000000000000000..948a3cc7632332a4f92a3568355be0ea35a4258f --- /dev/null +++ b/calindac/org.kde.phone.calindac.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=Calindori Reminder Client +Exec=calindac +Icon=calindori +Type=Application +Categories=Qt;KDE; +GenericName=Calindori Reminder Daemon Client +Terminal=false +X-KDE-autostart-phase=2 +X-KDE-autostart-condition=calindacrc:General:Autostart:true +X-DBUS-StartupType=Unique diff --git a/calindac/org.kde.phone.calindac.xml b/calindac/org.kde.phone.calindac.xml new file mode 100644 index 0000000000000000000000000000000000000000..46538930038692cf8cf5bbcb0a672980f157f8d7 --- /dev/null +++ b/calindac/org.kde.phone.calindac.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/contents/ui/EventEditor.qml b/src/contents/ui/EventEditor.qml index fa575aa4e3a7cf41ce55b6c00a751cfe7cf95c00..b5180a88da7f7ff0ad11e034b90bdbb69eacff67 100644 --- a/src/contents/ui/EventEditor.qml +++ b/src/contents/ui/EventEditor.qml @@ -235,6 +235,49 @@ Kirigami.Page { text: eventData ? eventData.description : "" placeholderText: i18n("Description") } + + RowLayout { + Controls2.Label { + id: remindersLabel + + Layout.fillWidth: true + text: i18n("Reminders") + } + + Controls2.ToolButton { + text: i18n("Add") + + onClicked: reminderEditor.open() + } + } + + Kirigami.Separator { + Layout.fillWidth: true + } + + Repeater { + id: alarmsList + + model: incidenceAlarmsModel + + delegate: Kirigami.SwipeListItem { + contentItem: Controls2.Label { + text: model.display + } + + Layout.fillWidth: true + + actions: [ + Kirigami.Action { + id: deleteAlarm + + iconName: "delete" + onTriggered: incidenceAlarmsModel.removeAlarm(model.index) + } + ] + } + } + } actions { @@ -269,7 +312,7 @@ Kirigami.Page { if(validation.success) { console.log("Saving event, root.startdt:" + startdt); var controller = eventController.createObject(parent, {calendar: root.calendar}); - controller.vevent = { "uid" : root.uid, "startDate": root.startdt, "summary": root.summary, "description": root.description, "startHour": root.startHour + (root.startPm ? 12 : 0), "startMinute": root.startMinute, "allDay": root.allDay, "location": root.location, "endDate": (root.allDay ? root.startdt : root.enddt), "endHour": root.endHour + (root.endPm ? 12 : 0), "endMinute": root.endMinute }; + controller.vevent = { "uid" : root.uid, "startDate": root.startdt, "summary": root.summary, "description": root.description, "startHour": root.startHour + (root.startPm ? 12 : 0), "startMinute": root.startMinute, "allDay": root.allDay, "location": root.location, "endDate": (root.allDay ? root.startdt : root.enddt), "endHour": root.endHour + (root.endPm ? 12 : 0), "endMinute": root.endMinute, "alarms": incidenceAlarmsModel.alarms() }; controller.addEdit(); editcompleted(); } @@ -298,4 +341,16 @@ Kirigami.Page { Calindori.EventController { } } + + Calindori.IncidenceAlarmsModel { + + id: incidenceAlarmsModel + alarmProperties: { "calendar" : root.calendar, "uid": root.uid } + } + + ReminderEditor { + id: reminderEditor + + onOffsetSelected: incidenceAlarmsModel.addAlarm(offset) + } } diff --git a/src/contents/ui/ReminderEditor.qml b/src/contents/ui/ReminderEditor.qml new file mode 100644 index 0000000000000000000000000000000000000000..ceec10a52971efd141b373ebaa5317d18ab867f4 --- /dev/null +++ b/src/contents/ui/ReminderEditor.qml @@ -0,0 +1,113 @@ +/* + * Copyright 2019 Dimitris Kardarakos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.0 +import QtQuick.Controls 2.4 as Controls2 +import QtQuick.Layouts 1.11 +import org.kde.kirigami 2.4 as Kirigami +import org.kde.phone.calindori 0.1 as Calindori + +Kirigami.OverlaySheet { + id: reminderEditorSheet + + property alias secondsOffset: seconds.value + property alias minutesOffset: minutes.value + property alias hoursOffset: hours.value + property alias daysOffset: days.value + + property int offset: seconds.value + minutes.value*60 + hours.value*3600 + days.value*86400 + + signal offsetSelected + + rightPadding: 0 + leftPadding: 0 + + contentItem: ColumnLayout { + Kirigami.Heading { + level:2 + text: i18n("Remind time from start") + Layout.alignment : Qt.AlignHCenter + } + + Kirigami.FormLayout { + id: alarmOffsetPicker + + Controls2.SpinBox { + id: seconds + + from: 0 + to: 60 + value: 0 + + Kirigami.FormData.label: i18n("Seconds:") + } + Controls2.SpinBox { + id: minutes + + from: 0 + to: 60 + value: 0 + + Kirigami.FormData.label: i18n("Minutes:") + } + + Controls2.SpinBox { + id: hours + + from: 0 + to: 24 + value: 0 + + Kirigami.FormData.label: i18n("Hours:") + } + + Controls2.SpinBox { + id: days + + from: 0 + value: 0 + + Kirigami.FormData.label: i18n("Days:") + } + } + } + + + footer: RowLayout { + + Item { + Layout.fillWidth: true + } + + Controls2.ToolButton { + text: i18n("OK") + onClicked: { + reminderEditorSheet.offsetSelected(); + reminderEditorSheet.close(); + } + } + + Controls2.ToolButton { + text: i18n("Cancel") + onClicked: { + reminderEditorSheet.close(); + } + } + } +} diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 7d309ded0f1e4178e06df902e822f03c9fb46fb4..fecac87c1f56d1e0aa9cea09bf42477158c032f7 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -6,6 +6,7 @@ set(qmlplugin_SRCS eventmodel.cpp eventcontroller.cpp todocontroller.cpp + incidencealarmsmodel.cpp ) add_library (calindoriqmlplugin SHARED ${qmlplugin_SRCS}) diff --git a/src/plugins/calindoriconfig.cpp b/src/plugins/calindoriconfig.cpp index 59b61faf251232d3f1593df87a7cb9d2724b57f4..34b27ace0fdf825c24fb613f8d43b30366d53176 100644 --- a/src/plugins/calindoriconfig.cpp +++ b/src/plugins/calindoriconfig.cpp @@ -118,6 +118,8 @@ QVariantMap CalindoriConfig::addCalendar(const QString & calendar) if(d->config.group("general").readEntry("calendars", QString()).isEmpty()) { d->config.group("general").writeEntry("calendars", calendar); + d->config.sync(); + return result; } @@ -133,13 +135,34 @@ QVariantMap CalindoriConfig::addCalendar(const QString & calendar) void CalindoriConfig::removeCalendar(const QString& calendar) { + d->config.reparseConfiguration(); QStringList calendarsList = d->config.group("general").readEntry("calendars", QString()).split(";"); if(calendarsList.contains(calendar)) { qDebug() << "Removing calendar " << calendar; calendarsList.removeAll(calendar); + + d->config.deleteGroup(calendar); d->config.group("general").writeEntry("calendars", calendarsList.join(";")); d->config.sync(); + emit calendarsChanged(); } } + +QString CalindoriConfig::calendarFile(const QString& calendarName) +{ + if(d->config.hasGroup(calendarName) && d->config.group(calendarName).hasKey("file")) + { + return d->config.group(calendarName).readEntry("file"); + } + d->config.group(calendarName).writeEntry("file", filenameToPath(calendarName)); + d->config.sync(); + + return filenameToPath(calendarName); +} + +QString CalindoriConfig::filenameToPath(const QString& calendarName) +{ + return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/calindori_" + calendarName + ".ics"; +} diff --git a/src/plugins/calindoriconfig.h b/src/plugins/calindoriconfig.h index 904e745cdc885e91880268fd3c13a640b740c43c..0400de92fc1f6bcbe17afee46904e105795a9802 100644 --- a/src/plugins/calindoriconfig.h +++ b/src/plugins/calindoriconfig.h @@ -33,6 +33,7 @@ public: ~CalindoriConfig() override; QString calendars() const; + QString calendarFile(const QString & calendarName); Q_SIGNAL void calendarsChanged(); QString activeCalendar() const; @@ -45,6 +46,8 @@ public Q_SLOTS: void removeCalendar(const QString& calendar); private: + static QString filenameToPath(const QString & calendarName) ; + class Private; Private* d; }; diff --git a/src/plugins/eventcontroller.cpp b/src/plugins/eventcontroller.cpp index 406c8c606c01791ce5e6bf4f4dc47e29e8f1ab86..b3be9e3896e156e3fc722a795624e097b9bb9845 100644 --- a/src/plugins/eventcontroller.cpp +++ b/src/plugins/eventcontroller.cpp @@ -71,7 +71,7 @@ void EventController::remove() void EventController::addEdit() { - qDebug() << "Creating event"; + qDebug() << "\naddEdit:\tCreating event"; auto localcalendar = qobject_cast(m_calendar); MemoryCalendar::Ptr memoryCalendar = localcalendar->memorycalendar(); @@ -117,11 +117,30 @@ void EventController::addEdit() event->setAllDay(allDayFlg); event->setLocation(m_event["location"].value()); + event->clearAlarms(); + QVariantList newAlarms = m_event["alarms"].value(); + QVariantList::const_iterator itr = newAlarms.constBegin(); + while(itr != newAlarms.constEnd()) + { + Alarm::Ptr newAlarm = event->newAlarm(); + QHash newAlarmHashMap = (*itr).value>(); + int startOffsetValue = newAlarmHashMap["startOffsetValue"].value(); + int startOffsetType = newAlarmHashMap["startOffsetType"].value(); + int actionType = newAlarmHashMap["actionType"].value(); + + qDebug() << "addEdit:\tAdding alarm with start offset value " << startOffsetValue; + newAlarm->setStartOffset(Duration(startOffsetValue, static_cast(startOffsetType))); + newAlarm->setType(static_cast(actionType)); + newAlarm->setEnabled(true); + newAlarm->setText((event->summary()).isEmpty() ? event->description() : event->summary()); + ++itr; + } + memoryCalendar->addEvent(event); bool merged = localcalendar->save(); - - qDebug() << "Event added/updated: " << merged; + + qDebug() << "addEdit:\tEvent added/updated: " << merged; emit veventChanged(); emit veventsUpdated(); diff --git a/src/plugins/incidencealarmsmodel.cpp b/src/plugins/incidencealarmsmodel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8736ffa6954479cab4bc584c33a0c44320b3076c --- /dev/null +++ b/src/plugins/incidencealarmsmodel.cpp @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2019 Dimitris Kardarakos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * As a special exception, permission is given to link this program + * with any edition of Qt, and distribute the resulting executable, + * without including the source code for Qt in the source distribution. + */ + +#include "incidencealarmsmodel.h" +#include "localcalendar.h" +#include +#include +#include + +IncidenceAlarmsModel::IncidenceAlarmsModel(QObject* parent) : QAbstractListModel(parent), mAlarms(QVariantList()) +{ + connect(this, &IncidenceAlarmsModel::alarmPropertiesChanged, this, &IncidenceAlarmsModel::loadPersistentAlarms); +} + +IncidenceAlarmsModel::~IncidenceAlarmsModel() = default; + +QHash IncidenceAlarmsModel::roleNames() const +{ + QHash roles = QAbstractItemModel::roleNames(); + roles.insert(StartOffsetType, "startOffsetType"); + roles.insert(StartOffsetValue, "startOffsetValue"); + roles.insert(ActionType, "actionType"); + return roles; +} + +void IncidenceAlarmsModel::removeAlarm(const int row) +{ + beginRemoveRows(QModelIndex(), row, row); + + qDebug() << "\nremoveRow:\tRemoving index: " << row; + mAlarms.removeAt(row); + + endRemoveRows(); +} + +void IncidenceAlarmsModel::addAlarm(const int secondsFromStart) +{ + qDebug() << "\nAddAlarm:\tAdding alarm. Seconds from start: " << secondsFromStart; + + beginInsertRows(QModelIndex(), mAlarms.count(), mAlarms.count()); + + QHash alarmMap; + if(secondsFromStart%86400 == 0) + { + alarmMap["startOffsetValue"] = -1*secondsFromStart/86400; + alarmMap["startOffsetType"] = Duration::Days; + } + else + { + alarmMap["startOffsetValue"] = -1*secondsFromStart; + alarmMap["startOffsetType"] = Duration::Seconds; + } + alarmMap["actionType"] = Alarm::Type::Display; + mAlarms.append(alarmMap); + + endInsertRows(); +} + +QVariantMap IncidenceAlarmsModel::alarmProperties() const +{ + return mAlarmProperties; +} + +void IncidenceAlarmsModel::setAlarmProperties(const QVariantMap& alarmProps) +{ + mAlarmProperties = alarmProps; + + emit alarmPropertiesChanged(); +} + +QVariant IncidenceAlarmsModel::data(const QModelIndex& index, int role) const +{ + if(!index.isValid()) + { + return QVariant(); + } + + switch(role) + { + case Qt::DisplayRole: + return displayText(index.row()); + case StartOffsetType: + return alarmStartOffsetType(index.row()); + case StartOffsetValue: + return alarmStartOffsetValue(index.row()); + case ActionType: + return alarmActionType(index.row()); + } + + return QVariant(); +} + +int IncidenceAlarmsModel::rowCount(const QModelIndex& parent) const +{ + if(parent.isValid()) + { + return 0; + } + + return mAlarms.count(); +} + +void IncidenceAlarmsModel::loadPersistentAlarms() +{ + + beginResetModel(); + + LocalCalendar* localCalendar = mAlarmProperties["calendar"].value(); + QString uid = mAlarmProperties["uid"].value(); + MemoryCalendar::Ptr memCalendar; + Incidence::Ptr alarmIncidence; + Alarm::List persistentAlarms = Alarm::List(); + + qDebug() << "\nloadPersistentAlarms: uid" << uid; + + if(localCalendar != nullptr) + { + memCalendar = localCalendar->memorycalendar(); + alarmIncidence = memCalendar->incidence(uid); + } + + if(alarmIncidence != nullptr) + { + persistentAlarms = alarmIncidence->alarms(); + } + + Alarm::List::const_iterator alarmItr = persistentAlarms.constBegin(); + + while(alarmItr != persistentAlarms.constEnd()) + { + QHash alarmMap; + alarmMap["startOffsetValue"] = (*alarmItr)->startOffset().value(); + alarmMap["startOffsetType"] = (*alarmItr)->startOffset().type(); + alarmMap["actionType"] = (*alarmItr)->type(); + + mAlarms.append(alarmMap); + ++alarmItr; + } + endResetModel(); +} + +QString IncidenceAlarmsModel::alarmText(const int idx) const +{ + QHash alarm = mAlarms.at(idx).value>(); + + return alarm["text"].value(); +} + +QString IncidenceAlarmsModel::alarmStartOffsetType(const int idx) const +{ + QHash alarm = mAlarms.at(idx).value>(); + + int durationType = alarm["startOffsetType"].value(); + + switch(durationType) + { + case Duration::Type::Days: + { + return QString(i18n("days from start")); + } + case Duration::Type::Seconds: + { + return QString(i18n("seconds from strart")); + } + default: + { + return QString(); + } + } +} + +int IncidenceAlarmsModel::alarmStartOffsetValue(const int idx) const +{ + QHash alarm = mAlarms.at(idx).value>(); + + return alarm["startOffsetValue"].value(); +} + + +QString IncidenceAlarmsModel::alarmUid(const int idx) const +{ + QHash alarm = mAlarms.at(idx).value>(); + + return alarm["uid"].value(); +} + +int IncidenceAlarmsModel::alarmActionType(const int idx) const +{ + QHash alarm = mAlarms.at(idx).value>(); + + return alarm["actionType"].value(); +} + + +QVariantList IncidenceAlarmsModel::alarms() const +{ + return mAlarms; +} + +QString IncidenceAlarmsModel::displayText(const int idx) const +{ + QHash alarm = mAlarms.at(idx).value>(); + + int durationType = alarm["startOffsetType"].value(); + int durationValue = -1*alarm["startOffsetValue"].value(); + + if(durationValue == 0) + { + return i18n("At start time"); + } + if(durationType == Duration::Type::Days) + { + return i18np("1 day from start", "%1 days from start", durationValue); + } + + QString alarmText; + int durDays = durationValue/86400; + alarmText = (durDays != 0) ? i18np("1 day", "%1 days", durDays) : QString(); + int durHours = (durationValue - durDays*86400)/3600; + alarmText = (durHours != 0) ? QString("%1 %2").arg(alarmText, i18np("1 hour", "%1 hours" ,durHours)) : alarmText; + int durMins = (durationValue - durHours*3600 - durDays*86400)/60 ; + alarmText = (durMins != 0) ? QString("%1 %2").arg(alarmText, i18np("1 minute", "%1 minutes", durMins)) : alarmText; + int durSeconds = durationValue - durMins*60- durHours*3600 - durDays*86400; + alarmText = (durSeconds != 0) ? QString("%1 %2").arg(alarmText, i18np("1 second", "%1 seconds", durSeconds)) : alarmText; + + return QString("%1 %2").arg(alarmText, i18n("from start")); +} + diff --git a/src/plugins/incidencealarmsmodel.h b/src/plugins/incidencealarmsmodel.h new file mode 100644 index 0000000000000000000000000000000000000000..f689e67afea17e916bf9c1b1c8cac85a20839469 --- /dev/null +++ b/src/plugins/incidencealarmsmodel.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2019 Dimitris Kardarakos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library. If not, see . + * + */ + +#ifndef INCIDENCEALARMSMODEL_H +#define INCIDENCEALARMSMODEL_H + +#include +#include +#include +#include +#include +#include + +class LocalCalendar; +/** + * @brief Model that serves the alarms of an Incidence set in the input properties + * + */ +class IncidenceAlarmsModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(QVariantMap alarmProperties READ alarmProperties WRITE setAlarmProperties NOTIFY alarmPropertiesChanged) + +public: + explicit IncidenceAlarmsModel(QObject* parent = nullptr); + ~IncidenceAlarmsModel() override; + + enum RoleNames + { + StartOffsetValue = Qt::UserRole+1, + StartOffsetType, + ActionType + }; + + QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex & parent = QModelIndex()) const override; + QHash roleNames() const override; + + /** + * @return A QVariantMap of the input properties + */ + QVariantMap alarmProperties() const; + /** + * @brief Sets the input properties of the model. \p alarmProps should be a QVariantMap with the following members: 1) uid: the uid of the Incidence 2) calendar: the LocalCalendar* that the Incidence belongs to + */ + void setAlarmProperties(const QVariantMap & alarmProps); + +public Q_SLOTS: + /** + * @brief Removes an alarm from the model + */ + void removeAlarm(const int row); + /** + * @brief Creates a model item and adds it to the model + */ + void addAlarm(const int secondsFromStart); + /** + * @return A QVariantList of the items of the model. The members of the list are QHash items that contain the following members: startOffsetValue, startOffsetType and actionType + */ + QVariantList alarms() const; + +Q_SIGNALS: + void alarmPropertiesChanged(); + +private: + void loadPersistentAlarms(); + QString alarmText(const int idx) const; + QString alarmUid(const int idx) const; + int alarmStartOffsetValue(const int idx) const; + QString alarmStartOffsetType(const int idx) const; + int alarmActionType(const int idx) const; + QString displayText(const int idx) const; + + QVariantList mAlarms; + QVariantMap mAlarmProperties; +}; + +#endif diff --git a/src/plugins/localcalendar.cpp b/src/plugins/localcalendar.cpp index bcecfa5d41559e858cd7c9ee893cd34d831efc50..3d648a162a03654032483f77beda55c84d3224ed 100644 --- a/src/plugins/localcalendar.cpp +++ b/src/plugins/localcalendar.cpp @@ -18,6 +18,7 @@ */ #include "localcalendar.h" +#include "calindoriconfig.h" #include #include #include @@ -54,7 +55,9 @@ void LocalCalendar::setName(QString calendarName) { MemoryCalendar::Ptr calendar(new MemoryCalendar(QTimeZone::systemTimeZoneId())); FileStorage::Ptr storage(new FileStorage(calendar)); - m_fullpath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/calindori_" + calendarName + ".ics" ; + CalindoriConfig* config = new CalindoriConfig(); + + m_fullpath = config->calendarFile(calendarName); QFile calendarFile(m_fullpath); storage->setFileName(m_fullpath); diff --git a/src/plugins/qmlplugin.cpp b/src/plugins/qmlplugin.cpp index f661bfa7f70e72001d8153b5eb59e91712f43472..0448c3f632cba59b9260002b52dbe329e943ee1d 100644 --- a/src/plugins/qmlplugin.cpp +++ b/src/plugins/qmlplugin.cpp @@ -23,6 +23,7 @@ #include "todosmodel.h" #include "eventmodel.h" #include "eventcontroller.h" +#include "incidencealarmsmodel.h" #include "todocontroller.h" #include #include @@ -36,4 +37,5 @@ void QmlPlugins::registerTypes(const char *uri) qmlRegisterType(uri, 0, 1, "EventModel"); qmlRegisterType(uri,0,1,"EventController"); qmlRegisterType(uri,0,1,"TodoController"); + qmlRegisterType(uri,0,1,"IncidenceAlarmsModel"); } diff --git a/src/resources.qrc b/src/resources.qrc index 368f2036f287b537fe8a5a85be9447bd310865ac..45170485834bbb7e3fdb3ae6e74e69fafb3e6495 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -21,5 +21,6 @@ contents/ui/FileChooserMobile.qml contents/ui/FileChooserDesktop.qml contents/ui/FileChooser.qml + contents/ui/ReminderEditor.qml