1/****************************************************************************
2**
3** Copyright (C) 2015 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the QtVersitOrganizer module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL21$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 2.1 or version 3 as published by the Free
20** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22** following information to ensure the GNU Lesser General Public License
23** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25**
26** As a special exception, The Qt Company gives you certain additional
27** rights. These rights are described in The Qt Company LGPL Exception
28** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29**
30** $QT_END_LICENSE$
31**
32****************************************************************************/
33
34#include "qversitorganizerexporter.h"
35#include "qversitorganizerexporter_p.h"
36
37#include <QtCore/qdebug.h>
38
39#include <QtOrganizer/qorganizer.h>
40
41#include <QtVersit/qversitdocument.h>
42#include <QtVersit/qversitproperty.h>
43#include <QtVersit/private/qversitutils_p.h>
44
45#include "qversitorganizerdefs_p.h"
46#include "qversitorganizerpluginloader_p.h"
47
48QTORGANIZER_USE_NAMESPACE
49QTVERSIT_USE_NAMESPACE
50
51QT_BEGIN_NAMESPACE_VERSITORGANIZER
52
53QVersitOrganizerExporterPrivate::QVersitOrganizerExporterPrivate(const QString& profile) :
54 mDetailHandler(NULL),
55 mTimeZoneHandler(NULL)
56{
57 int versitPropertyCount =
58 sizeof(versitOrganizerDetailMappings)/sizeof(VersitOrganizerDetailMapping);
59 for (int i = 0; i < versitPropertyCount; i++) {
60 mPropertyMappings.insert(
61 akey: versitOrganizerDetailMappings[i].detailType,
62 avalue: QPair<int, QString>(
63 versitOrganizerDetailMappings[i].detailField,
64 QLatin1String(versitOrganizerDetailMappings[i].versitPropertyName)));
65 }
66
67 mPluginDetailHandlers = QVersitOrganizerPluginLoader::instance()->createOrganizerHandlers(profile);
68 mTimeZoneHandler = QVersitOrganizerPluginLoader::instance()->timeZoneHandler();
69}
70
71QVersitOrganizerExporterPrivate::~QVersitOrganizerExporterPrivate()
72{
73 foreach (QVersitOrganizerHandler* pluginHandler, mPluginDetailHandlers) {
74 delete pluginHandler;
75 }
76}
77
78bool QVersitOrganizerExporterPrivate::exportItem(
79 const QOrganizerItem& item,
80 QVersitDocument* document,
81 QVersitOrganizerExporter::Error* error)
82{
83 if (item.isEmpty()) {
84 *error = QVersitOrganizerExporter::EmptyOrganizerError;
85 return false;
86 }
87 if (item.type() == QOrganizerItemType::TypeEvent
88 || item.type() == QOrganizerItemType::TypeEventOccurrence) {
89 document->setComponentType(QStringLiteral("VEVENT"));
90 } else if (item.type() == QOrganizerItemType::TypeTodo
91 || item.type() == QOrganizerItemType::TypeTodoOccurrence) {
92 document->setComponentType(QStringLiteral("VTODO"));
93 } else if (item.type() == QOrganizerItemType::TypeJournal) {
94 document->setComponentType(QStringLiteral("VJOURNAL"));
95 } else {
96 *error = QVersitOrganizerExporter::UnknownComponentTypeError;
97 return false;
98 }
99 QList<QOrganizerItemDetail> allDetails = item.details();
100 if (allDetails.isEmpty()) {
101 *error = QVersitOrganizerExporter::EmptyOrganizerError;
102 return false;
103 }
104 foreach (const QOrganizerItemDetail& detail, allDetails) {
105 exportDetail(item, detail, document);
106 }
107
108 // run plugin handlers
109 foreach (QVersitOrganizerExporterDetailHandler* handler, mPluginDetailHandlers) {
110 handler->itemProcessed(item, document);
111 }
112 // run the handler, if set
113 if (mDetailHandler) {
114 mDetailHandler->itemProcessed(item, document);
115 }
116 if (item.type() == QOrganizerItemType::TypeEventOccurrence
117 && !documentContainsUidAndRecurrenceId(document: *document)) {
118 *error = QVersitOrganizerExporter::UnderspecifiedOccurrenceError;
119 return false;
120 }
121 return true;
122}
123
124void QVersitOrganizerExporterPrivate::exportDetail(
125 const QOrganizerItem& item,
126 const QOrganizerItemDetail& detail,
127 QVersitDocument* document)
128{
129 QList<QVersitProperty> removedProperties;
130 QList<QVersitProperty> generatedProperties;
131 QSet<int> processedFields;
132
133 if (detail.type() == QOrganizerItemDetail::TypeEventTime) {
134 encodeEventTimeRange(detail, document: *document, removedProperties: &removedProperties, generatedProperties: &generatedProperties, processedFields: &processedFields);
135 } else if (detail.type() == QOrganizerItemDetail::TypeTodoTime) {
136 encodeTodoTimeRange(detail, document: *document, removedProperties: &removedProperties, generatedProperties: &generatedProperties, processedFields: &processedFields);
137 } else if (detail.type() == QOrganizerItemDetail::TypeJournalTime) {
138 encodeJournalTimeRange(detail, document: *document, removedProperties: &removedProperties, generatedProperties: &generatedProperties, processedFields: &processedFields);
139 } else if (detail.type() == QOrganizerItemDetail::TypeTimestamp) {
140 encodeTimestamp(detail, document: *document, removedProperties: &removedProperties, generatedProperties: &generatedProperties, processedFields: &processedFields);
141 } else if (detail.type() == QOrganizerItemDetail::TypeRecurrence) {
142 encodeRecurrence(item, detail, document: *document, removedProperties: &removedProperties, generatedProperties: &generatedProperties, processedFields: &processedFields);
143 } else if (detail.type() == QOrganizerItemDetail::TypePriority) {
144 encodePriority(detail, document: *document, removedProperties: &removedProperties, generatedProperties: &generatedProperties, processedFields: &processedFields);
145 } else if (detail.type() == QOrganizerItemDetail::TypeParent) {
146 encodeInstanceOrigin(detail, document: *document, removedProperties: &removedProperties, generatedProperties: &generatedProperties, processedFields: &processedFields);
147 } else if (detail.type() == QOrganizerItemDetail::TypeTodoProgress) {
148 encodeTodoProgress(detail, document: *document, removedProperties: &removedProperties, generatedProperties: &generatedProperties, processedFields: &processedFields);
149 } else if (detail.type() == QOrganizerItemDetail::TypeComment) {
150 encodeComment(detail, generatedProperties: &generatedProperties, processedFields: &processedFields);
151 } else if (detail.type() == QOrganizerItemDetail::TypeExtendedDetail) {
152 encodeExtendedDetail(detail, generatedProperties: &generatedProperties, processedFields: &processedFields);
153 } else if (detail.type() == QOrganizerItemDetail::TypeVersion) {
154 encodeVersion(detail, document: *document, removedProperties: &removedProperties, generatedProperties: &generatedProperties, processedFields: &processedFields);
155 } else if (detail.type() == QOrganizerItemDetail::TypeAudibleReminder) {
156 encodeAudibleReminder(item, detail, generatedProperties: &generatedProperties, processedFields: &processedFields);
157 } else if (detail.type() == QOrganizerItemDetail::TypeVisualReminder) {
158 encodeVisualReminder(item, detail, generatedProperties: &generatedProperties, processedFields: &processedFields);
159 } else if (detail.type() == QOrganizerItemDetail::TypeEmailReminder) {
160 encodeEmailReminder(item, detail, generatedProperties: &generatedProperties, processedFields: &processedFields);
161 } else if (mPropertyMappings.contains(akey: detail.type())) {
162 encodeSimpleProperty(detail, document: *document, removedProperties: &removedProperties, generatedProperties: &generatedProperties, processedFields: &processedFields);
163 }
164
165 // run the plugin handler
166 foreach (QVersitOrganizerExporterDetailHandler* handler, mPluginDetailHandlers) {
167 handler->detailProcessed(item, detail, document: *document,
168 processedFields: &processedFields, toBeRemoved: &removedProperties, toBeAdded: &generatedProperties);
169 }
170 // run the detail handler, if set
171 if (mDetailHandler) {
172 mDetailHandler->detailProcessed(item, detail, document: *document,
173 processedFields: &processedFields, toBeRemoved: &removedProperties, toBeAdded: &generatedProperties);
174 }
175
176 foreach(const QVersitProperty& property, removedProperties) {
177 document->removeProperty(property);
178 }
179 foreach(const QVersitProperty& property, generatedProperties) {
180 if (property.valueType() == QVersitProperty::VersitDocumentType) {
181 QVersitDocument subDocument(QVersitDocument::ICalendar20Type);
182 subDocument = property.value<QVersitDocument>();
183 document->addSubDocument(subdocument: subDocument);
184 } else {
185 document->addProperty(property);
186 }
187 }
188}
189
190void QVersitOrganizerExporterPrivate::encodeEventTimeRange(
191 const QOrganizerItemDetail& detail,
192 const QVersitDocument& document,
193 QList<QVersitProperty>* removedProperties,
194 QList<QVersitProperty>* generatedProperties,
195 QSet<int>* processedFields)
196{
197 const QOrganizerEventTime &etr = static_cast<const QOrganizerEventTime &>(detail);
198 bool isAllDay = etr.isAllDay();
199 QVersitProperty property = takeProperty(document, QStringLiteral("DTSTART"), toBeRemoved: removedProperties);
200 property.setName(QStringLiteral("DTSTART"));
201 if (isAllDay) {
202 property.setValue(etr.startDateTime().date().toString(QStringLiteral("yyyyMMdd")));
203 property.insertParameter(QStringLiteral("VALUE"), QStringLiteral("DATE"));
204 } else {
205 property.setValue(encodeDateTime(dateTime: etr.startDateTime()));
206 }
207 *generatedProperties << property;
208
209 property = takeProperty(document, QStringLiteral("DTEND"), toBeRemoved: removedProperties);
210 property.setName(QStringLiteral("DTEND"));
211 if (isAllDay) {
212 // In iCalendar, the end date is exclusive while in Qt Organizer, it is inclusive.
213 property.setValue(etr.endDateTime().date().addDays(days: 1).toString(QStringLiteral("yyyyMMdd")));
214 property.insertParameter(QStringLiteral("VALUE"), QStringLiteral("DATE"));
215 } else {
216 property.setValue(encodeDateTime(dateTime: etr.endDateTime()));
217 }
218 *generatedProperties << property;
219 *processedFields << QOrganizerEventTime::FieldStartDateTime
220 << QOrganizerEventTime::FieldEndDateTime;
221}
222
223void QVersitOrganizerExporterPrivate::encodeTodoTimeRange(
224 const QOrganizerItemDetail& detail,
225 const QVersitDocument& document,
226 QList<QVersitProperty>* removedProperties,
227 QList<QVersitProperty>* generatedProperties,
228 QSet<int>* processedFields)
229{
230 const QOrganizerTodoTime &ttr = static_cast<const QOrganizerTodoTime &>(detail);
231 bool isAllDay = ttr.isAllDay();
232 QVersitProperty property = takeProperty(document, QStringLiteral("DTSTART"), toBeRemoved: removedProperties);
233 property.setName(QStringLiteral("DTSTART"));
234 if (isAllDay) {
235 property.setValue(ttr.startDateTime().date().toString(QStringLiteral("yyyyMMdd")));
236 property.insertParameter(QStringLiteral("VALUE"), QStringLiteral("DATE"));
237 } else {
238 property.setValue(encodeDateTime(dateTime: ttr.startDateTime()));
239 }
240 *generatedProperties << property;
241
242 property = takeProperty(document, QStringLiteral("DUE"), toBeRemoved: removedProperties);
243 property.setName(QStringLiteral("DUE"));
244 if (isAllDay) {
245 property.setValue(ttr.dueDateTime().date().toString(QStringLiteral("yyyyMMdd")));
246 property.insertParameter(QStringLiteral("VALUE"), QStringLiteral("DATE"));
247 } else {
248 property.setValue(encodeDateTime(dateTime: ttr.dueDateTime()));
249 }
250 *generatedProperties << property;
251 *processedFields << QOrganizerTodoTime::FieldStartDateTime
252 << QOrganizerTodoTime::FieldDueDateTime;
253}
254
255void QVersitOrganizerExporterPrivate::encodeJournalTimeRange(
256 const QOrganizerItemDetail& detail,
257 const QVersitDocument& document,
258 QList<QVersitProperty>* removedProperties,
259 QList<QVersitProperty>* generatedProperties,
260 QSet<int>* processedFields)
261{
262 const QOrganizerJournalTime &jtr = static_cast<const QOrganizerJournalTime &>(detail);
263 QVersitProperty property = takeProperty(document, QStringLiteral("DTSTART"), toBeRemoved: removedProperties);
264 property.setName(QStringLiteral("DTSTART"));
265 property.setValue(encodeDateTime(dateTime: jtr.entryDateTime()));
266 *generatedProperties << property;
267 *processedFields << QOrganizerJournalTime::FieldEntryDateTime;
268}
269
270void QVersitOrganizerExporterPrivate::encodeTimestamp(
271 const QOrganizerItemDetail& detail,
272 const QVersitDocument& document,
273 QList<QVersitProperty>* removedProperties,
274 QList<QVersitProperty>* generatedProperties,
275 QSet<int>* processedFields)
276{
277 const QOrganizerItemTimestamp &timestamp = static_cast<const QOrganizerItemTimestamp &>(detail);
278 QVersitProperty property = takeProperty(document, QStringLiteral("CREATED"), toBeRemoved: removedProperties);
279 property.setName(QStringLiteral("CREATED"));
280 property.setValue(encodeDateTime(dateTime: timestamp.created().toUTC()));
281 *generatedProperties << property;
282
283 property = takeProperty(document, QStringLiteral("LAST-MODIFIED"), toBeRemoved: removedProperties);
284 property.setName(QStringLiteral("LAST-MODIFIED"));
285 property.setValue(encodeDateTime(dateTime: timestamp.lastModified().toUTC()));
286 *generatedProperties << property;
287 *processedFields << QOrganizerItemTimestamp::FieldCreated
288 << QOrganizerItemTimestamp::FieldLastModified;
289}
290
291void QVersitOrganizerExporterPrivate::encodeVersion(
292 const QOrganizerItemDetail& detail,
293 const QVersitDocument& document,
294 QList<QVersitProperty>* removedProperties,
295 QList<QVersitProperty>* generatedProperties,
296 QSet<int>* processedFields)
297{
298 const QOrganizerItemVersion &version = static_cast<const QOrganizerItemVersion &>(detail);
299 QVersitProperty property = takeProperty(document, QStringLiteral("X-QTPROJECT-VERSION"), toBeRemoved: removedProperties);
300 property.setName(QStringLiteral("X-QTPROJECT-VERSION"));
301 QStringList values(QString::number(version.version()));
302 values.append(t: QString::fromLocal8Bit(str: version.extendedVersion()));
303 property.setValue(values);
304 property.setValueType(QVersitProperty::CompoundType);
305 *generatedProperties << property;
306 *processedFields << QOrganizerItemVersion::FieldVersion
307 << QOrganizerItemVersion::FieldExtendedVersion;
308}
309
310void QVersitOrganizerExporterPrivate::encodeRecurrence(
311 const QOrganizerItem& item,
312 const QOrganizerItemDetail& detail,
313 const QVersitDocument& document,
314 QList<QVersitProperty>* removedProperties,
315 QList<QVersitProperty>* generatedProperties,
316 QSet<int>* processedFields)
317{
318 const QOrganizerItemRecurrence &recurrence = static_cast<const QOrganizerItemRecurrence &>(detail);
319 QSet<QOrganizerRecurrenceRule> rrules = recurrence.recurrenceRules();
320 QSet<QOrganizerRecurrenceRule> exrules = recurrence.exceptionRules();
321 QSet<QDate> rdates = recurrence.recurrenceDates();
322 QSet<QDate> exdates = recurrence.exceptionDates();
323 if (!rrules.isEmpty()) {
324 foreach (const QOrganizerRecurrenceRule& rrule, rrules) {
325 encodeRecurRule(QStringLiteral("RRULE"), rule: rrule, generatedProperties);
326 }
327 *processedFields << QOrganizerItemRecurrence::FieldRecurrenceRules;
328 }
329 if (!exrules.isEmpty()) {
330 foreach (const QOrganizerRecurrenceRule& exrule, exrules) {
331 encodeRecurRule(QStringLiteral("EXRULE"), rule: exrule, generatedProperties);
332 }
333 *processedFields << QOrganizerItemRecurrence::FieldExceptionRules;
334 }
335 if (!rdates.isEmpty()) {
336 encodeRecurDates(QStringLiteral("RDATE"), item, dates: rdates, document, removedProperties, generatedProperties);
337 *processedFields << QOrganizerItemRecurrence::FieldRecurrenceDates;
338 }
339 if (!exdates.isEmpty()) {
340 encodeRecurDates(QStringLiteral("EXDATE"), item, dates: exdates, document, removedProperties, generatedProperties);
341 *processedFields << QOrganizerItemRecurrence::FieldExceptionDates;
342 }
343}
344
345/*! Encodes an iCalendar recurrence specification from \a rule. */
346void QVersitOrganizerExporterPrivate::encodeRecurRule(
347 const QString& propertyName,
348 const QOrganizerRecurrenceRule& rule,
349 QList<QVersitProperty>* generatedProperties)
350{
351 QVersitProperty property;
352 property.setName(propertyName);
353 QString value = QStringLiteral("FREQ=");
354 switch (rule.frequency()) {
355 case QOrganizerRecurrenceRule::Daily:
356 value.append(QStringLiteral("DAILY"));
357 break;
358 case QOrganizerRecurrenceRule::Weekly:
359 value.append(QStringLiteral("WEEKLY"));
360 break;
361 case QOrganizerRecurrenceRule::Monthly:
362 value.append(QStringLiteral("MONTHLY"));
363 break;
364 case QOrganizerRecurrenceRule::Yearly:
365 value.append(QStringLiteral("YEARLY"));
366 break;
367 case QOrganizerRecurrenceRule::Invalid:
368 default:
369 return;
370 }
371 if (rule.limitType() == QOrganizerRecurrenceRule::CountLimit) {
372 value.append(QStringLiteral(";COUNT="));
373 value.append(s: QString::number(rule.limitCount()));
374 }
375 if (rule.limitType() == QOrganizerRecurrenceRule::DateLimit) {
376 value.append(QStringLiteral(";UNTIL="));
377 value.append(s: rule.limitDate().toString(QStringLiteral("yyyyMMdd")));
378 }
379 if (rule.interval() > 1) {
380 value.append(QStringLiteral(";INTERVAL="));
381 value.append(s: QString::number(rule.interval()));
382 }
383 if (!rule.daysOfWeek().isEmpty()) {
384 value.append(QStringLiteral(";BYDAY="));
385 bool first = true;
386 QList<Qt::DayOfWeek> daysOfWeek(QList<Qt::DayOfWeek>::fromSet(set: rule.daysOfWeek()));
387 std::sort(first: daysOfWeek.begin(), last: daysOfWeek.end());
388 foreach (Qt::DayOfWeek day, daysOfWeek) {
389 if (!first)
390 value.append(QStringLiteral(","));
391 first = false;
392 value.append(s: weekString(day));
393 }
394 }
395 if (!rule.daysOfMonth().isEmpty()) {
396 value.append(QStringLiteral(";BYMONTHDAY="));
397 appendInts(str: &value, ints: rule.daysOfMonth());
398 }
399 if (!rule.daysOfYear().isEmpty()) {
400 value.append(QStringLiteral(";BYYEARDAY="));
401 appendInts(str: &value, ints: rule.daysOfYear());
402 }
403 if (!rule.weeksOfYear().isEmpty()) {
404 value.append(QStringLiteral(";BYWEEKNO="));
405 appendInts(str: &value, ints: rule.weeksOfYear());
406 }
407 if (!rule.monthsOfYear().isEmpty()) {
408 value.append(QStringLiteral(";BYMONTH="));
409 bool first = true;
410 QList<QOrganizerRecurrenceRule::Month> months(
411 QList<QOrganizerRecurrenceRule::Month>::fromSet(set: rule.monthsOfYear()));
412 std::sort(first: months.begin(), last: months.end());
413 foreach (QOrganizerRecurrenceRule::Month month, months) {
414 if (!first)
415 value.append(QStringLiteral(","));
416 first = false;
417 value.append(s: QString::number(month));
418 }
419 }
420 if (!rule.positions().isEmpty()) {
421 value.append(QStringLiteral(";BYSETPOS="));
422 appendInts(str: &value, ints: rule.positions());
423 }
424 if (rule.firstDayOfWeek() != Qt::Monday && rule.firstDayOfWeek() > 0) {
425 value.append(QStringLiteral(";WKST="));
426 value.append(s: weekString(day: rule.firstDayOfWeek()));
427 }
428 property.setValue(value);
429 property.setValueType(QVersitProperty::PreformattedType);
430 *generatedProperties << property;
431}
432
433/*! Joins \a list together with commas and appends the result to \a str.
434 */
435void QVersitOrganizerExporterPrivate::appendInts(QString* str, const QSet<int>& ints) {
436 bool first = true;
437 QList<int> intList(QList<int>::fromSet(set: ints));
438 std::sort(first: intList.begin(), last: intList.end());
439 foreach (int n, intList) {
440 if (!first)
441 str->append(QStringLiteral(","));
442 first = false;
443 str->append(s: QString::number(n));
444 }
445}
446
447/*! Converts \a day to a two-character iCalendar string */
448QString QVersitOrganizerExporterPrivate::weekString(Qt::DayOfWeek day) {
449 switch (day) {
450 case Qt::Monday:
451 return QStringLiteral("MO");
452 case Qt::Tuesday:
453 return QStringLiteral("TU");
454 case Qt::Wednesday:
455 return QStringLiteral("WE");
456 case Qt::Thursday:
457 return QStringLiteral("TH");
458 case Qt::Friday:
459 return QStringLiteral("FR");
460 case Qt::Saturday:
461 return QStringLiteral("SA");
462 case Qt::Sunday:
463 return QStringLiteral("SU");
464 default:
465 return QString();
466 }
467}
468
469void QVersitOrganizerExporterPrivate::encodeRecurDates(
470 const QString& propertyName,
471 const QOrganizerItem& item,
472 const QSet<QDate>& dates,
473 const QVersitDocument& document,
474 QList<QVersitProperty>* removedProperties,
475 QList<QVersitProperty>* generatedProperties)
476{
477 Q_UNUSED(item)
478
479 QVersitProperty property;
480 property = takeProperty(document, propertyName, toBeRemoved: removedProperties);
481 property.setName(propertyName);
482 property.insertParameter(QStringLiteral("VALUE"), QStringLiteral("DATE"));
483 QString value = property.value();
484 bool valueIsEmpty = value.isEmpty();
485
486 QList<QDate> dateList(QList<QDate>::fromSet(set: dates));
487 std::sort(first: dateList.begin(), last: dateList.end());
488 foreach (const QDate& dt, dateList) {
489 QString str;
490 if (dt.isValid()) {
491 str = dt.toString(QStringLiteral("yyyyMMdd"));
492 if (!valueIsEmpty)
493 value += QLatin1Char(',');
494 value += str;
495 valueIsEmpty = false;
496 }
497 }
498 property.setValue(value);
499 *generatedProperties << property;
500}
501
502void QVersitOrganizerExporterPrivate::encodePriority(
503 const QOrganizerItemDetail& detail,
504 const QVersitDocument& document,
505 QList<QVersitProperty>* removedProperties,
506 QList<QVersitProperty>* generatedProperties,
507 QSet<int>* processedFields)
508{
509 const QOrganizerItemPriority &priority = static_cast<const QOrganizerItemPriority &>(detail);
510 QVersitProperty property =
511 takeProperty(document, QStringLiteral("PRIORITY"), toBeRemoved: removedProperties);
512 property.setName(QStringLiteral("PRIORITY"));
513 property.setValue(QString::number(priority.priority()));
514 *generatedProperties << property;
515 *processedFields << QOrganizerItemPriority::FieldPriority;
516}
517
518void QVersitOrganizerExporterPrivate::encodeInstanceOrigin(
519 const QOrganizerItemDetail& detail,
520 const QVersitDocument& document,
521 QList<QVersitProperty>* removedProperties,
522 QList<QVersitProperty>* generatedProperties,
523 QSet<int>* processedFields)
524{
525 const QOrganizerItemParent &instanceOrigin = static_cast<const QOrganizerItemParent &>(detail);
526 QVersitProperty property =
527 takeProperty(document, QStringLiteral("RECURRENCE-ID"), toBeRemoved: removedProperties);
528 property.setName(QStringLiteral("RECURRENCE-ID"));
529 property.setValue(instanceOrigin.originalDate().toString(QStringLiteral("yyyyMMdd")));
530 *generatedProperties << property;
531 *processedFields << QOrganizerItemParent::FieldOriginalDate;
532}
533
534void QVersitOrganizerExporterPrivate::encodeTodoProgress(
535 const QOrganizerItemDetail& detail,
536 const QVersitDocument& document,
537 QList<QVersitProperty>* removedProperties,
538 QList<QVersitProperty>* generatedProperties,
539 QSet<int>* processedFields)
540{
541 const QOrganizerTodoProgress &todoProgress = static_cast<const QOrganizerTodoProgress &>(detail);
542
543 if (todoProgress.finishedDateTime().isValid()) {
544 QVersitProperty property =
545 takeProperty(document, QStringLiteral("COMPLETED"), toBeRemoved: removedProperties);
546 property.setName(QStringLiteral("COMPLETED"));
547 property.setValue(todoProgress.finishedDateTime().toString(QStringLiteral("yyyyMMddTHHmmss")));
548 *generatedProperties << property;
549 *processedFields << QOrganizerTodoProgress::FieldFinishedDateTime;
550 }
551
552 if (todoProgress.hasValue(field: QOrganizerTodoProgress::FieldPercentageComplete)) {
553 QVersitProperty property =
554 takeProperty(document, QStringLiteral("PERCENT-COMPLETE"), toBeRemoved: removedProperties);
555 property.setName(QStringLiteral("PERCENT-COMPLETE"));
556 property.setValue(QString::number(todoProgress.percentageComplete()));
557 *generatedProperties << property;
558 *processedFields << QOrganizerTodoProgress::FieldPercentageComplete;
559 }
560
561 if (todoProgress.hasValue(field: QOrganizerTodoProgress::FieldStatus)) {
562 QVersitProperty property =
563 takeProperty(document, QStringLiteral("STATUS"), toBeRemoved: removedProperties);
564 property.setName(QStringLiteral("STATUS"));
565 switch (todoProgress.status()) {
566 case QOrganizerTodoProgress::StatusNotStarted:
567 property.setValue(QStringLiteral("NEEDS-ACTION"));
568 break;
569 case QOrganizerTodoProgress::StatusInProgress:
570 property.setValue(QStringLiteral("IN-PROCESS"));
571 break;
572 case QOrganizerTodoProgress::StatusComplete:
573 property.setValue(QStringLiteral("COMPLETED"));
574 break;
575 default:
576 return;
577 }
578 *generatedProperties << property;
579 *processedFields << QOrganizerTodoProgress::FieldStatus;
580 }
581}
582
583void QVersitOrganizerExporterPrivate::encodeComment(
584 const QOrganizerItemDetail& detail,
585 QList<QVersitProperty>* generatedProperties,
586 QSet<int>* processedFields)
587{
588 const QOrganizerItemComment &comment = static_cast<const QOrganizerItemComment &>(detail);
589 QVersitProperty property;
590 property.setName(QStringLiteral("COMMENT"));
591 property.setValue(comment.comment());
592 *generatedProperties << property;
593 *processedFields << QOrganizerItemComment::FieldComment;
594}
595
596void QVersitOrganizerExporterPrivate::encodeAudibleReminder(
597 const QOrganizerItem &item,
598 const QOrganizerItemDetail &detail,
599 QList<QVersitProperty> *generatedProperties,
600 QSet<int> *processedFields)
601{
602 const QOrganizerItemAudibleReminder &audibleReminder = static_cast<const QOrganizerItemAudibleReminder &>(detail);
603 QVersitProperty property;
604 QVersitDocument valarmDocument = encodeItemReminderCommonFields(item, reminder: audibleReminder, processedFields);
605
606 const QUrl attachUrl = audibleReminder.dataUrl();
607 if (!attachUrl.isEmpty()) {
608 property.setName(QStringLiteral("ATTACH"));
609 property.setValue(attachUrl.toString());
610 valarmDocument.addProperty(property);
611 *processedFields << QOrganizerItemAudibleReminder::FieldDataUrl;
612 }
613 property.setValueType(QVersitProperty::VersitDocumentType);
614 property.setValue(QVariant::fromValue(value: valarmDocument));
615 property.setName(QStringLiteral("VALARM"));
616 *generatedProperties << property;
617}
618
619void QVersitOrganizerExporterPrivate::encodeEmailReminder(
620 const QOrganizerItem &item,
621 const QOrganizerItemDetail &detail,
622 QList<QVersitProperty> *generatedProperties,
623 QSet<int> *processedFields)
624{
625 const QOrganizerItemEmailReminder &emailReminder = static_cast<const QOrganizerItemEmailReminder &>(detail);
626 QVersitProperty property;
627 QVersitDocument valarmDocument = encodeItemReminderCommonFields(item, reminder: emailReminder, processedFields);
628
629 foreach (const QVariant &attachment, emailReminder.attachments()) {
630 property.setName(QStringLiteral("ATTACH"));
631 property.setValue(attachment);
632 valarmDocument.addProperty(property);
633 *processedFields << QOrganizerItemEmailReminder::FieldAttachments;
634 }
635
636 if (emailReminder.hasValue(field: QOrganizerItemEmailReminder::FieldRecipients)) {
637 property.setName(QStringLiteral("ATTENDEE"));
638 foreach (const QString &recipient, emailReminder.recipients()) {
639 property.setValue(recipient);
640 valarmDocument.addProperty(property);
641 }
642 *processedFields << QOrganizerItemEmailReminder::FieldRecipients;
643 }
644
645 // DESCRIPTION and SUMMARY properties are not optional,
646 // so we add them anyway even when empty
647 property.setName(QStringLiteral("DESCRIPTION"));
648 property.setValue(emailReminder.body());
649 valarmDocument.addProperty(property);
650 *processedFields << QOrganizerItemEmailReminder::FieldBody;
651 property.setName(QStringLiteral("SUMMARY"));
652 property.setValue(emailReminder.subject());
653 valarmDocument.addProperty(property);
654 *processedFields << QOrganizerItemEmailReminder::FieldSubject;
655
656 property.setValueType(QVersitProperty::VersitDocumentType);
657 property.setValue(QVariant::fromValue(value: valarmDocument));
658 property.setName(QStringLiteral("VALARM"));
659 *generatedProperties << property;
660}
661
662void QVersitOrganizerExporterPrivate::encodeVisualReminder(
663 const QOrganizerItem &item,
664 const QOrganizerItemDetail &detail,
665 QList<QVersitProperty> *generatedProperties,
666 QSet<int> *processedFields)
667{
668 const QOrganizerItemVisualReminder &visualReminder = static_cast<const QOrganizerItemVisualReminder &>(detail);
669 QVersitProperty property;
670 QVersitDocument valarmDocument = encodeItemReminderCommonFields(item, reminder: visualReminder, processedFields);
671
672 const QUrl attachUrl = visualReminder.dataUrl();
673 const QString message = visualReminder.message();
674
675 if (!attachUrl.isEmpty()) {
676 //ICAL specs do not include ATTACH property for DISPLAY VALARM components
677 //Hence, we add it here as an (optional) extended QTPROJECT-specific property
678 property.setName(QStringLiteral("X-QTPROJECT-ATTACH"));
679 property.setValue(attachUrl.toString());
680 valarmDocument.addProperty(property);
681 *processedFields << QOrganizerItemAudibleReminder::FieldDataUrl;
682 }
683
684 //DESCRIPTION property is not optional, so we add it anyway even when empty
685 property.setName(QStringLiteral("DESCRIPTION"));
686 property.setValue(message);
687 valarmDocument.addProperty(property);
688 *processedFields << QOrganizerItemVisualReminder::FieldMessage;
689 property.setValueType(QVersitProperty::VersitDocumentType);
690 property.setValue(QVariant::fromValue(value: valarmDocument));
691 property.setName(QStringLiteral("VALARM"));
692 *generatedProperties << property;
693}
694
695QVersitDocument QVersitOrganizerExporterPrivate::encodeItemReminderCommonFields(
696 const QOrganizerItem &item,
697 const QOrganizerItemReminder &reminder,
698 QSet<int> *processedFields)
699{
700 QVersitProperty property;
701 QVersitDocument valarmDocument(QVersitDocument::ICalendar20Type);
702 QList<QVersitProperty> reminderProperties;
703 valarmDocument.setComponentType(QStringLiteral("VALARM"));
704 property.setName(QStringLiteral("ACTION"));
705 switch (reminder.type()) {
706 case QOrganizerItemDetail::TypeAudibleReminder:
707 property.setValue(QStringLiteral("AUDIO"));
708 break;
709 case QOrganizerItemDetail::TypeVisualReminder:
710 property.setValue(QStringLiteral("DISPLAY"));
711 break;
712 case QOrganizerItemDetail::TypeEmailReminder:
713 property.setValue(QStringLiteral("EMAIL"));
714 break;
715 default:
716 return QVersitDocument();
717 }
718 reminderProperties << property;
719 QMap<int, QVariant>::const_iterator valueIter = reminder.values().constBegin();
720 bool triggerFound = false;
721 while (valueIter != reminder.values().constEnd()) {
722 switch (valueIter.key()) {
723 case QOrganizerItemReminder::FieldSecondsBeforeStart: {
724 property.setName(QStringLiteral("TRIGGER"));
725 property.setValue(QString(QStringLiteral("-PT%1S")).arg(
726 a: QString::number(valueIter.value().toInt())));
727 switch (item.type()) {
728 case QOrganizerItemType::TypeEvent:
729 case QOrganizerItemType::TypeEventOccurrence:
730 property.insertParameter(QStringLiteral("RELATED"), QStringLiteral("START"));
731 break;
732 case QOrganizerItemType::TypeTodo:
733 case QOrganizerItemType::TypeTodoOccurrence:
734 property.insertParameter(QStringLiteral("RELATED"), QStringLiteral("END"));
735 break;
736 default:
737 break;
738 }
739 triggerFound = true;
740 break;
741 }
742 case QOrganizerItemReminder::FieldRepetitionCount: {
743 property.setName(QStringLiteral("REPEAT"));
744 property.setValue(valueIter.value().toString());
745 break;
746 }
747 case QOrganizerItemReminder::FieldRepetitionDelay: {
748 property.setName(QStringLiteral("DURATION"));
749 property.setValue(QString(QStringLiteral("PT%1S")).arg(
750 a: QString::number(valueIter.value().toInt())));
751 break;
752 }
753 default:
754 valueIter ++;
755 property.clear();
756 continue;
757 break;
758 }
759 reminderProperties << property;
760 *processedFields << valueIter.key();
761 property.clear();
762 valueIter ++;
763 }
764 if (!triggerFound) {
765 property.setName(QStringLiteral("TRIGGER"));
766 property.setValue(QString(QStringLiteral("-PT%1S")).arg(
767 a: QString::number(0)));
768 reminderProperties << property;
769 }
770 valarmDocument.setProperties(reminderProperties);
771 return valarmDocument;
772}
773
774void QVersitOrganizerExporterPrivate::encodeExtendedDetail(
775 const QOrganizerItemDetail &detail,
776 QList<QVersitProperty> *generatedProperties,
777 QSet<int> *processedFields)
778{
779 const QOrganizerItemExtendedDetail &extendedDetail = static_cast<const QOrganizerItemExtendedDetail &>(detail);
780 QVersitProperty property;
781 property.setName(QStringLiteral("X-QTPROJECT-EXTENDED-DETAIL"));
782 QString dataAsJson;
783 if (VersitUtils::convertToJson(data: extendedDetail.data(), json: &dataAsJson)) {
784 property.setValue(QStringList() << extendedDetail.name() << dataAsJson);
785 } else {
786 qWarning() << Q_FUNC_INFO << "Failed to export an extended detail";
787 return;
788 }
789 property.setValueType(QVersitProperty::CompoundType);
790 *generatedProperties << property;
791 *processedFields << QOrganizerItemExtendedDetail::FieldName
792 << QOrganizerItemExtendedDetail::FieldData;
793}
794
795void QVersitOrganizerExporterPrivate::encodeSimpleProperty(
796 const QOrganizerItemDetail& detail,
797 const QVersitDocument& document,
798 QList<QVersitProperty>* removedProperties,
799 QList<QVersitProperty>* generatedProperties,
800 QSet<int>* processedFields)
801{
802 QPair<int, QString> fieldPropertyMap = mPropertyMappings[detail.type()];
803 const int& fieldName = fieldPropertyMap.first;
804 const QString& propertyName = fieldPropertyMap.second;
805 QVersitProperty property =
806 takeProperty(document, propertyName, toBeRemoved: removedProperties);
807 property.setName(propertyName);
808 property.setValue(detail.value(field: fieldName));
809 *generatedProperties << property;
810 *processedFields << fieldName;
811}
812
813/*! Formats \a dateTime in ISO 8601 basic format */
814QString QVersitOrganizerExporterPrivate::encodeDateTime(const QDateTime& dateTime)
815{
816 if (dateTime.timeSpec() == Qt::UTC)
817 return dateTime.toString(QStringLiteral("yyyyMMddTHHmmssZ"));
818 else
819 return dateTime.toString(QStringLiteral("yyyyMMddTHHmmss"));
820}
821
822/*!
823 * Returns true if and only if \a document has both the UID and the RECURRENCE-ID properties
824 */
825bool QVersitOrganizerExporterPrivate::documentContainsUidAndRecurrenceId(const QVersitDocument &document)
826{
827 bool hasUid = false;
828 bool hasRecurId = false;
829 foreach (const QVersitProperty& property, document.properties()) {
830 const QString& name = property.name();
831 if (name == QStringLiteral("UID")) {
832 if (hasRecurId)
833 return true;
834 hasUid = true;
835 } else if (name == QStringLiteral("RECURRENCE-ID")) {
836 if (hasUid)
837 return true;
838 hasRecurId = true;
839 }
840 }
841 return false;
842}
843
844/*!
845 * Finds a property in the \a document with the given \a propertyName, adds it to \a toBeRemoved,
846 * and returns it.
847 */
848QVersitProperty QVersitOrganizerExporterPrivate::takeProperty(
849 const QVersitDocument& document,
850 const QString& propertyName,
851 QList<QVersitProperty>* toBeRemoved) {
852 foreach (const QVersitProperty& currentProperty, document.properties()) {
853 if (currentProperty.name() == propertyName) {
854 *toBeRemoved << currentProperty;
855 return currentProperty;
856 }
857 }
858 return QVersitProperty();
859}
860
861QT_END_NAMESPACE_VERSITORGANIZER
862

source code of qtpim/src/versitorganizer/qversitorganizerexporter_p.cpp