KAlarmCal

alarmtext.cpp
1 /*
2  * alarmtext.cpp - text/email alarm text conversion
3  * This file is part of kalarmcal library, which provides access to KAlarm
4  * calendar data.
5  * SPDX-FileCopyrightText: 2004-2020 David Jarvie <[email protected]>
6  *
7  * SPDX-License-Identifier: LGPL-2.0-or-later
8  */
9 
10 #include "alarmtext.h"
11 
12 #include "kaevent.h"
13 
14 #include <KLocalizedString>
15 
16 #include <QStringList>
17 #include <QDateTime>
18 #include <QLocale>
19 
20 namespace
21 {
22 const int MAIL_FROM_LINE = 0; // line number containing From in email text
23 const int MAIL_TO_LINE = 1; // line number containing To in email text
24 const int MAIL_CC_LINE = 2; // line number containing CC in email text
25 const int MAIL_MIN_LINES = 4; // allow for From, To, no CC, Date, Subject
26 }
27 
28 namespace KAlarmCal
29 {
30 
31 class Q_DECL_HIDDEN AlarmText::Private
32 {
33 public:
34  enum Type { None, Email, Script, Todo };
35  QString displayText() const;
36  void clear();
37  static void initialise();
38  static void setUpTranslations();
39  static int emailHeaderCount(const QStringList &);
40  static QString todoTitle(const QString &text);
41 
42  static QString mFromPrefix; // translated header prefixes
43  static QString mToPrefix;
44  static QString mCcPrefix;
45  static QString mDatePrefix;
46  static QString mSubjectPrefix;
47  static QString mTitlePrefix;
48  static QString mLocnPrefix;
49  static QString mDuePrefix;
50  static QString mFromPrefixEn; // untranslated header prefixes
51  static QString mToPrefixEn;
52  static QString mCcPrefixEn;
53  static QString mDatePrefixEn;
54  static QString mSubjectPrefixEn;
55  static bool mInitialised;
56 
57  QString mBody, mFrom, mTo, mCc, mTime, mSubject;
58  Akonadi::Item::Id mAkonadiItemId; // if email, message's Akonadi item ID, else -1
59  Type mType;
60  bool mIsEmail;
61 };
62 
63 QString AlarmText::Private::mFromPrefix;
64 QString AlarmText::Private::mToPrefix;
65 QString AlarmText::Private::mCcPrefix;
66 QString AlarmText::Private::mDatePrefix;
67 QString AlarmText::Private::mSubjectPrefix;
68 QString AlarmText::Private::mTitlePrefix;
69 QString AlarmText::Private::mLocnPrefix;
70 QString AlarmText::Private::mDuePrefix;
71 QString AlarmText::Private::mFromPrefixEn;
72 QString AlarmText::Private::mToPrefixEn;
73 QString AlarmText::Private::mCcPrefixEn;
74 QString AlarmText::Private::mDatePrefixEn;
75 QString AlarmText::Private::mSubjectPrefixEn;
76 bool AlarmText::Private::mInitialised = false;
77 
78 void AlarmText::Private::initialise()
79 {
80  if (!mInitialised) {
81  mInitialised = true;
82  mFromPrefixEn = QStringLiteral("From:");
83  mToPrefixEn = QStringLiteral("To:");
84  mCcPrefixEn = QStringLiteral("Cc:");
85  mDatePrefixEn = QStringLiteral("Date:");
86  mSubjectPrefixEn = QStringLiteral("Subject:");
87  }
88 }
89 
91  : d(new Private)
92 {
93  Private::initialise();
94  setText(text);
95 }
96 
97 AlarmText::AlarmText(const AlarmText &other)
98  : d(new Private(*other.d))
99 {
100 }
101 
102 AlarmText::~AlarmText()
103 {
104  delete d;
105 }
106 
107 AlarmText &AlarmText::operator=(const AlarmText &other)
108 {
109  if (&other != this) {
110  *d = *other.d;
111  }
112  return *this;
113 }
114 
116 {
117  d->clear();
118 }
119 
120 void AlarmText::setText(const QString &text)
121 {
122  d->clear();
123  d->mBody = text;
124  if (text.startsWith(QLatin1String("#!"))) {
125  d->mType = Private::Script;
126  }
127 }
128 
129 void AlarmText::setScript(const QString &text)
130 {
131  setText(text);
132  d->mType = Private::Script;
133 }
134 
135 void AlarmText::setEmail(const QString &to, const QString &from, const QString &cc, const QString &time,
136  const QString &subject, const QString &body, Akonadi::Item::Id itemId)
137 {
138  d->clear();
139  d->mType = Private::Email;
140  d->mTo = to;
141  d->mFrom = from;
142  d->mCc = cc;
143  d->mTime = time;
144  d->mSubject = subject;
145  d->mBody = body;
146  d->mAkonadiItemId = itemId;
147 }
148 
150 {
151  d->clear();
152  d->mType = Private::Todo;
153  d->mSubject = todo->summary();
154  d->mBody = todo->description();
155  d->mTo = todo->location();
156  if (todo->hasDueDate()) {
157  QDateTime due = todo->dtDue(false); // fetch the next due date
158  if (todo->hasStartDate() && todo->dtStart(true) != due) {
159  d->mTime = todo->allDay() ? QLocale().toString(due.date(), QLocale::ShortFormat)
161  }
162  }
163 }
164 
165 /******************************************************************************
166 * Return the text for a text message alarm, in display format.
167 */
169 {
170  return d->displayText();
171 }
172 
173 QString AlarmText::Private::displayText() const
174 {
175  QString text;
176  switch (mType) {
177  case Email:
178  // Format the email into a text alarm
179  setUpTranslations();
180  text = mFromPrefix + QLatin1Char('\t') + mFrom + QLatin1Char('\n');
181  text += mToPrefix + QLatin1Char('\t') + mTo + QLatin1Char('\n');
182  if (!mCc.isEmpty()) {
183  text += mCcPrefix + QLatin1Char('\t') + mCc + QLatin1Char('\n');
184  }
185  if (!mTime.isEmpty()) {
186  text += mDatePrefix + QLatin1Char('\t') + mTime + QLatin1Char('\n');
187  }
188  text += mSubjectPrefix + QLatin1Char('\t') + mSubject;
189  if (!mBody.isEmpty()) {
190  text += QLatin1String("\n\n");
191  text += mBody;
192  }
193  break;
194  case Todo:
195  // Format the todo into a text alarm
196  setUpTranslations();
197  if (!mSubject.isEmpty()) {
198  text = mTitlePrefix + QLatin1Char('\t') + mSubject + QLatin1Char('\n');
199  }
200  if (!mTo.isEmpty()) {
201  text += mLocnPrefix + QLatin1Char('\t') + mTo + QLatin1Char('\n');
202  }
203  if (!mTime.isEmpty()) {
204  text += mDuePrefix + QLatin1Char('\t') + mTime + QLatin1Char('\n');
205  }
206  if (!mBody.isEmpty()) {
207  if (!text.isEmpty()) {
208  text += QLatin1Char('\n');
209  }
210  text += mBody;
211  }
212  break;
213  default:
214  break;
215  }
216  return !text.isEmpty() ? text : mBody;
217 }
218 
220 {
221  return (d->mType == Private::Email) ? d->mTo : QString();
222 }
223 
225 {
226  return (d->mType == Private::Email) ? d->mFrom : QString();
227 }
228 
230 {
231  return (d->mType == Private::Email) ? d->mCc : QString();
232 }
233 
235 {
236  return (d->mType == Private::Email) ? d->mTime : QString();
237 }
238 
240 {
241  return (d->mType == Private::Email) ? d->mSubject : QString();
242 }
243 
245 {
246  return (d->mType == Private::Email) ? d->mBody : QString();
247 }
248 
250 {
251  return (d->mType == Private::Todo) ? d->mSubject : QString();
252 }
253 
255 {
256  return (d->mType == Private::Todo) ? d->mTo : QString();
257 }
258 
260 {
261  return (d->mType == Private::Todo) ? d->mTime : QString();
262 }
263 
265 {
266  return (d->mType == Private::Todo) ? d->mBody : QString();
267 }
268 
269 /******************************************************************************
270 * Return whether there is any text.
271 */
272 bool AlarmText::isEmpty() const
273 {
274  if (!d->mBody.isEmpty()) {
275  return false;
276  }
277  if (d->mType != Private::Email) {
278  return true;
279  }
280  return d->mFrom.isEmpty() && d->mTo.isEmpty() && d->mCc.isEmpty() && d->mTime.isEmpty() && d->mSubject.isEmpty();
281 }
282 
283 bool AlarmText::isEmail() const
284 {
285  return d->mType == Private::Email;
286 }
287 
289 {
290  return d->mType == Private::Script;
291 }
292 
293 bool AlarmText::isTodo() const
294 {
295  return d->mType == Private::Todo;
296 }
297 
299 {
300  return d->mAkonadiItemId;
301 }
302 
303 /******************************************************************************
304 * Return the alarm summary text for either single line or tooltip display.
305 * The maximum number of line returned is determined by 'maxLines'.
306 * If 'truncated' is non-null, it will be set true if the text returned has been
307 * truncated, other than to strip a trailing newline.
308 */
309 QString AlarmText::summary(const KAEvent &event, int maxLines, bool *truncated)
310 {
311  static const QRegExp localfile(QStringLiteral("^file:/+"));
312  QString text;
313  switch (event.actionSubType()) {
314  case KAEvent::AUDIO:
315  text = event.audioFile();
316  if (localfile.indexIn(text) >= 0) {
317  text = text.mid(localfile.matchedLength() - 1);
318  }
319  break;
320  case KAEvent::EMAIL:
321  text = event.emailSubject();
322  break;
323  case KAEvent::COMMAND:
324  text = event.cleanText();
325  if (localfile.indexIn(text) >= 0) {
326  text = text.mid(localfile.matchedLength() - 1);
327  }
328  break;
329  case KAEvent::FILE:
330  text = event.cleanText();
331  break;
332  case KAEvent::MESSAGE: {
333  text = event.cleanText();
334  // If the message is the text of an email, return its headers or just subject line
335  QString subject = emailHeaders(text, (maxLines <= 1));
336  if (!subject.isNull()) {
337  if (truncated) {
338  *truncated = true;
339  }
340  return subject;
341  }
342  if (maxLines == 1) {
343  // If the message is the text of a todo, return either the
344  // title/description or the whole text.
345  subject = Private::todoTitle(text);
346  if (!subject.isEmpty()) {
347  if (truncated) {
348  *truncated = true;
349  }
350  return subject;
351  }
352  }
353  break;
354  }
355  }
356  if (truncated) {
357  *truncated = false;
358  }
359  if (text.count(QLatin1Char('\n')) < maxLines) {
360  return text;
361  }
362  int newline = -1;
363  for (int i = 0; i < maxLines; ++i) {
364  newline = text.indexOf(QLatin1Char('\n'), newline + 1);
365  if (newline < 0) {
366  return text; // not truncated after all !?!
367  }
368  }
369  if (newline == static_cast<int>(text.length()) - 1) {
370  return text.left(newline); // text ends in newline
371  }
372  if (truncated) {
373  *truncated = true;
374  }
375  return text.left(newline + (maxLines <= 1 ? 0 : 1)) + QLatin1String("...");
376 }
377 
378 /******************************************************************************
379 * Check whether a text is an email.
380 */
382 {
383  const QStringList lines = text.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
384  return Private::emailHeaderCount(lines);
385 }
386 
387 /******************************************************************************
388 * Check whether a text is an email, and if so return its headers or optionally
389 * only its subject line.
390 * Reply = headers/subject line, or QString() if not the text of an email.
391 */
392 QString AlarmText::emailHeaders(const QString &text, bool subjectOnly)
393 {
394  const QStringList lines = text.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
395  const int n = Private::emailHeaderCount(lines);
396  if (!n) {
397  return {};
398  }
399  if (subjectOnly) {
400  return lines[n - 1].mid(Private::mSubjectPrefix.length()).trimmed();
401  }
402  QString h = lines[0];
403  for (int i = 1; i < n; ++i) {
404  h += QLatin1Char('\n');
405  h += lines[i];
406  }
407  return h;
408 }
409 
410 /******************************************************************************
411 * Translate an alarm calendar text to a display text.
412 * Translation is needed for email texts, since the alarm calendar stores
413 * untranslated email prefixes.
414 * 'email' is set to indicate whether it is an email text.
415 */
416 QString AlarmText::fromCalendarText(const QString &text, bool &email)
417 {
418  Private::initialise();
419  const QStringList lines = text.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
420  const int maxn = lines.count();
421  if (maxn >= MAIL_MIN_LINES
422  && lines[MAIL_FROM_LINE].startsWith(Private::mFromPrefixEn)
423  && lines[MAIL_TO_LINE].startsWith(Private::mToPrefixEn)) {
424  int n = MAIL_CC_LINE;
425  if (lines[MAIL_CC_LINE].startsWith(Private::mCcPrefixEn)) {
426  ++n;
427  }
428  if (maxn > n + 1
429  && lines[n].startsWith(Private::mDatePrefixEn)
430  && lines[n + 1].startsWith(Private::mSubjectPrefixEn)) {
431  Private::setUpTranslations();
432  QString dispText;
433  dispText = Private::mFromPrefix + lines[MAIL_FROM_LINE].mid(Private::mFromPrefixEn.length()) + QLatin1Char('\n');
434  dispText += Private::mToPrefix + lines[MAIL_TO_LINE].mid(Private::mToPrefixEn.length()) + QLatin1Char('\n');
435  if (n > MAIL_CC_LINE) {
436  dispText += Private::mCcPrefix + lines[MAIL_CC_LINE].mid(Private::mCcPrefixEn.length()) + QLatin1Char('\n');
437  }
438  dispText += Private::mDatePrefix + lines[n].mid(Private::mDatePrefixEn.length()) + QLatin1Char('\n');
439  dispText += Private::mSubjectPrefix + lines[n + 1].mid(Private::mSubjectPrefixEn.length());
440  int i = text.indexOf(Private::mSubjectPrefixEn);
441  i = text.indexOf(QLatin1Char('\n'), i);
442  if (i > 0) {
443  dispText += QStringView(text).mid(i);
444  }
445  email = true;
446  return dispText;
447  }
448  }
449  email = false;
450  return text;
451 }
452 
453 /******************************************************************************
454 * Return the text for a text message alarm, in alarm calendar format.
455 * (The prefix strings are untranslated in the calendar.)
456 */
458 {
459  Private::setUpTranslations();
460  const QStringList lines = text.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
461  const int maxn = lines.count();
462  if (maxn >= MAIL_MIN_LINES
463  && lines[MAIL_FROM_LINE].startsWith(Private::mFromPrefix)
464  && lines[MAIL_TO_LINE].startsWith(Private::mToPrefix)) {
465  int n = MAIL_CC_LINE;
466  if (lines[MAIL_CC_LINE].startsWith(Private::mCcPrefix)) {
467  ++n;
468  }
469  if (maxn > n + 1
470  && lines[n].startsWith(Private::mDatePrefix)
471  && lines[n + 1].startsWith(Private::mSubjectPrefix)) {
472  // Format the email into a text alarm
473  QString calText;
474  calText = Private::mFromPrefixEn + lines[MAIL_FROM_LINE].mid(Private::mFromPrefix.length()) + QLatin1Char('\n');
475  calText += Private::mToPrefixEn + lines[MAIL_TO_LINE].mid(Private::mToPrefix.length()) + QLatin1Char('\n');
476  if (n > MAIL_CC_LINE) {
477  calText += Private::mCcPrefixEn + lines[MAIL_CC_LINE].mid(Private::mCcPrefix.length()) + QLatin1Char('\n');
478  }
479  calText += Private::mDatePrefixEn + lines[n].mid(Private::mDatePrefix.length()) + QLatin1Char('\n');
480  calText += Private::mSubjectPrefixEn + lines[n + 1].mid(Private::mSubjectPrefix.length());
481  int i = text.indexOf(Private::mSubjectPrefix);
482  i = text.indexOf(QLatin1Char('\n'), i);
483  if (i > 0) {
484  calText += QStringView(text).mid(i);
485  }
486  return calText;
487  }
488  }
489  return text;
490 }
491 
492 void AlarmText::Private::clear()
493 {
494  mType = None;
495  mBody.clear();
496  mTo.clear();
497  mFrom.clear();
498  mCc.clear();
499  mTime.clear();
500  mSubject.clear();
501  mAkonadiItemId = -1;
502 }
503 
504 /******************************************************************************
505 * Set up messages used by executeDropEvent() and emailHeaders().
506 */
507 void AlarmText::Private::setUpTranslations()
508 {
509  initialise();
510  if (mFromPrefix.isNull()) {
511  mFromPrefix = i18nc("@info 'From' email address", "From:");
512  mToPrefix = i18nc("@info Email addressee", "To:");
513  mCcPrefix = i18nc("@info Copy-to in email headers", "Cc:");
514  mDatePrefix = i18nc("@info", "Date:");
515  mSubjectPrefix = i18nc("@info Email subject", "Subject:");
516  // Todo prefixes
517  mTitlePrefix = i18nc("@info Todo calendar item's title field", "To-do:");
518  mLocnPrefix = i18nc("@info Todo calendar item's location field", "Location:");
519  mDuePrefix = i18nc("@info Todo calendar item's due date/time", "Due:");
520  }
521 }
522 
523 /******************************************************************************
524 * Check whether a text is an email.
525 * Reply = number of email header lines, or 0 if not an email.
526 */
527 int AlarmText::Private::emailHeaderCount(const QStringList &lines)
528 {
529  setUpTranslations();
530  const int maxn = lines.count();
531  if (maxn >= MAIL_MIN_LINES
532  && lines[MAIL_FROM_LINE].startsWith(mFromPrefix)
533  && lines[MAIL_TO_LINE].startsWith(mToPrefix)) {
534  int n = MAIL_CC_LINE;
535  if (lines[MAIL_CC_LINE].startsWith(mCcPrefix)) {
536  ++n;
537  }
538  if (maxn > n + 1
539  && lines[n].startsWith(mDatePrefix)
540  && lines[n + 1].startsWith(mSubjectPrefix)) {
541  return n + 2;
542  }
543  }
544  return 0;
545 }
546 
547 /******************************************************************************
548 * Return the Todo title line, if the text is for a Todo.
549 */
550 QString AlarmText::Private::todoTitle(const QString &text)
551 {
552  setUpTranslations();
553  const QStringList lines = text.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
554  int n;
555  for (n = 0; n < lines.count() && lines[n].contains(QLatin1Char('\t')); ++n) {
556  ;
557  }
558  if (!n || n > 3) {
559  return {};
560  }
561  QString title;
562  int i = 0;
563  if (lines[i].startsWith(mTitlePrefix + QLatin1Char('\t'))) {
564  title = lines[i].mid(mTitlePrefix.length()).trimmed();
565  ++i;
566  }
567  if (i < n && lines[i].startsWith(mLocnPrefix + QLatin1Char('\t'))) {
568  ++i;
569  }
570  if (i < n && lines[i].startsWith(mDuePrefix + QLatin1Char('\t'))) {
571  ++i;
572  }
573  if (i == n) {
574  // It's a Todo text
575  if (!title.isEmpty()) {
576  return title;
577  }
578  if (n < lines.count()) {
579  return lines[n];
580  }
581  }
582  return {};
583 }
584 
585 } // namespace KAlarmCal
586 
587 // vim: et sw=4:
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QString toString(qlonglong i) const const
void setTodo(const KCalendarCore::Todo::Ptr &todo)
Set the instance contents to be a todo.
Definition: alarmtext.cpp:149
QString summary() const
Return the summary text for a todo.
Definition: alarmtext.cpp:249
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
QString cc() const
Return the &#39;Cc&#39; header parameter for an email alarm.
Definition: alarmtext.cpp:229
QString displayText() const
Return the text for a text message alarm, in display format.
Definition: alarmtext.cpp:168
static QString fromCalendarText(const QString &text, bool &email)
Translate an alarm calendar text to a display text.
Definition: alarmtext.cpp:416
bool isEmpty() const
Return whether the instance has any contents.
Definition: alarmtext.cpp:272
QString subject() const
Return the &#39;Subject&#39; header parameter for an email alarm.
Definition: alarmtext.cpp:239
execute a command
Definition: kaevent.h:234
QString location() const
Return the location text for a todo.
Definition: alarmtext.cpp:254
QString time() const
Return the &#39;Date&#39; header parameter for an email alarm.
Definition: alarmtext.cpp:234
bool isNull() const const
QString description() const
Return the description text for a todo.
Definition: alarmtext.cpp:264
Akonadi::Item::Id akonadiItemId() const
Return the Akonadi item ID of an email.
Definition: alarmtext.cpp:298
int matchedLength() const const
int indexIn(const QString &str, int offset, QRegExp::CaretMode caretMode) const const
static QString emailHeaders(const QString &text, bool subjectOnly)
Check whether a text is an email (with at least To and From headers), and if so return its headers or...
Definition: alarmtext.cpp:392
bool isTodo() const
Return whether the instance contains the text of a todo.
Definition: alarmtext.cpp:293
int count(const T &value) const const
static QString toCalendarText(const QString &text)
Return the text for an alarm message text, in alarm calendar format.
Definition: alarmtext.cpp:457
QString i18nc(const char *context, const char *text, const TYPE &arg...)
bool isEmpty() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool isScript() const
Return whether the instance contains the text of a script.
Definition: alarmtext.cpp:288
play an audio file
Definition: kaevent.h:236
QString due() const
Return the due date text for a todo.
Definition: alarmtext.cpp:259
SkipEmptyParts
Email
QString from() const
Return the &#39;From&#39; header parameter for an email alarm.
Definition: alarmtext.cpp:224
Parses email, todo and script alarm texts.
Definition: alarmtext.h:37
display the contents of a file
Definition: kaevent.h:233
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 an email.
Definition: alarmtext.cpp:135
send an email
Definition: kaevent.h:235
void setScript(const QString &text)
Set the instance contents to be a script.
Definition: alarmtext.cpp:129
QString mid(int position, int n) const const
QDate date() const const
void clear()
Initialise the instance to an empty state.
Definition: alarmtext.cpp:115
int count() const const
KAEvent represents a KAlarm event.
Definition: kaevent.h:184
SubAction actionSubType() const
Return the action sub-type of the event&#39;s main alarm.
Definition: kaevent.cpp:1776
QList< T > mid(int pos, int length) const const
int length() const const
static bool checkIfEmail(const QString &text)
Return whether a text is an email, with at least To and From headers.
Definition: alarmtext.cpp:381
QString left(int n) const const
bool isEmail() const
Return whether the instance contains the text of an email.
Definition: alarmtext.cpp:283
void setText(const QString &text)
Set the alarm text.
Definition: alarmtext.cpp:120
AlarmText(const QString &text=QString())
Constructor which sets the alarm text.
Definition: alarmtext.cpp:90
QString body() const
Return the email message body.
Definition: alarmtext.cpp:244
QString to() const
Return the &#39;To&#39; header parameter for an email alarm.
Definition: alarmtext.cpp:219
display a message text
Definition: kaevent.h:232
QStringView mid(qsizetype start) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Tue Dec 7 2021 23:02:21 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.