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 | |
48 | QTORGANIZER_USE_NAMESPACE |
49 | QTVERSIT_USE_NAMESPACE |
50 | |
51 | QT_BEGIN_NAMESPACE_VERSITORGANIZER |
52 | |
53 | QVersitOrganizerExporterPrivate::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 | |
71 | QVersitOrganizerExporterPrivate::~QVersitOrganizerExporterPrivate() |
72 | { |
73 | foreach (QVersitOrganizerHandler* pluginHandler, mPluginDetailHandlers) { |
74 | delete pluginHandler; |
75 | } |
76 | } |
77 | |
78 | bool 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 | |
124 | void 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 | |
190 | void 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 | |
223 | void 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 | |
255 | void 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 | |
270 | void 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 ×tamp = 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 | |
291 | void 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 | |
310 | void 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. */ |
346 | void 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 | */ |
435 | void 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 */ |
448 | QString 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 | |
469 | void 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 | |
502 | void 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 | |
518 | void 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 | |
534 | void 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 | |
583 | void QVersitOrganizerExporterPrivate::( |
584 | const QOrganizerItemDetail& detail, |
585 | QList<QVersitProperty>* generatedProperties, |
586 | QSet<int>* processedFields) |
587 | { |
588 | const QOrganizerItemComment & = 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 | |
596 | void 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 | |
619 | void 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 | |
662 | void 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 | |
695 | QVersitDocument 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 | |
774 | void 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 | |
795 | void 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 */ |
814 | QString 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 | */ |
825 | bool 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 | */ |
848 | QVersitProperty 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 | |
861 | QT_END_NAMESPACE_VERSITORGANIZER |
862 | |