diff --git a/src/alarmtext.cpp b/src/alarmtext.cpp index 6fb2dc3..fe08ead 100644 --- a/src/alarmtext.cpp +++ b/src/alarmtext.cpp @@ -1,598 +1,593 @@ /* * alarmtext.cpp - text/email alarm text conversion * This file is part of kalarmcal library, which provides access to KAlarm * calendar data. * Copyright © 2004-2019 David Jarvie * * This library 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 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 Library General Public * License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include "alarmtext.h" #include "kaevent.h" #include #include #include #include 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::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 KCalCore::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() != 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; } -unsigned long AlarmText::kmailSerialNumber() const -{ - return static_cast(akonadiItemId()); -} - 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(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'), QString::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'), QString::SkipEmptyParts); const int n = Private::emailHeaderCount(lines); if (!n) { return QString(); } 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'), QString::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 += text.midRef(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'), QString::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); i = text.indexOf(QLatin1Char('\n'), i); if (i > 0) { calText += text.midRef(i); } return calText; } } return text; } void AlarmText::Private::clear() { mType = None; mBody.clear(); mTo.clear(); mFrom.clear(); mCc.clear(); mTime.clear(); mSubject.clear(); mAkonadiItemId = -1; } /****************************************************************************** * Set up messages used by executeDropEvent() and emailHeaders(). */ void AlarmText::Private::setUpTranslations() { initialise(); if (mFromPrefix.isNull()) { mFromPrefix = i18nc("@info 'From' email address", "From:"); mToPrefix = i18nc("@info Email addressee", "To:"); mCcPrefix = i18nc("@info Copy-to in email headers", "Cc:"); mDatePrefix = i18nc("@info", "Date:"); mSubjectPrefix = i18nc("@info Email subject", "Subject:"); // Todo prefixes mTitlePrefix = i18nc("@info Todo calendar item's title field", "To-do:"); mLocnPrefix = i18nc("@info Todo calendar item's location field", "Location:"); mDuePrefix = i18nc("@info Todo calendar item's due date/time", "Due:"); } } /****************************************************************************** * Check whether a text is an email. * Reply = number of email header lines, or 0 if not an email. */ int AlarmText::Private::emailHeaderCount(const QStringList &lines) { setUpTranslations(); const int maxn = lines.count(); if (maxn >= MAIL_MIN_LINES && lines[MAIL_FROM_LINE].startsWith(mFromPrefix) && lines[MAIL_TO_LINE].startsWith(mToPrefix)) { int n = MAIL_CC_LINE; if (lines[MAIL_CC_LINE].startsWith(mCcPrefix)) { ++n; } if (maxn > n + 1 && lines[n].startsWith(mDatePrefix) && lines[n + 1].startsWith(mSubjectPrefix)) { return n + 2; } } return 0; } /****************************************************************************** * Return the Todo title line, if the text is for a Todo. */ QString AlarmText::Private::todoTitle(const QString &text) { setUpTranslations(); const QStringList lines = text.split(QLatin1Char('\n'), QString::SkipEmptyParts); int n; for (n = 0; n < lines.count() && lines[n].contains(QLatin1Char('\t')); ++n) ; if (!n || n > 3) { return QString(); } QString title; int i = 0; if (lines[i].startsWith(mTitlePrefix + QLatin1Char('\t'))) { title = lines[i].mid(mTitlePrefix.length()).trimmed(); ++i; } if (i < n && lines[i].startsWith(mLocnPrefix + QLatin1Char('\t'))) { ++i; } if (i < n && lines[i].startsWith(mDuePrefix + QLatin1Char('\t'))) { ++i; } if (i == n) { // It's a Todo text if (!title.isEmpty()) { return title; } if (n < lines.count()) { return lines[n]; } } return QString(); } } // namespace KAlarmCal // vim: et sw=4: diff --git a/src/alarmtext.h b/src/alarmtext.h index 7c353b4..6e195dc 100644 --- a/src/alarmtext.h +++ b/src/alarmtext.h @@ -1,220 +1,214 @@ /* * alarmtext.h - text/email alarm text conversion * This file is part of kalarmcal library, which provides access to KAlarm * calendar data. * Copyright © 2004-2019 David Jarvie * * This library 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 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 Library General Public * License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #ifndef KALARM_ALARMTEXT_H #define KALARM_ALARMTEXT_H #include "kalarmcal_export.h" #include #include #include namespace KAlarmCal { class KAEvent; /** * @short Parses email, todo and script alarm texts. * * This class parses email, todo and script texts, enabling drag and drop of * these items to be recognised and interpreted. It also holds plain alarm * texts. * * - Email texts must contain headers (To, From, etc.) in normal RFC format. * - Todos should be in iCalendar format. * - Scripts are assumed if the alarm text starts with '#!'. * * @author David Jarvie */ class KALARMCAL_EXPORT AlarmText { public: /** Constructor which sets the alarm text. * If @p text starts with '#!', it is flagged as a script, else plain text. * @param text alarm text to set */ explicit AlarmText(const QString &text = QString()); AlarmText(const AlarmText &other); ~AlarmText(); AlarmText &operator=(const AlarmText &other); /** Set the alarm text. * If @p text starts with '#!', it is flagged as a script, else plain text. * @param text alarm text to set */ void setText(const QString &text); /** Set the instance contents to be a script. * @param text text of script to set */ void setScript(const QString &text); /** Set the instance contents to be an email. * @param to 'To' header parameter * @param from 'From' header parameter * @param cc 'Cc' header parameter * @param time 'Date' header parameter * @param subject 'Subject' header parameter * @param body email body text * @param itemId Akonadi item ID of the email. */ void setEmail(const QString &to, const QString &from, const QString &cc, const QString &time, const QString &subject, const QString &body, Akonadi::Item::Id itemId = -1); /** Set the instance contents to be a todo. * @param todo Todo instance to set as the text */ void setTodo(const KCalCore::Todo::Ptr &todo); /** Return the text for a text message alarm, in display format. * - An email is returned as a sequence of headers followed by the message body. * - A todo is returned as a subject, location and due date followed by any text. * - A script or plain text is returned without interpretation. */ QString displayText() const; /** Return the 'To' header parameter for an email alarm. * @return 'from' value, or empty if not an email text. */ QString to() const; /** Return the 'From' header parameter for an email alarm. * @return 'from' value, or empty if not an email text. */ QString from() const; /** Return the 'Cc' header parameter for an email alarm. * @return 'cc' value, or empty if not an email text. */ QString cc() const; /** Return the 'Date' header parameter for an email alarm. * @return 'date' value, or empty if not an email text. */ QString time() const; /** Return the 'Subject' header parameter for an email alarm. * @return 'subject' value, or empty if not an email text. */ QString subject() const; /** Return the email message body. * @return message body, or empty if not an email text. */ QString body() const; /** Return the summary text for a todo. * @return summary text, or empty if not a todo. */ QString summary() const; /** Return the location text for a todo. * @return location text, or empty if not a todo. */ QString location() const; /** Return the due date text for a todo. * @return due date text, or empty if not a todo. */ QString due() const; /** Return the description text for a todo. * @return description text, or empty if not a todo. */ QString description() const; /** Return whether the instance has any contents. */ bool isEmpty() const; /** Return whether the instance contains the text of an email. */ bool isEmail() const; /** Return whether the instance contains the text of a script. */ bool isScript() const; /** Return whether the instance contains the text of a todo. */ bool isTodo() const; - /** Return the kmail serial number of an email. - * @return serial number, or 0 if none. - * @deprecated Use akonadiItemId() instead - */ - KALARMCAL_DEPRECATED unsigned long kmailSerialNumber() const; - /** Return the Akonadi item ID of an email. - * @return serial number, or 0 if none. + * @return Item ID, or -1 if none. */ Akonadi::Item::Id akonadiItemId() const; /** Return the alarm summary text for either single line or tooltip display. * @param event event whose summary text is to be returned * @param maxLines the maximum number of lines returned * @param truncated if non-null, points to a variable which will be set true * if the text returned has been truncated, other than to * strip a trailing newline, or false otherwise */ static QString summary(const KAEvent &event, int maxLines = 1, bool *truncated = nullptr); /** Return whether a text is an email, with at least To and From headers. * @param text text to check */ static bool checkIfEmail(const QString &text); /** Check whether a text is an email (with at least To and From headers), * and if so return its headers or optionally only its subject line. * @param text text to check * @param subjectOnly true to only return the subject line, * false to return all headers * @return headers/subject line, or QString() if not the text of an email. */ static QString emailHeaders(const QString &text, bool subjectOnly); /** Translate an alarm calendar text to a display text. * Translation is needed for email texts, since the alarm calendar stores * untranslated email prefixes. * @param text text to translate * @param email updated to indicate whether it is an email text */ static QString fromCalendarText(const QString &text, bool &email); /** Return the text for an alarm message text, in alarm calendar format. * (The prefix strings are untranslated in the calendar.) * @param text alarm message text */ static QString toCalendarText(const QString &text); private: //@cond PRIVATE class Private; Private *const d; //@endcond }; } // namespace KAlarmCal #endif // KALARM_ALARMTEXT_H // vim: et sw=4: