Commit 1681114a authored by David Jarvie's avatar David Jarvie
Browse files

Merge kalarmcal into kalarm

parents 0a7bc059 d452d428
This diff is collapsed.
/*!
* @mainpage The KAlarm client library
*
* @section purpose Purpose
*
* This library provides access to and handling of KAlarm calendar data.
*
* @section desc Description
*
* This library provides access to KAlarm calendar data, but not to the storage
* of the data, which is handled separately.
* The main class, KAEvent, represents a KAlarm event, and contains both the
* event definition including the main alarm and optional subsidiary alarms, and
* status information about the event.
*
* Calendar format information is accessed through the KACalendar class, which
* provides read and write access to the calendar format version and
* iCalendar product ID.
*
* Recurrence and sub-repetition information is represented by the KARecurrence
* and Repetition classes respectively.
*
* The KADateTime class is analagous to QDateTime, but can alternatively hold
* a date-only value, and its time zone definition is more flexible than
* QDateTime. The DateTime class is similar to KADateTime but with a
* configurable start-of-day time for date-only times.
*
* Three Akonadi attributes, for Collections and Items, are provided by
* CollectionAttribute, CompatibilityAttribute and EventAttribute classes.
*
* @authors
* David Jarvie \<djarvie@kde.org\>
*
* @licenses
* @lgpl
*/
// DOXYGEN_PROJECTNAME=KAlarm Library
// DOXYGEN_REFERENCES=kdecore kcalendarcore kholidays kpimidentities akonadi
/*
* akonadi.cpp - Akonadi object functions
* This file is part of kalarmcal library, which provides access to KAlarm
* calendar data.
* SPDX-FileCopyrightText: 2011, 2019 David Jarvie <djarvie@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "akonadi.h"
#include "kaevent.h"
#include <Akonadi/Item>
namespace KAlarmCal
{
/******************************************************************************
* Initialise an Item with the event.
* Note that the event is not updated with the Item ID.
* Reply = true if successful,
* false if event's category does not match collection's mime types.
*/
bool setItemPayload(Akonadi::Item &item, const KAEvent &event, const QStringList &collectionMimeTypes)
{
QString mimetype;
switch (event.category()) {
case CalEvent::ACTIVE: mimetype = MIME_ACTIVE; break;
case CalEvent::ARCHIVED: mimetype = MIME_ARCHIVED; break;
case CalEvent::TEMPLATE: mimetype = MIME_TEMPLATE; break;
default: Q_ASSERT(0); return false;
}
if (!collectionMimeTypes.contains(mimetype)) {
return false;
}
item.setMimeType(mimetype);
item.setPayload<KAEvent>(event);
return true;
}
}
/*
* akonadi.h - Akonadi object functions
* This file is part of kalarmcal library, which provides access to KAlarm
* calendar data.
* SPDX-FileCopyrightText: 2011, 2019 David Jarvie <djarvie@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#pragma once
#include "kalarmcal_export.h"
#include <QStringList>
namespace Akonadi
{
class Item;
}
namespace KAlarmCal
{
class KAEvent;
/** Initialise an Akonadi::Item with the event's data.
* Note that the event is not updated with the Item ID, and the Item is not
* added to the Collection.
* @param item the Item to initialise.
* @param event the event whose data will be used to initialise the Item.
* @param collectionMimeTypes the mime types for the Collection which will contain the Item.
* @return @c true if successful; @c false if the event's category does not match the
* collection's mime types.
*/
KALARMCAL_EXPORT bool setItemPayload(Akonadi::Item &item, const KAEvent &event, const QStringList &collectionMimeTypes);
}
/*
* alarmtext.cpp - text/email alarm text conversion
* This file is part of kalarmcal library, which provides access to KAlarm
* calendar data.
* SPDX-FileCopyrightText: 2004-2020 David Jarvie <djarvie@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "alarmtext.h"
#include "kaevent.h"
#include <KLocalizedString>
#include <QStringList>
#include <QDateTime>
#include <QLocale>
#include <QRegExp>
namespace
{
const int MAIL_FROM_LINE = 0; // line number containing From in email text
const int MAIL_TO_LINE = 1; // line number containing To in email text
const int MAIL_CC_LINE = 2; // line number containing CC in email text
const int MAIL_MIN_LINES = 4; // allow for From, To, no CC, Date, Subject
}
namespace KAlarmCal
{
class Q_DECL_HIDDEN AlarmText::Private
{
public:
enum Type { None, Email, Script, Todo };
QString displayText() const;
void clear();
static void initialise();
static void setUpTranslations();
static int emailHeaderCount(const QStringList &);
static QString todoTitle(const QString &text);
static QString mFromPrefix; // translated header prefixes
static QString mToPrefix;
static QString mCcPrefix;
static QString mDatePrefix;
static QString mSubjectPrefix;
static QString mTitlePrefix;
static QString mLocnPrefix;
static QString mDuePrefix;
static QString mFromPrefixEn; // untranslated header prefixes
static QString mToPrefixEn;
static QString mCcPrefixEn;
static QString mDatePrefixEn;
static QString mSubjectPrefixEn;
static bool mInitialised;
QString mBody, mFrom, mTo, mCc, mTime, mSubject;
Akonadi::Item::Id mAkonadiItemId; // if email, message's Akonadi item ID, else -1
Type mType;
bool mIsEmail;
};
QString AlarmText::Private::mFromPrefix;
QString AlarmText::Private::mToPrefix;
QString AlarmText::Private::mCcPrefix;
QString AlarmText::Private::mDatePrefix;
QString AlarmText::Private::mSubjectPrefix;
QString AlarmText::Private::mTitlePrefix;
QString AlarmText::Private::mLocnPrefix;
QString AlarmText::Private::mDuePrefix;
QString AlarmText::Private::mFromPrefixEn;
QString AlarmText::Private::mToPrefixEn;
QString AlarmText::Private::mCcPrefixEn;
QString AlarmText::Private::mDatePrefixEn;
QString AlarmText::Private::mSubjectPrefixEn;
bool AlarmText::Private::mInitialised = false;
void AlarmText::Private::initialise()
{
if (!mInitialised) {
mInitialised = true;
mFromPrefixEn = QStringLiteral("From:");
mToPrefixEn = QStringLiteral("To:");
mCcPrefixEn = QStringLiteral("Cc:");
mDatePrefixEn = QStringLiteral("Date:");
mSubjectPrefixEn = QStringLiteral("Subject:");
}
}
AlarmText::AlarmText(const QString &text)
: d(new Private)
{
Private::initialise();
setText(text);
}
AlarmText::AlarmText(const AlarmText &other)
: d(new Private(*other.d))
{
}
AlarmText::~AlarmText()
{
delete d;
}
AlarmText &AlarmText::operator=(const AlarmText &other)
{
if (&other != this) {
*d = *other.d;
}
return *this;
}
void AlarmText::clear()
{
d->clear();
}
void AlarmText::setText(const QString &text)
{
d->clear();
d->mBody = text;
if (text.startsWith(QLatin1String("#!"))) {
d->mType = Private::Script;
}
}
void AlarmText::setScript(const QString &text)
{
setText(text);
d->mType = Private::Script;
}
void AlarmText::setEmail(const QString &to, const QString &from, const QString &cc, const QString &time,
const QString &subject, const QString &body, Akonadi::Item::Id itemId)
{
d->clear();
d->mType = Private::Email;
d->mTo = to;
d->mFrom = from;
d->mCc = cc;
d->mTime = time;
d->mSubject = subject;
d->mBody = body;
d->mAkonadiItemId = itemId;
}
void AlarmText::setTodo(const KCalendarCore::Todo::Ptr &todo)
{
d->clear();
d->mType = Private::Todo;
d->mSubject = todo->summary();
d->mBody = todo->description();
d->mTo = todo->location();
if (todo->hasDueDate()) {
QDateTime due = todo->dtDue(false); // fetch the next due date
if (todo->hasStartDate() && todo->dtStart(true) != due) {
d->mTime = todo->allDay() ? QLocale().toString(due.date(), QLocale::ShortFormat)
: QLocale().toString(due, QLocale::ShortFormat);
}
}
}
/******************************************************************************
* Return the text for a text message alarm, in display format.
*/
QString AlarmText::displayText() const
{
return d->displayText();
}
QString AlarmText::Private::displayText() const
{
QString text;
switch (mType) {
case Email:
// Format the email into a text alarm
setUpTranslations();
text = mFromPrefix + QLatin1Char('\t') + mFrom + QLatin1Char('\n');
text += mToPrefix + QLatin1Char('\t') + mTo + QLatin1Char('\n');
if (!mCc.isEmpty()) {
text += mCcPrefix + QLatin1Char('\t') + mCc + QLatin1Char('\n');
}
if (!mTime.isEmpty()) {
text += mDatePrefix + QLatin1Char('\t') + mTime + QLatin1Char('\n');
}
text += mSubjectPrefix + QLatin1Char('\t') + mSubject;
if (!mBody.isEmpty()) {
text += QLatin1String("\n\n");
text += mBody;
}
break;
case Todo:
// Format the todo into a text alarm
setUpTranslations();
if (!mSubject.isEmpty()) {
text = mTitlePrefix + QLatin1Char('\t') + mSubject + QLatin1Char('\n');
}
if (!mTo.isEmpty()) {
text += mLocnPrefix + QLatin1Char('\t') + mTo + QLatin1Char('\n');
}
if (!mTime.isEmpty()) {
text += mDuePrefix + QLatin1Char('\t') + mTime + QLatin1Char('\n');
}
if (!mBody.isEmpty()) {
if (!text.isEmpty()) {
text += QLatin1Char('\n');
}
text += mBody;
}
break;
default:
break;
}
return !text.isEmpty() ? text : mBody;
}
QString AlarmText::to() const
{
return (d->mType == Private::Email) ? d->mTo : QString();
}
QString AlarmText::from() const
{
return (d->mType == Private::Email) ? d->mFrom : QString();
}
QString AlarmText::cc() const
{
return (d->mType == Private::Email) ? d->mCc : QString();
}
QString AlarmText::time() const
{
return (d->mType == Private::Email) ? d->mTime : QString();
}
QString AlarmText::subject() const
{
return (d->mType == Private::Email) ? d->mSubject : QString();
}
QString AlarmText::body() const
{
return (d->mType == Private::Email) ? d->mBody : QString();
}
QString AlarmText::summary() const
{
return (d->mType == Private::Todo) ? d->mSubject : QString();
}
QString AlarmText::location() const
{
return (d->mType == Private::Todo) ? d->mTo : QString();
}
QString AlarmText::due() const
{
return (d->mType == Private::Todo) ? d->mTime : QString();
}
QString AlarmText::description() const
{
return (d->mType == Private::Todo) ? d->mBody : QString();
}
/******************************************************************************
* Return whether there is any text.
*/
bool AlarmText::isEmpty() const
{
if (!d->mBody.isEmpty()) {
return false;
}
if (d->mType != Private::Email) {
return true;
}
return d->mFrom.isEmpty() && d->mTo.isEmpty() && d->mCc.isEmpty() && d->mTime.isEmpty() && d->mSubject.isEmpty();
}
bool AlarmText::isEmail() const
{
return d->mType == Private::Email;
}
bool AlarmText::isScript() const
{
return d->mType == Private::Script;
}
bool AlarmText::isTodo() const
{
return d->mType == Private::Todo;
}
Akonadi::Item::Id AlarmText::akonadiItemId() const
{
return d->mAkonadiItemId;
}
/******************************************************************************
* Return the alarm summary text for either single line or tooltip display.
* The maximum number of line returned is determined by 'maxLines'.
* If 'truncated' is non-null, it will be set true if the text returned has been
* truncated, other than to strip a trailing newline.
*/
QString AlarmText::summary(const KAEvent &event, int maxLines, bool *truncated)
{
static const QRegExp localfile(QStringLiteral("^file:/+"));
QString text;
switch (event.actionSubType()) {
case KAEvent::AUDIO:
text = event.audioFile();
if (localfile.indexIn(text) >= 0) {
text = text.mid(localfile.matchedLength() - 1);
}
break;
case KAEvent::EMAIL:
text = event.emailSubject();
break;
case KAEvent::COMMAND:
text = event.cleanText();
if (localfile.indexIn(text) >= 0) {
text = text.mid(localfile.matchedLength() - 1);
}
break;
case KAEvent::FILE:
text = event.cleanText();
break;
case KAEvent::MESSAGE: {
text = event.cleanText();
// If the message is the text of an email, return its headers or just subject line
QString subject = emailHeaders(text, (maxLines <= 1));
if (!subject.isNull()) {
if (truncated) {
*truncated = true;
}
return subject;
}
if (maxLines == 1) {
// If the message is the text of a todo, return either the
// title/description or the whole text.
subject = Private::todoTitle(text);
if (!subject.isEmpty()) {
if (truncated) {
*truncated = true;
}
return subject;
}
}
break;
}
}
if (truncated) {
*truncated = false;
}
if (text.count(QLatin1Char('\n')) < maxLines) {
return text;
}
int newline = -1;
for (int i = 0; i < maxLines; ++i) {
newline = text.indexOf(QLatin1Char('\n'), newline + 1);
if (newline < 0) {
return text; // not truncated after all !?!
}
}
if (newline == static_cast<int>(text.length()) - 1) {
return text.left(newline); // text ends in newline
}
if (truncated) {
*truncated = true;
}
return text.left(newline + (maxLines <= 1 ? 0 : 1)) + QLatin1String("...");
}
/******************************************************************************
* Check whether a text is an email.
*/
bool AlarmText::checkIfEmail(const QString &text)
{
const QStringList lines = text.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
return Private::emailHeaderCount(lines);
}
/******************************************************************************
* Check whether a text is an email, and if so return its headers or optionally
* only its subject line.
* Reply = headers/subject line, or QString() if not the text of an email.
*/
QString AlarmText::emailHeaders(const QString &text, bool subjectOnly)
{
const QStringList lines = text.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
const int n = Private::emailHeaderCount(lines);
if (!n) {
return {};
}
if (subjectOnly) {
return lines[n - 1].mid(Private::mSubjectPrefix.length()).trimmed();
}
QString h = lines[0];
for (int i = 1; i < n; ++i) {
h += QLatin1Char('\n');
h += lines[i];
}
return h;
}
/******************************************************************************
* Translate an alarm calendar text to a display text.
* Translation is needed for email texts, since the alarm calendar stores
* untranslated email prefixes.
* 'email' is set to indicate whether it is an email text.
*/
QString AlarmText::fromCalendarText(const QString &text, bool &email)
{
Private::initialise();
const QStringList lines = text.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
const int maxn = lines.count();
if (maxn >= MAIL_MIN_LINES
&& lines[MAIL_FROM_LINE].startsWith(Private::mFromPrefixEn)
&& lines[MAIL_TO_LINE].startsWith(Private::mToPrefixEn)) {
int n = MAIL_CC_LINE;
if (lines[MAIL_CC_LINE].startsWith(Private::mCcPrefixEn)) {
++n;
}
if (maxn > n + 1
&& lines[n].startsWith(Private::mDatePrefixEn)
&& lines[n + 1].startsWith(Private::mSubjectPrefixEn)) {
Private::setUpTranslations();
QString dispText;
dispText = Private::mFromPrefix + lines[MAIL_FROM_LINE].mid(Private::mFromPrefixEn.length()) + QLatin1Char('\n');
dispText += Private::mToPrefix + lines[MAIL_TO_LINE].mid(Private::mToPrefixEn.length()) + QLatin1Char('\n');
if (n > MAIL_CC_LINE) {
dispText += Private::mCcPrefix + lines[MAIL_CC_LINE].mid(Private::mCcPrefixEn.length()) + QLatin1Char('\n');
}
dispText += Private::mDatePrefix + lines[n].mid(Private::mDatePrefixEn.length()) + QLatin1Char('\n');
dispText += Private::mSubjectPrefix + lines[n + 1].mid(Private::mSubjectPrefixEn.length());
int i = text.indexOf(Private::mSubjectPrefixEn);
i = text.indexOf(QLatin1Char('\n'), i);
if (i > 0) {
dispText += QStringView(text).mid(i);
}
email = true;
return dispText;
}
}
email = false;
return text;
}
/******************************************************************************
* Return the text for a text message alarm, in alarm calendar format.
* (The prefix strings are untranslated in the calendar.)
*/
QString AlarmText::toCalendarText(const QString &text)
{
Private::setUpTranslations();
const QStringList lines = text.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
const int maxn = lines.count();
if (maxn >= MAIL_MIN_LINES
&& lines[MAIL_FROM_LINE].startsWith(Private::mFromPrefix)
&& lines[MAIL_TO_LINE].startsWith(Private::mToPrefix)) {
int n = MAIL_CC_LINE;
if (lines[MAIL_CC_LINE].startsWith(Private::mCcPrefix)) {
++n;
}
if (maxn > n + 1
&& lines[n].startsWith(Private::mDatePrefix)
&& lines[n + 1].startsWith(Private::mSubjectPrefix)) {
// Format the email into a text alarm
QString calText;
calText = Private::mFromPrefixEn + lines[MAIL_FROM_LINE].mid(Private::mFromPrefix.length()) + QLatin1Char('\n');
calText += Private::mToPrefixEn + lines[MAIL_TO_LINE].mid(Private::mToPrefix.length()) + QLatin1Char('\n');
if (n > MAIL_CC_LINE) {
calText += Private::mCcPrefixEn + lines[MAIL_CC_LINE].mid(Private::mCcPrefix.length()) + QLatin1Char('\n');
}
calText += Private::mDatePrefixEn + lines[n].mid(Private::mDatePrefix.length()) + QLatin1Char('\n');
calText += Private::mSubjectPrefixEn + lines[n + 1].mid(Private::mSubjectPrefix.length());
int i = text.indexOf(Private::mSubjectPrefix);