Commit ffe008df authored by Dimitris Kardarakos's avatar Dimitris Kardarakos

Event alarms functionality added

When adding/editing an event, the users may add multiple event alarms, setting the exact time from the start time of the event. An application has also been created that looks for alarms into the Calindori ical files and triggers alarm notifications. The users may dismiss the alarm displayed or suspend it for a -configurable- amount of time.
parent 6e868db7
......@@ -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)
......
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})
#! /bin/sh
$XGETTEXT `find . -name "*.cpp" -o -name "*.h" | grep -v '/tests/'` -o $podir/calindac.pot
/*
* Copyright (c) 2019 Dimitris Kardarakos <dimkard@posteo.net>
*
* 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 <KLocalizedString>
#include <QDebug>
#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;
}
/*
* Copyright (c) 2019 Dimitris Kardarakos <dimkard@posteo.net>
*
* 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 <KNotification>
#include <QDateTime>
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
/*
* Copyright (c) 2019 Dimitris Kardarakos <dimkard@posteo.net>
*
* 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 <KSharedConfig>
#include <KConfigGroup>
#include <QFile>
#include <QDebug>
AlarmsModel::AlarmsModel(QObject* parent) : QAbstractListModel(parent), mMemoryCalendars(QVector<MemoryCalendar::Ptr>()), mFileStorages(QVector<FileStorage::Ptr>()), mAlarms(Alarm::List()), mCalendarFiles(QStringList()), mParams(QHash<QString, QVariant>())
{
connect(this, &AlarmsModel::paramsChanged, this, &AlarmsModel::loadAlarms);
}
AlarmsModel::~AlarmsModel() = default;
QHash<int, QByteArray> AlarmsModel::roleNames() const
{
QHash<int, QByteArray> 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<MemoryCalendar::Ptr>::const_iterator itr = mMemoryCalendars.constBegin();
while(itr != mMemoryCalendars.constEnd())
{
QDateTime from = mPeriod["from"].value<QDateTime>();
QDateTime to = mPeriod["to"].value<QDateTime>();
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<QString, QVariant> AlarmsModel::params() const
{
return mParams;
}
void AlarmsModel::setParams(const QHash<QString, QVariant>& parameters)
{
mParams = parameters;
QStringList calendarFiles = mParams["calendarFiles"].value<QStringList>();
QVariantMap period = (mParams["period"].value<QVariant>()).value<QVariantMap>();
mCalendarFiles = calendarFiles;
setCalendars();
mPeriod = period;
emit paramsChanged();
}
void AlarmsModel::openLoadStorages()
{
QVector<FileStorage::Ptr>::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<FileStorage::Ptr>::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;
}
/*
Copyright (c) 2019 Dimitris Kardarakos <dimkard@posteo.net>
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 <QAbstractListModel>
#include <KCalCore/Alarm>
#include <KCalCore/MemoryCalendar>
#include <KCalCore/FileStorage>
#include <QVariantMap>
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<QString, QVariant> 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<int, QByteArray> 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<QString, QVariant> 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<QString, QVariant> & 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<MemoryCalendar::Ptr> mMemoryCalendars;
QVector<FileStorage::Ptr> mFileStorages;
Alarm::List mAlarms;
QStringList mCalendarFiles;
QHash<QString, QVariant> mParams;
};
#endif
/*
* Copyright (c) 2019 Dimitris Kardarakos <dimkard@posteo.net>
*
* 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 <KSharedConfig>
#include <KConfigGroup>
#include <QDebug>
#include <QVariantMap>
#include <KLocalizedString>
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<QString, QVariant> modelProperties;
modelProperties["calendarFiles"] = calendarFileList();
modelProperties["period"] = checkPeriod;
mAlarmsModel->setParams(modelProperties);
mNotificationHandler->setPeriod(checkPeriod);
qDebug() << "checkAlarms:\tModel Alarms:" << mAlarmsModel->rowCount();
for(int i=0; i<mAlarmsModel->rowCount(); ++i)
{
QModelIndex index = mAlarmsModel->index(i, 0, QModelIndex());
mNotificationHandler->addActiveNotification(mAlarmsModel->data(index, AlarmsModel::Roles::Uid).value<QString>(), QString("%1\n%2").arg(mAlarmsModel->data(index, AlarmsModel::Roles::IncidenceStartDt).value<QDateTime>().toString("hh:mm"), mAlarmsModel->data(index, AlarmsModel::Roles::Text).value<QString>()));
}
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<QString, QVariant> modelProperties;
modelProperties["calendarFiles"] = calendarFileList();
modelProperties["period"] = checkPeriod;
model->setParams(modelProperties);
QStringList lst = QStringList();
for(int i=0; i<model->rowCount(); ++i)
{
QModelIndex index = model->index(i, 0, QModelIndex());