[prev in list] [next in list] [prev in thread] [next in thread]
List: kde-commits
Subject: [kcalutils] /: Port part of KCalUtils to Grantlee templates
From: Dan_Vrátil <dvratil () redhat ! com>
Date: 2015-10-06 10:36:09
Message-ID: E1ZjPbN-0003Cm-In () scm ! kde ! org
[Download RAW message or body]
Git commit c0c318942bc70714091b59fa078ee62332e8a60f by Dan Vrátil.
Committed on 06/10/2015 at 10:34.
Pushed by dvratil into branch 'master'.
Port part of KCalUtils to Grantlee templates
Instead of assembling the HTML by hand in C++ code, we use Grantlee templates
for presentation, and only prepare the data in C++ code. This commit only ports
the event, tasks, journal and freebusy views to Grantlee templates. The ITIP
HTML code is still assembled in C++, I want to wait with that until Sandro is
finished with his work on ObjectTreeParser refactoring in kdepim.git to see what
the requierements will be.
This code also includes a bunch of useful Grantlee plugins and tools that we
might want to eventually move to some other repository so it can be used
from other projects as well.
REVIEW: 125331
M +2 -0 CMakeLists.txt
A +6 -0 Messages.sh
M +27 -3 autotests/CMakeLists.txt
A +4 -0 autotests/data/broken-template.html
A +74 -0 autotests/data/event-1.html
A +22 -0 autotests/data/event-1.ical
A +116 -0 autotests/data/event-2.html
A +102 -0 autotests/data/event-2.ical
A +65 -0 autotests/data/event-allday-multiday.html
A +19 -0 autotests/data/event-allday-multiday.ical
A +65 -0 autotests/data/event-allday.html
A +19 -0 autotests/data/event-allday.ical
A +73 -0 autotests/data/event-exception-single.html
A +19 -0 autotests/data/event-exception-single.ical
A +73 -0 autotests/data/event-exception-thisandfuture.html
A +19 -0 autotests/data/event-exception-thisandfuture.ical
A +74 -0 autotests/data/event-multiday.html
A +20 -0 autotests/data/event-multiday.ical
A +73 -0 autotests/data/event-recurrence-single.out.html
A +73 -0 autotests/data/event-recurrence-thisandfuture.out.html
A +31 -0 autotests/data/freebusy-1.html
A +17 -0 autotests/data/freebusy-1.ical
A +58 -0 autotests/data/journal-1.html
A +82 -0 autotests/data/journal-1.ical
A +113 -0 autotests/data/todo-1.html
A +30 -0 autotests/data/todo-1.ical
A +5 -0 autotests/test_config.h.cmake
M +234 -0 autotests/testincidenceformatter.cpp
M +26 -0 autotests/testincidenceformatter.h
A +54 -0 scripts/extract_strings_ki18n.py
A +365 -0 scripts/grantlee_strings_extractor.py
M +5 -0 src/CMakeLists.txt
D +0 -3 src/Messages.sh
A +19 -0 src/grantlee_plugin/CMakeLists.txt
A +124 -0 src/grantlee_plugin/datetimefilters.cpp [License: LGPL (v2.1+)]
A +58 -0 src/grantlee_plugin/datetimefilters.h [License: LGPL (v2.1+)]
A +144 -0 src/grantlee_plugin/icon.cpp [License: LGPL (v2.1+)]
A +79 -0 src/grantlee_plugin/icon.h [License: LGPL (v2.1+)]
A +54 -0 src/grantlee_plugin/kcalendargrantleeplugin.cpp [License: LGPL \
(v2.1+)] A +40 -0 src/grantlee_plugin/kcalendargrantleeplugin.h [License: \
LGPL (v2.1+)] A +112 -0 src/grantleeki18nlocalizer.cpp [License: LGPL \
(v2.1+)] A +54 -0 src/grantleeki18nlocalizer_p.h [License: LGPL (v2.1+)]
A +126 -0 src/grantleetemplatemanager.cpp [License: LGPL (v2.1+)]
A +63 -0 src/grantleetemplatemanager_p.h [License: LGPL (v2.1+)]
M +214 -488 src/incidenceformatter.cpp
A +10 -0 templates/CMakeLists.txt
A +28 -0 templates/attendee_row.html
A +198 -0 templates/event.html
A +28 -0 templates/freebusy.html
A +24 -0 templates/incidence_header.html
A +48 -0 templates/journal.html
A +22 -0 templates/template_base.html
A +203 -0 templates/todo.html
http://commits.kde.org/kcalutils/c0c318942bc70714091b59fa078ee62332e8a60f
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d145b6a..bd4109e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -32,6 +32,7 @@ find_package(KF5Config ${KF5_VERSION} CONFIG REQUIRED)
find_package(KF5I18n ${KF5_VERSION} CONFIG REQUIRED)
find_package(KF5KDELibs4Support ${KF5_VERSION} CONFIG REQUIRED)
find_package(KF5Codecs ${KF5_VERSION} CONFIG REQUIRED)
+find_package(Grantlee5 "5.0" CONFIG REQUIRED)
find_package(KF5CalendarCore ${CALENDARCORE_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5IdentityManagement ${IDENTITYMANAGER_LIB_VERSION} CONFIG REQUIRED)
@@ -41,6 +42,7 @@ add_definitions("-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII")
########### Targets ###########
add_subdirectory(src)
+add_subdirectory(templates)
if(BUILD_TESTING)
add_subdirectory(autotests)
diff --git a/Messages.sh b/Messages.sh
new file mode 100644
index 0000000..8864580
--- /dev/null
+++ b/Messages.sh
@@ -0,0 +1,6 @@
+#! /bin/sh
+scripts/extract_strings_ki18n.py `find templates -name \*.html` >> html.cpp
+$EXTRACTRC *.kcfg *.ui >> rc.cpp
+$XGETTEXT rc.cpp html.cpp src/*.cpp src/*.h -o $podir/libkcalutils5.pot
+rm -f rc.cpp html.cpp
+
diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
index a7ce2f1..6fa5c28 100644
--- a/autotests/CMakeLists.txt
+++ b/autotests/CMakeLists.txt
@@ -5,6 +5,30 @@ include(ECMAddTests)
set(QT_REQUIRED_VERSION "5.2.0")
find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED COMPONENTS Test)
-ecm_add_tests(testdndfactory.cpp testincidenceformatter.cpp teststringify.cpp
- NAME_PREFIX "kcalutils-"
- LINK_LIBRARIES KF5CalendarUtils Qt5::Test)
+set(TEST_DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/data")
+set(TEST_TEMPLATE_PATH "${CMAKE_SOURCE_DIR}/templates")
+set(TEST_PLUGIN_PATH "${CMAKE_BINARY_DIR}/grantlee")
+configure_file(test_config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/test_config.h @ONLY)
+
+include_directories(${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}/src)
+
+ecm_add_tests(testdndfactory.cpp teststringify.cpp
+ NAME_PREFIX "kcalutils-"
+ LINK_LIBRARIES KF5CalendarUtils Qt5::Test
+)
+
+
+ecm_add_test(testincidenceformatter.cpp
+ ${CMAKE_SOURCE_DIR}/src/incidenceformatter.cpp
+ ${CMAKE_SOURCE_DIR}/src/grantleetemplatemanager.cpp
+ ${CMAKE_SOURCE_DIR}/src/grantleeki18nlocalizer.cpp
+ ${CMAKE_SOURCE_DIR}/src/stringify.cpp
+ ${CMAKE_BINARY_DIR}/src/kcalutils_debug.cpp
+ TEST_NAME "testincidenceformatter"
+ NAME_PREFIX "kcalutils-"
+ LINK_LIBRARIES Qt5::Core Qt5::Test KF5::CalendarCore KF5::I18n \
KF5::IdentityManagement Grantlee5::Templates +
+)
+
+# Make sure that dates are formatted in C locale
+set_tests_properties(kcalutils-testincidenceformatter PROPERTIES ENVIRONMENT \
"LC_ALL=C")
diff --git a/autotests/data/broken-template.html \
b/autotests/data/broken-template.html new file mode 100644
index 0000000..be733eb
--- /dev/null
+++ b/autotests/data/broken-template.html
@@ -0,0 +1,4 @@
+<b>This is a template, and it's broken</b>
+
+{% if shouldBreak %}
+ <p>Why, you ask?</p>
diff --git a/autotests/data/event-1.html b/autotests/data/event-1.html
new file mode 100644
index 0000000..70fb30e
--- /dev/null
+++ b/autotests/data/event-1.html
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" \
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html \
xmlns="http://www.w3.org/1999/xhtml"> + <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF8" />
+ <title></title>
+ <style></style>
+ </head>
+ <body>
+ <style type="text/css">
+th {
+ text-align: left;
+ width: 25%;
+ white-space: nowrap;
+ font-weight: bold;
+}
+td {
+ padding-left: 5px;
+}
+
+</style>
+ <table class="header">
+ <tr>
+ <td>
+ <img src="file:view-calendar-day.svg" align="top" height="16" width="16" \
alt="Event" title="Event" /> + <img src="file:edit-redo.svg" align="top" \
height="16" width="16" alt="Recurring incidence" title="Recurring incidence" /> + \
</td> + <td>
+ <b>
+ <u>20. Mai 2005, 19-20 Uhr, alle 3 Monate am -2. Fr, 17 mal</u>
+ </b>
+ </td>
+ </tr>
+ </table>
+ <table>
+<!-- Calendar name -->
+<!-- Location -->
+<!-- Start/end -->
+ <tr>
+ <th>Date:</th>
+ <td>Friday, 20 May 2005</td>
+ </tr>
+ <tr>
+ <th>Time:</th>
+ <td>17:00:00</td>
+ </tr>
+<!-- Duration -->
+ <tr>
+ <th>Duration:</th>
+ <td>1 hour </td>
+ </tr>
+<!-- Recurrence -->
+ <tr>
+ <th>Recurrence:</th>
+ <td>Recurs every 3 months on the 2nd Last Friday until 2009-05-22 17:00 (17 \
occurrences)</td> + </tr>
+<!-- Birthday -->
+<!-- Anniversary -->
+<!-- Description -->
+<!-- Alarms -->
+<!-- Organizer -->
+<!-- Attendees - Chair -->
+<!-- Attendees - Required Participants -->
+<!-- Attendees - Optional Participants -->
+<!-- Attendees - Observers -->
+<!-- Categories -->
+<!-- Attachments -->
+ </table>
+<!-- Creation -->
+ <p>
+ <em>Creation date: Friday, 20 May 2005 10:58:56 UTC</em>
+ </p>
+ </body>
+</html>
diff --git a/autotests/data/event-1.ical b/autotests/data/event-1.ical
new file mode 100644
index 0000000..4ba587c
--- /dev/null
+++ b/autotests/data/event-1.ical
@@ -0,0 +1,22 @@
+BEGIN:VCALENDAR
+PRODID:-//K Desktop Environment//NONSGML KOrganizer 3.4//EN
+VERSION:2.0
+X-LibKCal-Testsuite-OutTZ:Europe/Vienna
+BEGIN:VEVENT
+DTSTAMP:20050520T105856Z
+ORGANIZER;CN=Reinhold Kainhofer:MAILTO:reinhold@kainhofer.com
+CREATED:20050520T105219Z
+UID:KOrganizer-45214176.303
+SEQUENCE:2
+LAST-MODIFIED:20050520T105815Z
+SUMMARY:20. Mai 2005\, 19-20 Uhr\, alle 3 Monate am -2. Fr\, 17 mal
+CLASS:PUBLIC
+PRIORITY:5
+RRULE:FREQ=MONTHLY;COUNT=17;INTERVAL=3;BYDAY=-2FR
+DTSTART:20050520T170000Z
+DTEND:20050520T180000Z
+TRANSP:OPAQUE
+END:VEVENT
+
+END:VCALENDAR
+
diff --git a/autotests/data/event-2.html b/autotests/data/event-2.html
new file mode 100644
index 0000000..500b38a
--- /dev/null
+++ b/autotests/data/event-2.html
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" \
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html \
xmlns="http://www.w3.org/1999/xhtml"> + <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF8" />
+ <title></title>
+ <style></style>
+ </head>
+ <body>
+ <style type="text/css">
+th {
+ text-align: left;
+ width: 25%;
+ white-space: nowrap;
+ font-weight: bold;
+}
+td {
+ padding-left: 5px;
+}
+
+</style>
+ <table class="header">
+ <tr>
+ <td>
+ <img src="file:view-calendar-day.svg" align="top" height="16" width="16" \
alt="Event" title="Event" /> + <img \
src="file:preferences-desktop-notification-bell.svg" align="top" height="16" \
width="16" alt="Incidence with a reminder" title="Incidence with a reminder" /> + \
<img src="file:edit-redo.svg" align="top" height="16" width="16" alt="Recurring \
incidence" title="Recurring incidence" /> + </td>
+ <td>
+ <b>
+ <u>Plánovací meeting</u>
+ </b>
+ </td>
+ </tr>
+ </table>
+ <table>
+<!-- Calendar name -->
+<!-- Location -->
+ <tr>
+ <th>Location:</th>
+ <td>Zasedačka číslo 3</td>
+ </tr>
+<!-- Start/end -->
+ <tr>
+ <th>Date:</th>
+ <td>Wednesday, 30 September 2015</td>
+ </tr>
+ <tr>
+ <th>Time:</th>
+ <td>03:00:00</td>
+ </tr>
+<!-- Duration -->
+ <tr>
+ <th>Duration:</th>
+ <td>1 hour 30 minutes</td>
+ </tr>
+<!-- Recurrence -->
+ <tr>
+ <th>Recurrence:</th>
+ <td>Recurs every 2 weeks on Wed (excluding 1 day)</td>
+ </tr>
+<!-- Birthday -->
+<!-- Anniversary -->
+<!-- Description -->
+<!-- Alarms -->
+ <tr>
+ <th>Reminder:</th>
+ <td>15 minutes before the start</td>
+ </tr>
+<!-- Organizer -->
+ <tr>
+ <th>Organizer:</th>
+ <td><img src="file:meeting-organizer.png" align="top" height="16" width="16" \
alt="meeting-organizer" title="" /> +Daniel Vrátil
+ <a href="mailto:Daniel Vrátil %3Cdvratil@kde.org%3E"><img \
src="file:mail-message-new.svg" align="top" height="16" width="16" alt="Send email" \
title="Send email" /></a> +
+ </td>
+ </tr>
+<!-- Attendees - Chair -->
+<!-- Attendees - Required Participants -->
+ <tr>
+ <th>Required Participants:</th>
+ <td>
+ <img src="file:dialog-ok-apply.svg" align="top" height="16" width="16" \
alt="Accepted" title="Accepted" /> + <a href="uid:62645952">Volker Krause
+ </a>
+ <a href="mailto:Volker Krause %3Cvkrause@kde.org%3E">
+ <img src="file:mail-message-new.svg" align="top" height="16" width="16" \
alt="Send email" title="Send email" /> + </a>
+ <br />
+ <img src="file:help-about.svg" align="top" height="16" width="16" \
alt="Needs action" title="Needs action" /> + <a \
href="uid:35926528">Christian Mollekopf + </a>
+ <a href="mailto:Christian Mollekopf %3Ccmollekopf@gmail.com%3E">
+ <img src="file:mail-message-new.svg" align="top" height="16" width="16" \
alt="Send email" title="Send email" /> + </a>
+ <br />
+ <img src="file:mail-forward.svg" align="top" height="16" width="16" \
alt="Delegated" title="Delegated" /> + <a href="uid:64325328">Sandro Knauß
+ </a>
+ <a href="mailto:Sandro Knauß %3Csknauss@kde.org%3E">
+ <img src="file:mail-message-new.svg" align="top" height="16" width="16" \
alt="Send email" title="Send email" /> + </a>
+ </td>
+ </tr>
+<!-- Attendees - Optional Participants -->
+<!-- Attendees - Observers -->
+<!-- Categories -->
+<!-- Attachments -->
+ </table>
+<!-- Creation -->
+ <p>
+ <em>Creation date: Wednesday, 30 September 2015 09:03:51 UTC</em>
+ </p>
+ </body>
+</html>
diff --git a/autotests/data/event-2.ical b/autotests/data/event-2.ical
new file mode 100644
index 0000000..62971ee
--- /dev/null
+++ b/autotests/data/event-2.ical
@@ -0,0 +1,102 @@
+BEGIN:VCALENDAR
+PRODID:-//K Desktop Environment//NONSGML KOrganizer 5.0.44 pre//EN
+VERSION:2.0
+X-KDE-ICAL-IMPLEMENTATION-VERSION:1.0
+METHOD:REQUEST
+BEGIN:VTIMEZONE
+TZID:Europe/Prague
+BEGIN:STANDARD
+TZNAME:CET
+TZOFFSETFROM:+005744
+TZOFFSETTO:+0100
+DTSTART:19011213T214336
+RDATE;VALUE=DATE-TIME:19011213T214336
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:CEST
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+DTSTART:19810329T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+TZNAME:CEST
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+DTSTART:19160430T230000
+RDATE;VALUE=DATE-TIME:19160430T230000
+RDATE;VALUE=DATE-TIME:19170416T020000
+RDATE;VALUE=DATE-TIME:19180415T020000
+RDATE;VALUE=DATE-TIME:19400401T020000
+RDATE;VALUE=DATE-TIME:19430329T020000
+RDATE;VALUE=DATE-TIME:19440403T020000
+RDATE;VALUE=DATE-TIME:19450408T020000
+RDATE;VALUE=DATE-TIME:19460506T020000
+RDATE;VALUE=DATE-TIME:19470420T020000
+RDATE;VALUE=DATE-TIME:19480418T020000
+RDATE;VALUE=DATE-TIME:19490409T020000
+RDATE;VALUE=DATE-TIME:19790401T020000
+RDATE;VALUE=DATE-TIME:19800406T020000
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:CET
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+DTSTART:19790930T030000
+RRULE:FREQ=YEARLY;COUNT=17;BYDAY=-1SU;BYMONTH=9
+END:STANDARD
+BEGIN:STANDARD
+TZNAME:CET
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+DTSTART:19961027T030000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+END:STANDARD
+BEGIN:STANDARD
+TZNAME:CET
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+DTSTART:19161001T010000
+RDATE;VALUE=DATE-TIME:19161001T010000
+RDATE;VALUE=DATE-TIME:19170917T030000
+RDATE;VALUE=DATE-TIME:19180916T030000
+RDATE;VALUE=DATE-TIME:19421102T030000
+RDATE;VALUE=DATE-TIME:19431004T030000
+RDATE;VALUE=DATE-TIME:19440917T030000
+RDATE;VALUE=DATE-TIME:19451118T030000
+RDATE;VALUE=DATE-TIME:19461006T030000
+RDATE;VALUE=DATE-TIME:19471005T030000
+RDATE;VALUE=DATE-TIME:19481003T030000
+RDATE;VALUE=DATE-TIME:19491002T030000
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+ORGANIZER;CN="Daniel Vrátil":MAILTO:dvratil@kde.org
+DTSTAMP:20150930T090704Z
+ATTENDEE;CN="Volker Krause";RSVP=FALSE;PARTSTAT=ACCEPTED;
+ ROLE=REQ-PARTICIPANT;CUTYPE=INDIVIDUAL;X-UID=62645952:mailto:
+ vkrause@kde.org
+ATTENDEE;CN="Christian Mollekopf";RSVP=FALSE;PARTSTAT=NEEDS-ACTION;
+ ROLE=REQ-PARTICIPANT;CUTYPE=INDIVIDUAL;X-UID=35926528:mailto:
+ cmollekopf@gmail.com
+ATTENDEE;CN="Sandro Knauß";RSVP=FALSE;PARTSTAT=DELEGATED;
+ ROLE=REQ-PARTICIPANT;CUTYPE=INDIVIDUAL;X-UID=64325328:mailto:
+ sknauss@kde.org
+CREATED:20150930T090351Z
+UID:15ea20f1-33b4-4d4a-a746-482f09cb5287
+LAST-MODIFIED:20150930T090629Z
+SUMMARY:Plánovací meeting
+LOCATION:Zasedačka číslo 3
+RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=WE
+EXDATE;VALUE=DATE:20151118
+DTSTART;TZID=Europe/Prague:20150930T030000
+DTEND;TZID=Europe/Prague:20150930T043000
+TRANSP:OPAQUE
+BEGIN:VALARM
+DESCRIPTION:
+ACTION:DISPLAY
+TRIGGER;VALUE=DURATION:-PT15M
+X-KDE-KCALCORE-ENABLED:TRUE
+END:VALARM
+END:VEVENT
+END:VCALENDAR
diff --git a/autotests/data/event-allday-multiday.html \
b/autotests/data/event-allday-multiday.html new file mode 100644
index 0000000..6ca77a4
--- /dev/null
+++ b/autotests/data/event-allday-multiday.html
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" \
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html \
xmlns="http://www.w3.org/1999/xhtml"> + <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF8" />
+ <title></title>
+ <style></style>
+ </head>
+ <body>
+ <style type="text/css">
+th {
+ text-align: left;
+ width: 25%;
+ white-space: nowrap;
+ font-weight: bold;
+}
+td {
+ padding-left: 5px;
+}
+
+</style>
+ <table class="header">
+ <tr>
+ <td>
+ <img src="file:view-calendar-day.svg" align="top" height="16" width="16" \
alt="Event" title="Event" /> + </td>
+ <td>
+ <b>
+ <u>20. May 2005, allday, multiday</u>
+ </b>
+ </td>
+ </tr>
+ </table>
+ <table>
+<!-- Calendar name -->
+<!-- Location -->
+<!-- Start/end -->
+ <tr>
+ <th>Date:</th>
+ <td>Friday, 20 May 2005 - Saturday, 21 May 2005</td>
+ </tr>
+<!-- Duration -->
+ <tr>
+ <th>Duration:</th>
+ <td>2 days</td>
+ </tr>
+<!-- Recurrence -->
+<!-- Birthday -->
+<!-- Anniversary -->
+<!-- Description -->
+<!-- Alarms -->
+<!-- Organizer -->
+<!-- Attendees - Chair -->
+<!-- Attendees - Required Participants -->
+<!-- Attendees - Optional Participants -->
+<!-- Attendees - Observers -->
+<!-- Categories -->
+<!-- Attachments -->
+ </table>
+<!-- Creation -->
+ <p>
+ <em>Creation date: Friday, 20 May 2005 10:58:56 UTC</em>
+ </p>
+ </body>
+</html>
diff --git a/autotests/data/event-allday-multiday.ical \
b/autotests/data/event-allday-multiday.ical new file mode 100644
index 0000000..bbc9864
--- /dev/null
+++ b/autotests/data/event-allday-multiday.ical
@@ -0,0 +1,19 @@
+BEGIN:VCALENDAR
+PRODID:-//K Desktop Environment//NONSGML KOrganizer 3.4//EN
+VERSION:2.0
+X-LibKCal-Testsuite-OutTZ:Europe/Vienna
+BEGIN:VEVENT
+DTSTAMP:20050520T105856Z
+ORGANIZER;CN=Reinhold Kainhofer:MAILTO:reinhold@kainhofer.com
+CREATED:20050520T105219Z
+UID:KOrganizer-45214176.303
+SEQUENCE:2
+LAST-MODIFIED:20050520T105815Z
+SUMMARY:20. May 2005\, allday\, multiday
+CLASS:PUBLIC
+PRIORITY:5
+DTSTART:20050520
+DTEND:20050522
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
diff --git a/autotests/data/event-allday.html b/autotests/data/event-allday.html
new file mode 100644
index 0000000..47012c6
--- /dev/null
+++ b/autotests/data/event-allday.html
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" \
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html \
xmlns="http://www.w3.org/1999/xhtml"> + <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF8" />
+ <title></title>
+ <style></style>
+ </head>
+ <body>
+ <style type="text/css">
+th {
+ text-align: left;
+ width: 25%;
+ white-space: nowrap;
+ font-weight: bold;
+}
+td {
+ padding-left: 5px;
+}
+
+</style>
+ <table class="header">
+ <tr>
+ <td>
+ <img src="file:view-calendar-day.svg" align="top" height="16" width="16" \
alt="Event" title="Event" /> + </td>
+ <td>
+ <b>
+ <u>20. May 2005, allday</u>
+ </b>
+ </td>
+ </tr>
+ </table>
+ <table>
+<!-- Calendar name -->
+<!-- Location -->
+<!-- Start/end -->
+ <tr>
+ <th>Date:</th>
+ <td>Friday, 20 May 2005</td>
+ </tr>
+<!-- Duration -->
+ <tr>
+ <th>Duration:</th>
+ <td>1 day</td>
+ </tr>
+<!-- Recurrence -->
+<!-- Birthday -->
+<!-- Anniversary -->
+<!-- Description -->
+<!-- Alarms -->
+<!-- Organizer -->
+<!-- Attendees - Chair -->
+<!-- Attendees - Required Participants -->
+<!-- Attendees - Optional Participants -->
+<!-- Attendees - Observers -->
+<!-- Categories -->
+<!-- Attachments -->
+ </table>
+<!-- Creation -->
+ <p>
+ <em>Creation date: Friday, 20 May 2005 10:58:56 UTC</em>
+ </p>
+ </body>
+</html>
diff --git a/autotests/data/event-allday.ical b/autotests/data/event-allday.ical
new file mode 100644
index 0000000..173917a
--- /dev/null
+++ b/autotests/data/event-allday.ical
@@ -0,0 +1,19 @@
+BEGIN:VCALENDAR
+PRODID:-//K Desktop Environment//NONSGML KOrganizer 3.4//EN
+VERSION:2.0
+X-LibKCal-Testsuite-OutTZ:Europe/Vienna
+BEGIN:VEVENT
+DTSTAMP:20050520T105856Z
+ORGANIZER;CN=Reinhold Kainhofer:MAILTO:reinhold@kainhofer.com
+CREATED:20050520T105219Z
+UID:KOrganizer-45214176.303
+SEQUENCE:2
+LAST-MODIFIED:20050520T105815Z
+SUMMARY:20. May 2005\, allday
+CLASS:PUBLIC
+PRIORITY:5
+DTSTART:20050520
+DTEND:20050520
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
diff --git a/autotests/data/event-exception-single.html \
b/autotests/data/event-exception-single.html new file mode 100644
index 0000000..f1e7932
--- /dev/null
+++ b/autotests/data/event-exception-single.html
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" \
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html \
xmlns="http://www.w3.org/1999/xhtml"> + <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF8" />
+ <title></title>
+ <style></style>
+ </head>
+ <body>
+ <style type="text/css">
+th {
+ text-align: left;
+ width: 25%;
+ white-space: nowrap;
+ font-weight: bold;
+}
+td {
+ padding-left: 5px;
+}
+
+</style>
+ <table class="header">
+ <tr>
+ <td>
+ <img src="file:view-calendar-day.svg" align="top" height="16" width="16" \
alt="Event" title="Event" /> + </td>
+ <td>
+ <b>
+ <u>summaryException</u>
+ </b>
+ </td>
+ </tr>
+ </table>
+ <table>
+<!-- Calendar name -->
+<!-- Location -->
+<!-- Start/end -->
+ <tr>
+ <th>Date:</th>
+ <td>Thursday, 19 May 2005</td>
+ </tr>
+ <tr>
+ <th>Time:</th>
+ <td>09:45:00</td>
+ </tr>
+<!-- Duration -->
+ <tr>
+ <th>Duration:</th>
+ <td>3 hours 30 minutes</td>
+ </tr>
+<!-- Recurrence -->
+ <tr>
+ <th>Recurrence:</th>
+ <td>Exception</td>
+ </tr>
+<!-- Birthday -->
+<!-- Anniversary -->
+<!-- Description -->
+<!-- Alarms -->
+<!-- Organizer -->
+<!-- Attendees - Chair -->
+<!-- Attendees - Required Participants -->
+<!-- Attendees - Optional Participants -->
+<!-- Attendees - Observers -->
+<!-- Categories -->
+<!-- Attachments -->
+ </table>
+<!-- Creation -->
+ <p>
+ <em>Creation date: Sunday, 22 May 2005 20:12:07 UTC</em>
+ </p>
+ </body>
+</html>
diff --git a/autotests/data/event-exception-single.ical \
b/autotests/data/event-exception-single.ical new file mode 100644
index 0000000..a1c2910
--- /dev/null
+++ b/autotests/data/event-exception-single.ical
@@ -0,0 +1,19 @@
+BEGIN:VCALENDAR
+PRODID:-//K Desktop Environment//NONSGML KOrganizer 3.4//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20050522T201207Z
+ORGANIZER;CN=Reinhold Kainhofer:MAILTO:reinhold@kainhofer.com
+CREATED:20050522T201119Z
+UID:KOrganizer-557711714.436
+SEQUENCE:0
+LAST-MODIFIED:20050522T201119Z
+SUMMARY:summaryException
+CLASS:PUBLIC
+PRIORITY:5
+RECURRENCE-ID:20050519T084500Z
+DTSTART:20050519T094500Z
+DTEND:20050519T131500Z
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
diff --git a/autotests/data/event-exception-thisandfuture.html \
b/autotests/data/event-exception-thisandfuture.html new file mode 100644
index 0000000..f1e7932
--- /dev/null
+++ b/autotests/data/event-exception-thisandfuture.html
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" \
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html \
xmlns="http://www.w3.org/1999/xhtml"> + <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF8" />
+ <title></title>
+ <style></style>
+ </head>
+ <body>
+ <style type="text/css">
+th {
+ text-align: left;
+ width: 25%;
+ white-space: nowrap;
+ font-weight: bold;
+}
+td {
+ padding-left: 5px;
+}
+
+</style>
+ <table class="header">
+ <tr>
+ <td>
+ <img src="file:view-calendar-day.svg" align="top" height="16" width="16" \
alt="Event" title="Event" /> + </td>
+ <td>
+ <b>
+ <u>summaryException</u>
+ </b>
+ </td>
+ </tr>
+ </table>
+ <table>
+<!-- Calendar name -->
+<!-- Location -->
+<!-- Start/end -->
+ <tr>
+ <th>Date:</th>
+ <td>Thursday, 19 May 2005</td>
+ </tr>
+ <tr>
+ <th>Time:</th>
+ <td>09:45:00</td>
+ </tr>
+<!-- Duration -->
+ <tr>
+ <th>Duration:</th>
+ <td>3 hours 30 minutes</td>
+ </tr>
+<!-- Recurrence -->
+ <tr>
+ <th>Recurrence:</th>
+ <td>Exception</td>
+ </tr>
+<!-- Birthday -->
+<!-- Anniversary -->
+<!-- Description -->
+<!-- Alarms -->
+<!-- Organizer -->
+<!-- Attendees - Chair -->
+<!-- Attendees - Required Participants -->
+<!-- Attendees - Optional Participants -->
+<!-- Attendees - Observers -->
+<!-- Categories -->
+<!-- Attachments -->
+ </table>
+<!-- Creation -->
+ <p>
+ <em>Creation date: Sunday, 22 May 2005 20:12:07 UTC</em>
+ </p>
+ </body>
+</html>
diff --git a/autotests/data/event-exception-thisandfuture.ical \
b/autotests/data/event-exception-thisandfuture.ical new file mode 100644
index 0000000..e02dab8
--- /dev/null
+++ b/autotests/data/event-exception-thisandfuture.ical
@@ -0,0 +1,19 @@
+BEGIN:VCALENDAR
+PRODID:-//K Desktop Environment//NONSGML KOrganizer 3.4//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20050522T201207Z
+ORGANIZER;CN=Reinhold Kainhofer:MAILTO:reinhold@kainhofer.com
+CREATED:20050522T201119Z
+UID:KOrganizer-557711714.436
+SEQUENCE:0
+LAST-MODIFIED:20050522T201119Z
+SUMMARY:summaryException
+CLASS:PUBLIC
+PRIORITY:5
+RECURRENCE-ID;RANGE=THISANDFUTURE:20050519T084500Z
+DTSTART:20050519T094500Z
+DTEND:20050519T131500Z
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
diff --git a/autotests/data/event-multiday.html b/autotests/data/event-multiday.html
new file mode 100644
index 0000000..e14da33
--- /dev/null
+++ b/autotests/data/event-multiday.html
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" \
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html \
xmlns="http://www.w3.org/1999/xhtml"> + <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF8" />
+ <title></title>
+ <style></style>
+ </head>
+ <body>
+ <style type="text/css">
+th {
+ text-align: left;
+ width: 25%;
+ white-space: nowrap;
+ font-weight: bold;
+}
+td {
+ padding-left: 5px;
+}
+
+</style>
+ <table class="header">
+ <tr>
+ <td>
+ <img src="file:view-calendar-day.svg" align="top" height="16" width="16" \
alt="Event" title="Event" /> + <img src="file:edit-redo.svg" align="top" \
height="16" width="16" alt="Recurring incidence" title="Recurring incidence" /> + \
</td> + <td>
+ <b>
+ <u>20 May 2005 - 21 May 2015</u>
+ </b>
+ </td>
+ </tr>
+ </table>
+ <table>
+<!-- Calendar name -->
+<!-- Location -->
+<!-- Start/end -->
+ <tr>
+ <th>Date:</th>
+ <td>Friday, 20 May 2005</td>
+ </tr>
+ <tr>
+ <th>Time:</th>
+ <td>17:00:00</td>
+ </tr>
+<!-- Duration -->
+ <tr>
+ <th>Duration:</th>
+ <td>1 day 1 hour </td>
+ </tr>
+<!-- Recurrence -->
+ <tr>
+ <th>Recurrence:</th>
+ <td>Recurs every 3 months on the 2nd Last Friday until 2009-05-22 17:00 (17 \
occurrences)</td> + </tr>
+<!-- Birthday -->
+<!-- Anniversary -->
+<!-- Description -->
+<!-- Alarms -->
+<!-- Organizer -->
+<!-- Attendees - Chair -->
+<!-- Attendees - Required Participants -->
+<!-- Attendees - Optional Participants -->
+<!-- Attendees - Observers -->
+<!-- Categories -->
+<!-- Attachments -->
+ </table>
+<!-- Creation -->
+ <p>
+ <em>Creation date: Friday, 20 May 2005 10:58:56 UTC</em>
+ </p>
+ </body>
+</html>
diff --git a/autotests/data/event-multiday.ical b/autotests/data/event-multiday.ical
new file mode 100644
index 0000000..2a7826b
--- /dev/null
+++ b/autotests/data/event-multiday.ical
@@ -0,0 +1,20 @@
+BEGIN:VCALENDAR
+PRODID:-//K Desktop Environment//NONSGML KOrganizer 3.4//EN
+VERSION:2.0
+X-LibKCal-Testsuite-OutTZ:Europe/Vienna
+BEGIN:VEVENT
+DTSTAMP:20050520T105856Z
+ORGANIZER;CN=Reinhold Kainhofer:MAILTO:reinhold@kainhofer.com
+CREATED:20050520T105219Z
+UID:KOrganizer-45214176.303
+SEQUENCE:2
+LAST-MODIFIED:20050520T105815Z
+SUMMARY:20 May 2005 - 21 May 2015
+CLASS:PUBLIC
+PRIORITY:5
+RRULE:FREQ=MONTHLY;COUNT=17;INTERVAL=3;BYDAY=-2FR
+DTSTART:20050520T170000Z
+DTEND:20050521T180000Z
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
diff --git a/autotests/data/event-recurrence-single.out.html \
b/autotests/data/event-recurrence-single.out.html new file mode 100644
index 0000000..dd5d0fa
--- /dev/null
+++ b/autotests/data/event-recurrence-single.out.html
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" \
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html \
xmlns="http://www.w3.org/1999/xhtml"> + <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF8" />
+ <title></title>
+ <style></style>
+ </head>
+ <body>
+ <style type="text/css">
+th {
+ text-align: left;
+ width: 25%;
+ white-space: nowrap;
+ font-weight: bold;
+}
+td {
+ padding-left: 5px;
+}
+
+</style>
+ <table class="header">
+ <tr>
+ <td>
+ <img src="file:view-calendar-day.svg" align="top" height="16" width="16" \
/> + </td>
+ <td>
+ <b>
+ <u>summaryException</u>
+ </b>
+ </td>
+ </tr>
+ </table>
+ <table>
+<!-- Calendar name -->
+<!-- Location -->
+<!-- Start/end -->
+ <tr>
+ <th>Date:</th>
+ <td>Thursday, 19 May 2005</td>
+ </tr>
+ <tr>
+ <th>Time:</th>
+ <td>09:45:00</td>
+ </tr>
+<!-- Duration -->
+ <tr>
+ <th>Duration:</th>
+ <td>3 hours 30 minutes</td>
+ </tr>
+<!-- Recurrence -->
+ <tr>
+ <th>Recurrence:</th>
+ <td>Exception</td>
+ </tr>
+<!-- Birthday -->
+<!-- Anniversary -->
+<!-- Description -->
+<!-- Alarms -->
+<!-- Organizer -->
+<!-- Attendees - Chair -->
+<!-- Attendees - Required Participants -->
+<!-- Attendees - Optional Participants -->
+<!-- Attendees - Observers -->
+<!-- Categories -->
+<!-- Attachments -->
+ </table>
+<!-- Creation -->
+ <p>
+ <em>Creation date: Sunday, 22 May 2005 20:12:07 UTC</em>
+ </p>
+ </body>
+</html>
diff --git a/autotests/data/event-recurrence-thisandfuture.out.html \
b/autotests/data/event-recurrence-thisandfuture.out.html new file mode 100644
index 0000000..dd5d0fa
--- /dev/null
+++ b/autotests/data/event-recurrence-thisandfuture.out.html
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" \
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html \
xmlns="http://www.w3.org/1999/xhtml"> + <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF8" />
+ <title></title>
+ <style></style>
+ </head>
+ <body>
+ <style type="text/css">
+th {
+ text-align: left;
+ width: 25%;
+ white-space: nowrap;
+ font-weight: bold;
+}
+td {
+ padding-left: 5px;
+}
+
+</style>
+ <table class="header">
+ <tr>
+ <td>
+ <img src="file:view-calendar-day.svg" align="top" height="16" width="16" \
/> + </td>
+ <td>
+ <b>
+ <u>summaryException</u>
+ </b>
+ </td>
+ </tr>
+ </table>
+ <table>
+<!-- Calendar name -->
+<!-- Location -->
+<!-- Start/end -->
+ <tr>
+ <th>Date:</th>
+ <td>Thursday, 19 May 2005</td>
+ </tr>
+ <tr>
+ <th>Time:</th>
+ <td>09:45:00</td>
+ </tr>
+<!-- Duration -->
+ <tr>
+ <th>Duration:</th>
+ <td>3 hours 30 minutes</td>
+ </tr>
+<!-- Recurrence -->
+ <tr>
+ <th>Recurrence:</th>
+ <td>Exception</td>
+ </tr>
+<!-- Birthday -->
+<!-- Anniversary -->
+<!-- Description -->
+<!-- Alarms -->
+<!-- Organizer -->
+<!-- Attendees - Chair -->
+<!-- Attendees - Required Participants -->
+<!-- Attendees - Optional Participants -->
+<!-- Attendees - Observers -->
+<!-- Categories -->
+<!-- Attachments -->
+ </table>
+<!-- Creation -->
+ <p>
+ <em>Creation date: Sunday, 22 May 2005 20:12:07 UTC</em>
+ </p>
+ </body>
+</html>
diff --git a/autotests/data/freebusy-1.html b/autotests/data/freebusy-1.html
new file mode 100644
index 0000000..b0b963f
--- /dev/null
+++ b/autotests/data/freebusy-1.html
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" \
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html \
xmlns="http://www.w3.org/1999/xhtml"> + <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF8" />
+ <title></title>
+ <style></style>
+ </head>
+ <body>
+ <style type="text/css">
+th {
+ text-align: left;
+ width: 25%;
+ white-space: nowrap;
+ font-weight: bold;
+}
+td {
+ padding-left: 5px;
+}
+
+</style>
+ <h2>Free/Busy information for dvratil@kde.org</h2>
+ <h4>Busy times in date range 30 Sep 2015 - 31 Oct 2015:</h4>
+ <p><em><b>Busy:</b></em>
+30 Sep 2015, 12:00:00 - 14:00:00
+ <br />5 Oct 2015, 09:30:00 - 10:00:00
+ <br />12 Oct 2015, 09:30:00 - 10:00:00
+ <br />20 Oct 2015 00:00:00 - 27 Oct 2015 00:00:00
+</p>
+ </body>
+</html>
diff --git a/autotests/data/freebusy-1.ical b/autotests/data/freebusy-1.ical
new file mode 100644
index 0000000..c637562
--- /dev/null
+++ b/autotests/data/freebusy-1.ical
@@ -0,0 +1,17 @@
+BEGIN:VCALENDAR
+PRODID:-//K Desktop Environment//NONSGML KOrganizer 3.4//EN
+VERSION:2.0
+X-LibKCal-Testsuite-OutTZ:Europe/Vienna
+BEGIN:VFREEBUSY
+UID:xyz
+ORGANIZER:MAILTO:dvratil@kde.org
+ATTENDEE:MAILTO:dvratil@kde.org
+DTSTART:20150930T000000Z
+DTEND:20151031T235959Z
+DTSTAMP:20150930T083000Z
+FREEBUSY:20150930T120000Z/20150930T140000Z
+FREEBUSY:20151005T093000Z/20151005T100000Z
+FREEBUSY:20151012T093000Z/20151012T100000Z
+FREEBUSY:20151020T000000Z/20151027T000000Z
+END:VFREEBUSY
+END:VCALENDAR
diff --git a/autotests/data/journal-1.html b/autotests/data/journal-1.html
new file mode 100644
index 0000000..38a7a89
--- /dev/null
+++ b/autotests/data/journal-1.html
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" \
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html \
xmlns="http://www.w3.org/1999/xhtml"> + <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF8" />
+ <title></title>
+ <style></style>
+ </head>
+ <body>
+ <style type="text/css">
+th {
+ text-align: left;
+ width: 25%;
+ white-space: nowrap;
+ font-weight: bold;
+}
+td {
+ padding-left: 5px;
+}
+
+</style>
+ <table class="header">
+ <tr>
+ <td>
+ <img src="file:view-pim-journal.svg" align="top" height="16" width="16" \
alt="Journal" title="Journal" /> + </td>
+ <td>
+ <b>
+ <u>Dear diary</u>
+ </b>
+ </td>
+ </tr>
+ </table>
+ <table>
+<!-- Calendar -->
+<!-- Date -->
+ <tr>
+ <th>Date:</th>
+ <td>Monday, 21 September 2015</td>
+ </tr>
+<!-- Description -->
+ <tr>
+ <th>Description:</th>
+ <td>Dear diary,<br />
+<br />
+today I went to Prague and it was amazing.<br />
+<br />
+End of story.<br />
+</td>
+ </tr>
+<!-- Categories -->
+ </table>
+<!-- Creation date -->
+ <p>
+ <em>Creation date: Wednesday, 30 September 2015 09:50:37 UTC</em>
+ </p>
+ </body>
+</html>
diff --git a/autotests/data/journal-1.ical b/autotests/data/journal-1.ical
new file mode 100644
index 0000000..b1d1eeb
--- /dev/null
+++ b/autotests/data/journal-1.ical
@@ -0,0 +1,82 @@
+BEGIN:VCALENDAR
+PRODID:-//K Desktop Environment//NONSGML libkcal 4.3//EN
+VERSION:2.0
+X-KDE-ICAL-IMPLEMENTATION-VERSION:1.0
+BEGIN:VJOURNAL
+DTSTAMP:20150930T095037Z
+CREATED:20150930T095037Z
+UID:2fdb08af-bf2a-4643-a6d7-47b03724e314
+LAST-MODIFIED:20150930T095037Z
+DESCRIPTION:Dear diary\,\n\ntoday I went to Prague and it was
+ amazing.\n\nEnd of story.\n
+SUMMARY:Dear diary
+DTSTART;TZID=Europe/Prague:20150921T080000
+END:VJOURNAL
+BEGIN:VTIMEZONE
+TZID:Europe/Prague
+BEGIN:STANDARD
+TZNAME:CET
+TZOFFSETFROM:+005744
+TZOFFSETTO:+0100
+DTSTART:19011213T214336
+RDATE;VALUE=DATE-TIME:19011213T214336
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:CEST
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+DTSTART:19810329T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+TZNAME:CEST
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+DTSTART:19160430T230000
+RDATE;VALUE=DATE-TIME:19160430T230000
+RDATE;VALUE=DATE-TIME:19170416T020000
+RDATE;VALUE=DATE-TIME:19180415T020000
+RDATE;VALUE=DATE-TIME:19400401T020000
+RDATE;VALUE=DATE-TIME:19430329T020000
+RDATE;VALUE=DATE-TIME:19440403T020000
+RDATE;VALUE=DATE-TIME:19450408T020000
+RDATE;VALUE=DATE-TIME:19460506T020000
+RDATE;VALUE=DATE-TIME:19470420T020000
+RDATE;VALUE=DATE-TIME:19480418T020000
+RDATE;VALUE=DATE-TIME:19490409T020000
+RDATE;VALUE=DATE-TIME:19790401T020000
+RDATE;VALUE=DATE-TIME:19800406T020000
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:CET
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+DTSTART:19790930T030000
+RRULE:FREQ=YEARLY;COUNT=17;BYDAY=-1SU;BYMONTH=9
+END:STANDARD
+BEGIN:STANDARD
+TZNAME:CET
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+DTSTART:19961027T030000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+END:STANDARD
+BEGIN:STANDARD
+TZNAME:CET
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+DTSTART:19161001T010000
+RDATE;VALUE=DATE-TIME:19161001T010000
+RDATE;VALUE=DATE-TIME:19170917T030000
+RDATE;VALUE=DATE-TIME:19180916T030000
+RDATE;VALUE=DATE-TIME:19421102T030000
+RDATE;VALUE=DATE-TIME:19431004T030000
+RDATE;VALUE=DATE-TIME:19440917T030000
+RDATE;VALUE=DATE-TIME:19451118T030000
+RDATE;VALUE=DATE-TIME:19461006T030000
+RDATE;VALUE=DATE-TIME:19471005T030000
+RDATE;VALUE=DATE-TIME:19481003T030000
+RDATE;VALUE=DATE-TIME:19491002T030000
+END:STANDARD
+END:VTIMEZONE
+END:VCALENDAR
diff --git a/autotests/data/todo-1.html b/autotests/data/todo-1.html
new file mode 100644
index 0000000..3a98c05
--- /dev/null
+++ b/autotests/data/todo-1.html
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" \
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html \
xmlns="http://www.w3.org/1999/xhtml"> + <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF8" />
+ <title></title>
+ <style></style>
+ </head>
+ <body>
+ <style type="text/css">
+th {
+ text-align: left;
+ width: 25%;
+ white-space: nowrap;
+ font-weight: bold;
+}
+td {
+ padding-left: 5px;
+}
+
+</style>
+ <table class="header">
+ <tr>
+ <td>
+ <img src="file:view-calendar-tasks.svg" align="top" height="16" width="16" \
alt="Todo" title="Todo" /> + <img \
src="file:preferences-desktop-notification-bell.svg" align="top" height="16" \
width="16" alt="Incidence with a reminder" title="Incidence with a reminder" /> + \
</td> + <td>
+ <b>
+ <u>Buy Milk</u>
+ </b>
+ </td>
+ </tr>
+ </table>
+ <table class="main">
+<!-- Calendar -->
+<!-- Location -->
+ <tr>
+ <th>Location:</th>
+ <td>Grocery</td>
+ </tr>
+<!-- Start date -->
+ <tr>
+ <th>Start:</th>
+ <td>Wednesday, 30 September 2015 08:00:00 UTC</td>
+ </tr>
+<!-- Due date -->
+ <tr>
+ <th>Due:</th>
+ <td>Thursday, 1 October 2015 08:00:00 UTC</td>
+ </tr>
+<!-- Duration -->
+ <tr>
+ <th>Duration:</th>
+ <td>1 day </td>
+ </tr>
+<!-- Recurrence -->
+<!-- Organizer -->
+<!-- Attendee -->
+<!-- Description -->
+ <tr>
+ <th>Description:</th>
+ <td>We need milk <img align="center" title=":)" alt=":)" \
src="/home/dvratil/.local/share/emoticons/Ubuntu/icon_smile.png" width="16" \
height="15" /></td> + </tr>
+<!-- Comments -->
+<!-- Reminders -->
+ <tr>
+ <th>Reminder:</th>
+ <td>15 minutes before the to-do is due</td>
+ </tr>
+<!-- Organizer -->
+ <tr>
+ <th>Organizer:</th>
+ <td><img src="file:meeting-organizer.png" align="top" height="16" width="16" \
alt="meeting-organizer" title="" /> +Daniel Vrátil
+ <a href="mailto:Daniel Vrátil %3Cme@dvratil.cz%3E"><img \
src="file:mail-message-new.svg" align="top" height="16" width="16" alt="Send email" \
title="Send email" /></a> +
+ </td>
+ </tr>
+<!-- Attendees - Chair -->
+<!-- Attendees - Required Participants -->
+ <tr>
+ <th>Required Participants:</th>
+ <td>
+ <img src="file:help-about.svg" align="top" height="16" width="16" \
alt="Needs action" title="Needs action" /> + <a href="uid:68225424">Flatmate
+ </a>
+ <a href="mailto:Flatmate %3Cmy@flat.mate%3E">
+ <img src="file:mail-message-new.svg" align="top" height="16" width="16" \
alt="Send email" title="Send email" /> + </a>
+ </td>
+ </tr>
+<!-- Attendees - Optional Participants -->
+<!-- Attendees - Observers -->
+<!-- Categories -->
+<!-- Priority -->
+ <tr>
+ <th>Priority:</th>
+ <td>3</td>
+ </tr>
+<!-- Completed -->
+ <tr>
+ <th>Percent done:</th>
+ <td>50%</td>
+ </tr>
+<!-- Attachments -->
+ </table>
+<!-- Creation date -->
+ <p>
+ <em>Creation date: Wednesday, 30 September 2015 09:43:11 UTC</em>
+ </p>
+ </body>
+</html>
diff --git a/autotests/data/todo-1.ical b/autotests/data/todo-1.ical
new file mode 100644
index 0000000..59f557d
--- /dev/null
+++ b/autotests/data/todo-1.ical
@@ -0,0 +1,30 @@
+BEGIN:VCALENDAR
+PRODID:-//K Desktop Environment//NONSGML KOrganizer 5.0.44 pre//EN
+VERSION:2.0
+X-KDE-ICAL-IMPLEMENTATION-VERSION:1.0
+METHOD:REQUEST
+BEGIN:VTODO
+ORGANIZER;CN="Daniel Vrátil":MAILTO:me@dvratil.cz
+DTSTAMP:20150930T094648Z
+ATTENDEE;CN="Flatmate";RSVP=FALSE;PARTSTAT=NEEDS-ACTION;
+ ROLE=REQ-PARTICIPANT;CUTYPE=INDIVIDUAL;X-UID=68225424:mailto:
+ my@flat.mate
+CREATED:20150930T094311Z
+UID:c8e304fa-f103-4693-bb7a-897bedf4552e
+LAST-MODIFIED:20150930T094525Z
+DESCRIPTION:We need milk :)
+SUMMARY:Buy Milk
+LOCATION:Grocery
+STATUS:IN-PROCESS
+PRIORITY:3
+DUE:20151001T080000Z
+DTSTART:20150930T080000Z
+PERCENT-COMPLETE:50
+BEGIN:VALARM
+DESCRIPTION:
+ACTION:DISPLAY
+TRIGGER;VALUE=DURATION;RELATED=END:-PT15M
+X-KDE-KCALCORE-ENABLED:TRUE
+END:VALARM
+END:VTODO
+END:VCALENDAR
diff --git a/autotests/test_config.h.cmake b/autotests/test_config.h.cmake
new file mode 100644
index 0000000..0947c1a
--- /dev/null
+++ b/autotests/test_config.h.cmake
@@ -0,0 +1,5 @@
+#define TEST_DATA_DIR "@TEST_DATA_DIR@"
+
+#define TEST_TEMPLATE_PATH "@TEST_TEMPLATE_PATH@"
+
+#define TEST_PLUGIN_PATH "@TEST_PLUGIN_PATH@"
\ No newline at end of file
diff --git a/autotests/testincidenceformatter.cpp \
b/autotests/testincidenceformatter.cpp index 28dd952..ce9f750 100644
--- a/autotests/testincidenceformatter.cpp
+++ b/autotests/testincidenceformatter.cpp
@@ -20,21 +20,37 @@
*/
#include "testincidenceformatter.h"
+#include "test_config.h"
+
#include "incidenceformatter.h"
+#include "grantleetemplatemanager_p.h"
#include <kcalcore/event.h>
+#include <kcalcore/icalformat.h>
+#include <kcalcore/todo.h>
+#include <kcalcore/journal.h>
+#include <kcalcore/freebusy.h>
+#include <kcalcore/memorycalendar.h>
#include <KDateTime>
#include <KLocalizedString>
#include <KLocale>
#include <QDebug>
+#include <QProcess>
#include <qtest.h>
+
QTEST_MAIN(IncidenceFormatterTest)
using namespace KCalCore;
using namespace KCalUtils;
+void IncidenceFormatterTest::initTestCase()
+{
+ GrantleeTemplateManager::instance()->setTemplatePath(QStringLiteral(TEST_TEMPLATE_PATH));
+ GrantleeTemplateManager::instance()->setPluginPath(QStringLiteral(TEST_PLUGIN_PATH));
+}
+
void IncidenceFormatterTest::testRecurrenceString()
{
// TEST: A daily recurrence with date exclusions //
@@ -127,3 +143,221 @@ void IncidenceFormatterTest::testRecurrenceString()
// qDebug() << "recurrenceString=" << IncidenceFormatter::recurrenceString( e3 );
}
+
+
+KCalCore::Calendar::Ptr IncidenceFormatterTest::loadCalendar(const QString &name)
+{
+ auto calendar = KCalCore::MemoryCalendar::Ptr::create(KDateTime::UTC);
+ KCalCore::ICalFormat format;
+
+ if (!format.load(calendar, QStringLiteral(TEST_DATA_DIR "/%1.ical").arg(name))) \
{ + return KCalCore::Calendar::Ptr();
+ }
+
+ return calendar;
+}
+
+bool IncidenceFormatterTest::validateHtml(const QString &name, const QString &_html)
+{
+ QString html = QStringLiteral("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 \
Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" + \
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" + " <head>\n"
+ " <title></title>\n"
+ " <style></style>\n"
+ " </head>\n"
+ "<body>")
+ + _html
+ + QStringLiteral("</body>\n</html>");
+
+ const QString outFileName = QStringLiteral(TEST_DATA_DIR "/%1.out").arg(name);
+ const QString htmlFileName = QStringLiteral(TEST_DATA_DIR \
"/%1.out.html").arg(name); + QFile outFile(outFileName);
+ if (!outFile.open(QIODevice::WriteOnly)) {
+ return false;
+ }
+ outFile.write(html.toUtf8());
+ outFile.close();
+
+ // validate xml and pretty-print for comparisson
+ // TODO add proper cmake check for xmllint and diff
+ const QStringList args = {
+ QStringLiteral("--format"),
+ QStringLiteral("--encode"),
+ QStringLiteral("UTF8"),
+ QStringLiteral("--output"),
+ htmlFileName,
+ outFileName
+ };
+
+ const int result = QProcess::execute(QLatin1String("xmllint"), args);
+ return result == 0;
+}
+
+bool IncidenceFormatterTest::compareHtml(const QString &name)
+{
+ const QString htmlFileName = QStringLiteral(TEST_DATA_DIR \
"/%1.out.html").arg(name); + const QString referenceFileName = \
QStringLiteral(TEST_DATA_DIR "/%1.html").arg(name); +
+ // get rid of system dependent or random paths
+ {
+ QFile f(htmlFileName);
+ if (!f.open(QIODevice::ReadOnly)) {
+ return false;
+ }
+ QString content = QString::fromUtf8(f.readAll());
+ f.close();
+ content.replace(QRegExp(QLatin1String("\"file:[^\"]*[/(?:%2F)]([^\"/(?:%2F)]*)\"")), \
QStringLiteral("\"file:\\1\"")); + if (!f.open(QIODevice::WriteOnly | \
QIODevice::Truncate)) { + return false;
+ }
+ f.write(content.toUtf8());
+ f.close();
+ }
+
+ // compare to reference file
+ const QStringList args = {
+ QStringLiteral("-u"),
+ referenceFileName,
+ htmlFileName
+ };
+
+ QProcess proc;
+ proc.setProcessChannelMode(QProcess::ForwardedChannels);
+ proc.start(QLatin1String("diff"), args);
+ if (!proc.waitForFinished()) {
+ return false;
+ }
+
+ return proc.exitCode() == 0;
+}
+
+void IncidenceFormatterTest::cleanup(const QString &name)
+{
+ QFile::remove(QStringLiteral(TEST_DATA_DIR "/%1.out").arg(name));
+ QFile::remove(QStringLiteral(TEST_DATA_DIR "/%1.out.html").arg(name));
+}
+
+void IncidenceFormatterTest::testErrorTemplate()
+{
+ GrantleeTemplateManager::instance()->setTemplatePath(QStringLiteral(TEST_DATA_DIR));
+ const QString html = \
GrantleeTemplateManager::instance()->render(QStringLiteral("broken-template.html"), \
QVariantHash()); + \
GrantleeTemplateManager::instance()->setTemplatePath(QStringLiteral(TEST_TEMPLATE_PATH));
+
+ const QString expected = QStringLiteral(
+ "<h1>Template parsing error</h1>\n"
+ "<b>Template:</b> broken-template.html<br>\n"
+ "<b>Error message:</b> Unclosed tag in template broken-template.html. \
Expected one of: (else endif), line 2, broken-template.html"); +
+ QCOMPARE(html, expected);
+}
+
+void IncidenceFormatterTest::testDisplayViewFormatEvent_data()
+{
+ QTest::addColumn<QString>("name");
+
+ QTest::newRow("event-1") << QStringLiteral("event-1");
+ QTest::newRow("event-2") << QStringLiteral("event-2");
+ QTest::newRow("event-exception-thisandfuture") << \
QStringLiteral("event-exception-thisandfuture"); + \
QTest::newRow("event-exception-single") << QStringLiteral("event-exception-single"); \
+ QTest::newRow("event-allday-multiday") << \
QStringLiteral("event-allday-multiday"); + QTest::newRow("event-allday") << \
QStringLiteral("event-allday"); + QTest::newRow("event-multiday") << \
QStringLiteral("event-multiday"); +}
+
+void IncidenceFormatterTest::testDisplayViewFormatEvent()
+{
+ QFETCH(QString, name);
+
+ KCalCore::Calendar::Ptr calendar = loadCalendar(name);
+ QVERIFY(calendar);
+
+ const auto events = calendar->events();
+ QCOMPARE(events.size(), 1);
+
+ const QString html = IncidenceFormatter::extensiveDisplayStr(calendar, \
events[0]); +
+ QVERIFY(validateHtml(name, html));
+ QVERIFY(compareHtml(name));
+
+ cleanup(name);
+}
+
+void IncidenceFormatterTest::testDisplayViewFormatTodo_data()
+{
+ QTest::addColumn<QString>("name");
+
+ QTest::newRow("todo-1") << QStringLiteral("todo-1");
+}
+
+void IncidenceFormatterTest::testDisplayViewFormatTodo()
+{
+ QFETCH(QString, name);
+
+ KCalCore::Calendar::Ptr calendar = loadCalendar(name);
+ QVERIFY(calendar);
+
+ const auto todos = calendar->todos();
+ QCOMPARE(todos.size(), 1);
+
+ const QString html = IncidenceFormatter::extensiveDisplayStr(calendar, \
todos[0]); +
+ QVERIFY(validateHtml(name, html));
+ QVERIFY(compareHtml(name));
+
+ cleanup(name);
+}
+
+void IncidenceFormatterTest::testDisplayViewFormatJournal_data()
+{
+ QTest::addColumn<QString>("name");
+
+ QTest::newRow("journal-1") << QStringLiteral("journal-1");
+}
+
+void IncidenceFormatterTest::testDisplayViewFormatJournal()
+{
+ QFETCH(QString, name);
+
+ KCalCore::Calendar::Ptr calendar = loadCalendar(name);
+ QVERIFY(calendar);
+
+ const auto journals = calendar->journals();
+ QCOMPARE(journals.size(), 1);
+
+ const QString html = IncidenceFormatter::extensiveDisplayStr(calendar, \
journals[0]); +
+ QVERIFY(validateHtml(name, html));
+ QVERIFY(compareHtml(name));
+
+ cleanup(name);
+}
+
+void IncidenceFormatterTest::testDisplayViewFreeBusy_data()
+{
+ QTest::addColumn<QString>("name");
+
+ QTest::newRow("freebusy-1") << QStringLiteral("freebusy-1");
+}
+
+void IncidenceFormatterTest::testDisplayViewFreeBusy()
+{
+ QFETCH(QString, name);
+
+ KCalCore::Calendar::Ptr calendar = loadCalendar(name);
+ QVERIFY(calendar);
+
+ QFile file(QStringLiteral(TEST_DATA_DIR "/%1.ical").arg(name));
+ QVERIFY(file.open(QIODevice::ReadOnly));
+ const QByteArray fbData = file.readAll();
+
+ KCalCore::ICalFormat format;
+ KCalCore::FreeBusy::Ptr freeBusy = \
format.parseFreeBusy(QString::fromUtf8(fbData)); + QVERIFY(freeBusy);
+
+ const QString html = IncidenceFormatter::extensiveDisplayStr(calendar, \
freeBusy); +
+ QVERIFY(validateHtml(name, html));
+ QVERIFY(compareHtml(name));
+
+ cleanup(name);
+}
diff --git a/autotests/testincidenceformatter.h b/autotests/testincidenceformatter.h
index 0065745..11e4d14 100644
--- a/autotests/testincidenceformatter.h
+++ b/autotests/testincidenceformatter.h
@@ -24,11 +24,37 @@
#include <QtCore/QObject>
+#include <KCalCore/MemoryCalendar>
+
class IncidenceFormatterTest : public QObject
{
Q_OBJECT
+
+private:
+ /* Helper functions for testDisplayViewFormat* */
+ KCalCore::Calendar::Ptr loadCalendar(const QString &name);
+ bool validateHtml(const QString &name, const QString &html);
+ bool compareHtml(const QString &name);
+ void cleanup(const QString &name);
+
private Q_SLOTS:
+ void initTestCase();
+
void testRecurrenceString();
+
+ void testErrorTemplate();
+
+ void testDisplayViewFormatEvent_data();
+ void testDisplayViewFormatEvent();
+
+ void testDisplayViewFormatTodo_data();
+ void testDisplayViewFormatTodo();
+
+ void testDisplayViewFormatJournal_data();
+ void testDisplayViewFormatJournal();
+
+ void testDisplayViewFreeBusy_data();
+ void testDisplayViewFreeBusy();
};
#endif
diff --git a/scripts/extract_strings_ki18n.py b/scripts/extract_strings_ki18n.py
new file mode 100755
index 0000000..5d11148
--- /dev/null
+++ b/scripts/extract_strings_ki18n.py
@@ -0,0 +1,54 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+##
+# Copyright (C) 2015 Daniel Vrátil <dvratil@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+##
+
+
+import os, sys, glob, operator
+from grantlee_strings_extractor import TranslationOutputter
+
+class KI18nExtractStrings(TranslationOutputter):
+ def createOutput(self, template_filename, context_strings, outputfile):
+ for context_string in context_strings:
+ outputfile.write("// i18n: file: %s\n" % template_filename)
+ if context_string.context:
+ if not context_string.plural:
+ outputfile.write("i18nc(\"" + context_string.context + "\", \"" \
+ context_string._string + "\");\n") + else:
+ outputfile.write("i18ncp(\"" + context_string.context + "\", \"" \
+ context_string._string + "\", \"" + context_string.plural + "\", 1);\n") + \
else: + if context_string.plural:
+ outputfile.write("i18np(\"" + context_string._string + "\", \"" \
+ context_string.plural + "\", 1);\n") + else:
+ outputfile.write("i18n(\"" + context_string._string + "\");\n")
+
+
+
+if __name__ == "__main__":
+ ex = KI18nExtractStrings()
+
+ outputfile = sys.stdout
+
+ files = reduce(operator.add, map(glob.glob, sys.argv[1:]))
+
+ for filename in files:
+ f = open(filename, "r")
+ ex.translate(f, outputfile)
+
+ outputfile.write("\n")
diff --git a/scripts/grantlee_strings_extractor.py \
b/scripts/grantlee_strings_extractor.py new file mode 100644
index 0000000..c7d71dd
--- /dev/null
+++ b/scripts/grantlee_strings_extractor.py
@@ -0,0 +1,365 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+##
+# Copyright 2010,2011 Stephen Kelly <steveire@gmail.com>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+##
+
+## Parts of this file are reproduced from the Django framework. The Django licence \
appears below. +
+##
+# Copyright (c) Django Software Foundation and individual contributors.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of Django nor the names of its contributors may be used
+# to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+##
+
+import re
+import os.path
+
+# == Introduction to the template syntax ==
+#
+# The template syntax looks like this:
+# (For more see here: http://grantlee.org/apidox/for_themers.html )
+#
+# This is plain text
+# This is text with a {{ value }} substitution
+# This is {% if condition_is_met %}a conditional{% endif %}
+# {# This is a comment #}
+# This is a {% comment %} multi-line
+# comment
+# {% endcomment %}
+#
+# That is, we have plain text.
+# We have value substitution with {{ }}
+# We have comments with {# #}
+# We have control tags with {% %}
+#
+# The first token inside {% %} syntax is called a tag name. Above, we have
+# an if tag and a comment tag.
+#
+# The 'value' in {{ value }} is called a filter expression. In the above case
+# the filter expression is a simple value which was inserted into the context.
+# In other cases it can be {{ value|upper }}, that is the value can be passed
+# through a filter called 'upper' with the '|', or filter expression can
+# be {{ value|join:"-" }}, that is it can be passed through the join filter
+# which takes an argument. In this case, the 'value' would actually be a list,
+# and the join filter would concatenate them with a dash. A filter can have
+# either no arguments, like upper, or it can take one argument, delimited by
+# a colon (';'). A filter expression can consist of a value followed by a
+# chain of filters, such as {{ value|join:"-"|upper }}. A filter expression
+# can appear one time inside {{ }} but may appear multiple times inside {% %}
+# For example {% cycle foo|upper bar|join:"-" bat %} contains 3 filter
+# expressions, 'foo|upper', 'bar|join:"-"' and 'bat'.
+#
+# Comments are ignored in the templates.
+#
+# == i18n in templates ==
+#
+# The purpose of this script is to extract translatable strings from templates
+# The aim is to allow template authors to write templates like this:
+#
+# This is a {{ _("translatable string") }} in the template.
+# This is a {% i18n "translatable string about %1" something %}
+# This is a {% i18nc "Some context information" "string about %1" something %}
+# This is a {% i18np "%1 string about %2" numthings something %}
+# This is a {% i18ncp "some context" "%1 string about %2" numthings something %}
+#
+# That is, simple translation with _(), and i18n* tags to allow for variable
+# substitution, context messages and plurals. Translatable strings may appear
+# in a filter expression, either as the value begin filtered, or as the argument
+# or both:
+#
+# {{ _("hello")|upper }}
+# {{ list|join:_("and") }}
+#
+# == How the strings are extracted ==
+#
+# The strings are extracted by parsing the template with regular expressions.
+# The tag_re regular expression breaks the template into a stream of tokens
+# containing plain text, {{ values }} and {% tags %}.
+# That work is done by the tokenize method with the create_token method.
+# Each token is then processed to extract the translatable strings from
+# the filter expressions.
+
+
+# The original context of much of this script is in the django template system:
+# http://code.djangoproject.com/browser/django/trunk/django/template/base.py
+
+
+TOKEN_TEXT = 0
+TOKEN_VAR = 1
+TOKEN_BLOCK = 2
+TOKEN_COMMENT = 3
+
+# template syntax constants
+FILTER_SEPARATOR = '|'
+FILTER_ARGUMENT_SEPARATOR = ':'
+BLOCK_TAG_START = '{%'
+BLOCK_TAG_END = '%}'
+VARIABLE_TAG_START = '{{'
+VARIABLE_TAG_END = '}}'
+COMMENT_TAG_START = '{#'
+COMMENT_TAG_END = '#}'
+
+# match a variable or block tag and capture the entire tag, including start/end \
delimiters +tag_re = re.compile('(%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), \
re.escape(BLOCK_TAG_END), + \
re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END))) +
+
+# Expression to match some_token and some_token="with spaces" (and similarly
+# for single-quoted strings).
+smart_split_re = re.compile(r"""
+ ((?:
+ [^\s'"]*
+ (?:
+ (?:"(?:[^"\\]|\\.)*" | '(?:[^'\\]|\\.)*')
+ [^\s'"]*
+ )+
+ ) | \S+)
+""", re.VERBOSE)
+
+def smart_split(text):
+ r"""
+ Generator that splits a string by spaces, leaving quoted phrases together.
+ Supports both single and double quotes, and supports escaping quotes with
+ backslashes. In the output, strings will keep their initial and trailing
+ quote marks and escaped quotes will remain escaped (the results can then
+ be further processed with unescape_string_literal()).
+
+ >>> list(smart_split(r'This is "a person\'s" test.'))
+ [u'This', u'is', u'"a person\\\'s"', u'test.']
+ >>> list(smart_split(r"Another 'person\'s' test."))
+ [u'Another', u"'person\\'s'", u'test.']
+ >>> list(smart_split(r'A "\"funky\" style" test.'))
+ [u'A', u'"\\"funky\\" style"', u'test.']
+ """
+ for bit in smart_split_re.finditer(text):
+ yield bit.group(0)
+
+
+# This only matches constant *strings* (things in quotes or marked for
+# translation).
+
+constant_string = r"(?:%(strdq)s|%(strsq)s)" % {
+ 'strdq': r'"[^"\\]*(?:\\.[^"\\]*)*"', # double-quoted string
+ 'strsq': r"'[^'\\]*(?:\\.[^'\\]*)*'", # single-quoted string
+ }
+
+filter_raw_string = \
r"""^%(i18n_open)s(?P<l10nable>%(constant_string)s)%(i18n_close)s""" % { + \
'constant_string': constant_string, + 'i18n_open' : re.escape("_("),
+ 'i18n_close' : re.escape(")"),
+ }
+
+filter_re = re.compile(filter_raw_string, re.UNICODE|re.VERBOSE)
+
+class TemplateSyntaxError(Exception):
+ pass
+
+class TranslatableString:
+ _string = ''
+ context = ''
+ plural = ''
+
+ def __repr__(self):
+ return "String('%s', '%s', '%s')" % (self._string, self.context, \
self.plural) +
+class Token(object):
+ def __init__(self, token_type, contents):
+ # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT.
+ self.token_type, self.contents = token_type, contents
+
+ def __str__(self):
+ return '<%s token: "%s...">' % \
+ ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', \
TOKEN_COMMENT: 'Comment'}[self.token_type], + \
self.contents[:20].replace('\n', '')) +
+def create_token(token_string, in_tag):
+ """
+ Convert the given token string into a new Token object and return it.
+ If in_tag is True, we are processing something that matched a tag,
+ otherwise it should be treated as a literal string.
+ """
+ if in_tag:
+ if token_string.startswith(VARIABLE_TAG_START):
+ token = Token(TOKEN_VAR, \
token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip()) + elif \
token_string.startswith(BLOCK_TAG_START): + token = Token(TOKEN_BLOCK, \
token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()) + elif \
token_string.startswith(COMMENT_TAG_START): + token = Token(TOKEN_COMMENT, \
'') + else:
+ token = Token(TOKEN_TEXT, token_string)
+ return token
+
+def tokenize(template_string):
+
+ in_tag = False
+ result = []
+ for bit in tag_re.split(template_string):
+ if bit:
+ result.append(create_token(bit, in_tag))
+ in_tag = not in_tag
+ return result
+
+class TranslationOutputter:
+ translatable_strings = []
+
+ def get_translatable_filter_args(self, token):
+ """
+ Find the filter expressions in token and extract the strings in it.
+ """
+ matches = filter_re.finditer(token)
+ upto = 0
+ var_obj = False
+ for match in matches:
+ l10nable = match.group("l10nable")
+
+ if l10nable:
+ # Make sure it's a quoted string
+ if l10nable.startswith('"') and l10nable.endswith('"') \
+ or l10nable.startswith("'") and l10nable.endswith("'"):
+ ts = TranslatableString()
+ ts._string = l10nable[1:-1]
+ self.translatable_strings.append(ts)
+
+ def get_contextual_strings(self, token):
+ split = []
+ _bits = smart_split(token.contents)
+ _bit = _bits.next()
+ if _bit =="i18n" or _bit == "i18n_var":
+ # {% i18n "A one %1, a two %2, a three %3" var1 var2 var3 %}
+ # {% i18n_var "A one %1, a two %2, a three %3" var1 var2 var3 as result \
%} + _bit = _bits.next()
+ if not _bit.startswith("'") and not _bit.startswith('"'):
+ return
+
+ sentinal = _bit[0]
+ if not _bit.endswith(sentinal):
+ return
+
+ translatable_string = TranslatableString()
+ translatable_string._string = _bit[1:-1]
+ self.translatable_strings.append(translatable_string)
+ elif _bit =="i18nc" or _bit == "i18nc_var":
+ # {% i18nc "An email send operation failed." "%1 Failed!" var1 %}
+ # {% i18nc_var "An email send operation failed." "%1 Failed!" var1 as \
result %} + _bit = _bits.next()
+ if not _bit.startswith("'") and not _bit.startswith('"'):
+ return
+
+ sentinal = _bit[0]
+ if not _bit.endswith(sentinal):
+ return
+
+ translatable_string = TranslatableString()
+ translatable_string.context = _bit[1:-1]
+ _bit = _bits.next()
+ translatable_string._string = _bit[1:-1]
+ self.translatable_strings.append(translatable_string)
+ elif _bit =="i18np" or _bit =="i18np_var":
+ # {% i18np "An email send operation failed." "%1 email send operations \
failed. Error : % 2." count count errorMsg %} + # {% i18np_var "An email \
send operation failed." "%1 email send operations failed. Error : % 2." count count \
errorMsg as result %} + _bit = _bits.next()
+ if not _bit.startswith("'") and not _bit.startswith('"'):
+ return
+
+ sentinal = _bit[0]
+ if not _bit.endswith(sentinal):
+ return
+
+ translatable_string = TranslatableString()
+ translatable_string._string = _bit[1:-1]
+ _bit = _bits.next()
+ translatable_string.plural = _bit[1:-1]
+ self.translatable_strings.append(translatable_string)
+ elif _bit =="i18ncp" or _bit =="i18ncp_var":
+ # {% i18np "The user tried to send an email, but that failed." "An email \
send operation failed." "%1 email send operation failed." count count %} + \
# {% i18np_var "The user tried to send an email, but that failed." "An email send \
operation failed." "%1 email send operation failed." count count as result %} +
+ _bit = _bits.next()
+ if not _bit.startswith("'") and not _bit.startswith('"'):
+ return
+
+ sentinal = _bit[0]
+ if not _bit.endswith(sentinal):
+ return
+
+ translatable_string = TranslatableString()
+ translatable_string.context = _bit[1:-1]
+ _bit = _bits.next()
+ translatable_string._string = _bit[1:-1]
+ _bit = _bits.next()
+ translatable_string.plural = _bit[1:-1]
+ self.translatable_strings.append(translatable_string)
+ else:
+ return
+
+ for _bit in _bits:
+
+ if (_bit == "as"):
+ return
+ self.get_translatable_filter_args(_bit)
+
+ def get_plain_strings(self, token):
+ split = []
+ bits = iter(smart_split(token.contents))
+ for bit in bits:
+ self.get_translatable_filter_args(bit)
+
+ def translate(self, template_file, outputfile):
+ template_string = template_file.read()
+ self.translatable_strings = []
+ for token in tokenize(template_string):
+ if token.token_type == TOKEN_VAR or token.token_type == TOKEN_BLOCK:
+ self.get_plain_strings(token)
+ if token.token_type == TOKEN_BLOCK:
+ self.get_contextual_strings(token)
+ self.createOutput(os.path.relpath(template_file.name), \
self.translatable_strings, outputfile) +
+ def createOutput(self, template_filename, translatable_strings, outputfile):
+ pass
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0d7bdcc..633cb37 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,3 +1,5 @@
+add_subdirectory(grantlee_plugin)
+
set(kcalutils_SRCS
htmlexport.cpp
icaldrag.cpp
@@ -7,6 +9,8 @@ set(kcalutils_SRCS
scheduler.cpp
vcaldrag.cpp
dndfactory.cpp
+ grantleeki18nlocalizer.cpp
+ grantleetemplatemanager.cpp
)
ecm_qt_declare_logging_category(kcalutils_SRCS HEADER kcalutils_debug.h IDENTIFIER \
KCALUTILS_LOG CATEGORY_NAME log_kcalutils)
@@ -31,6 +35,7 @@ PRIVATE
KF5::I18n
KF5::IdentityManagement
KF5::Codecs
+ Grantlee5::Templates
)
set_target_properties(KF5CalendarUtils PROPERTIES
diff --git a/src/Messages.sh b/src/Messages.sh
deleted file mode 100644
index 5f5de62..0000000
--- a/src/Messages.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#! /bin/sh
-$EXTRACTRC *.kcfg *.ui >> rc.cpp
-$XGETTEXT *.cpp -o $podir/libkcalutils5.pot
diff --git a/src/grantlee_plugin/CMakeLists.txt b/src/grantlee_plugin/CMakeLists.txt
new file mode 100644
index 0000000..4a95707
--- /dev/null
+++ b/src/grantlee_plugin/CMakeLists.txt
@@ -0,0 +1,19 @@
+kde_enable_exceptions()
+
+set(grantleeplugin_SRCS
+ kcalendargrantleeplugin.cpp
+ icon.cpp
+ datetimefilters.cpp
+)
+
+add_library(kcalendar_grantlee_plugin MODULE ${grantleeplugin_SRCS})
+grantlee_adjust_plugin_name(kcalendar_grantlee_plugin)
+target_link_libraries(kcalendar_grantlee_plugin
+ Grantlee5::Templates
+ KF5::IconThemes
+ KF5CalendarUtils
+)
+
+install(TARGETS kcalendar_grantlee_plugin
+ LIBRARY DESTINATION \
${LIB_INSTALL_DIR}/grantlee/${Grantlee5_VERSION_MAJOR}.${Grantlee5_VERSION_MINOR}/ +)
diff --git a/src/grantlee_plugin/datetimefilters.cpp \
b/src/grantlee_plugin/datetimefilters.cpp new file mode 100644
index 0000000..d87c1d5
--- /dev/null
+++ b/src/grantlee_plugin/datetimefilters.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 Daniel Vrátil <dvratil@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "datetimefilters.h"
+#include "../incidenceformatter.h"
+
+#include <grantlee/safestring.h>
+
+#include <KDateTime>
+
+#include <QDebug>
+
+KDateFilter::KDateFilter()
+ : Grantlee::Filter()
+{
+}
+
+KDateFilter::~KDateFilter()
+{
+}
+
+QVariant KDateFilter::doFilter(const QVariant &input, const QVariant &argument, bool \
autoescape) const +{
+ Q_UNUSED(autoescape);
+
+ QDate date;
+ if (input.type() == QVariant::Date) {
+ date = input.toDate();
+ } else if (input.type() == QVariant::DateTime) {
+ date = input.toDateTime().date();
+ } else {
+ return QString();
+ }
+
+ const bool shortFmt = \
(argument.value<Grantlee::SafeString>().get().compare(QLatin1String("short"), \
Qt::CaseInsensitive) == 0); + return \
Grantlee::SafeString(KCalUtils::IncidenceFormatter::dateToString(KDateTime(date), \
shortFmt)); +}
+
+bool KDateFilter::isSafe() const
+{
+ return true;
+}
+
+
+
+KTimeFilter::KTimeFilter()
+ : Grantlee::Filter()
+{
+}
+
+KTimeFilter::~KTimeFilter()
+{
+}
+
+QVariant KTimeFilter::doFilter(const QVariant &input, const QVariant &argument, bool \
autoescape) const +{
+ Q_UNUSED(autoescape);
+
+ QTime time;
+ if (input.type() == QVariant::Time) {
+ time = input.toTime();
+ } else if (input.type() == QVariant::DateTime) {
+ time = input.toDateTime().time();
+ } else {
+ return QString();
+ }
+
+ const bool shortFmt = \
(argument.value<Grantlee::SafeString>().get().compare(QLatin1String("short"), \
Qt::CaseInsensitive) == 0); +
+ return Grantlee::SafeString( \
KCalUtils::IncidenceFormatter::timeToString(KDateTime(QDate(), time), shortFmt)); +}
+
+bool KTimeFilter::isSafe() const
+{
+ return true;
+}
+
+
+
+KDateTimeFilter::KDateTimeFilter()
+ : Grantlee::Filter()
+{
+}
+
+KDateTimeFilter::~KDateTimeFilter()
+{
+}
+
+QVariant KDateTimeFilter::doFilter(const QVariant &input, const QVariant &argument, \
bool autoescape) const +{
+ Q_UNUSED(autoescape);
+
+ if (input.type() != QVariant::DateTime) {
+ return QString();
+ }
+ const QDateTime dt = input.toDateTime();
+
+ const QStringList arguments = \
argument.value<Grantlee::SafeString>().get().split(QLatin1Char(',')); + const bool \
shortFmt = arguments.contains(QStringLiteral("short"), Qt::CaseInsensitive); + \
const bool dateOnly = arguments.contains(QStringLiteral("dateonly"), \
Qt::CaseInsensitive); +
+ return Grantlee::SafeString(KCalUtils::IncidenceFormatter::dateTimeToString(KDateTime(dt), \
dateOnly, shortFmt)); +}
+
+bool KDateTimeFilter::isSafe() const
+{
+ return true;
+}
diff --git a/src/grantlee_plugin/datetimefilters.h \
b/src/grantlee_plugin/datetimefilters.h new file mode 100644
index 0000000..8d35c8c
--- /dev/null
+++ b/src/grantlee_plugin/datetimefilters.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 Daniel Vrátil <dvratil@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef DATETIMEFILTERS_H
+#define DATETIMEFILTERS_H
+
+#include <grantlee/filter.h>
+
+class KDateFilter : public Grantlee::Filter
+{
+public:
+ explicit KDateFilter();
+ ~KDateFilter();
+
+ QVariant doFilter(const QVariant &input, const QVariant &argument = QVariant(),
+ bool autoescape = false) const Q_DECL_OVERRIDE;
+ bool isSafe() const Q_DECL_OVERRIDE;
+};
+
+class KTimeFilter : public Grantlee::Filter
+{
+public:
+ explicit KTimeFilter();
+ ~KTimeFilter();
+
+ QVariant doFilter(const QVariant &input, const QVariant &argument = QVariant(),
+ bool autoescape = false) const Q_DECL_OVERRIDE;
+ bool isSafe() const Q_DECL_OVERRIDE;
+};
+
+class KDateTimeFilter : public Grantlee::Filter
+{
+public:
+ explicit KDateTimeFilter();
+ ~KDateTimeFilter();
+
+ QVariant doFilter(const QVariant &input, const QVariant &argument = QVariant(),
+ bool autoescape = false) const Q_DECL_OVERRIDE;
+ bool isSafe() const Q_DECL_OVERRIDE;
+};
+
+#endif // DATETIMEFILTERS_H
diff --git a/src/grantlee_plugin/icon.cpp b/src/grantlee_plugin/icon.cpp
new file mode 100644
index 0000000..b4293f9
--- /dev/null
+++ b/src/grantlee_plugin/icon.cpp
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2015 Daniel Vrátil <dvratil@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "icon.h"
+
+#include <grantlee/exception.h>
+#include <grantlee/parser.h>
+#include <grantlee/variable.h>
+
+#include <KIconLoader>
+
+IconTag::IconTag(QObject* parent)
+ : Grantlee::AbstractNodeFactory(parent)
+{
+}
+
+IconTag::~IconTag()
+{
+}
+
+Grantlee::Node *IconTag::getNode(const QString &tagContent, Grantlee::Parser *p) \
const +{
+ Q_UNUSED(p);
+
+ static QHash<QString, int> sizeOrGroupLookup
+ = { { QStringLiteral("desktop"), KIconLoader::Desktop },
+ { QStringLiteral("toolbar"), KIconLoader::Toolbar },
+ { QStringLiteral("maintoolbar"), KIconLoader::MainToolbar },
+ { QStringLiteral("small"), KIconLoader::Small },
+ { QStringLiteral("panel"), KIconLoader::Panel },
+ { QStringLiteral("dialog"), KIconLoader::Dialog },
+ { QStringLiteral("sizesmall"), KIconLoader::SizeSmall },
+ { QStringLiteral("sizesmallmedium"), KIconLoader::SizeSmallMedium },
+ { QStringLiteral("sizemedium"), KIconLoader::SizeMedium },
+ { QStringLiteral("sizelarge"), KIconLoader::SizeLarge },
+ { QStringLiteral("sizehuge"), KIconLoader::SizeHuge },
+ { QStringLiteral("sizeenormous"), KIconLoader::SizeEnormous }
+ };
+
+ const QStringList parts = smartSplit(tagContent);
+ const int partsSize = parts.size();
+ if (partsSize < 2) {
+ throw Grantlee::Exception(Grantlee::TagSyntaxError, QStringLiteral("icon tag \
takes at least 1 argument")); + }
+ if (partsSize > 4) {
+ throw Grantlee::Exception(Grantlee::TagSyntaxError, QStringLiteral("icon tag \
takes at maximum 3 arguments, %1 given").arg(partsSize)); + }
+
+ int sizeOrGroup = KIconLoader::Small;
+ QString altText;
+ if (partsSize >= 3) {
+ const QString sizeStr = parts.at(2);
+ bool ok = false;
+ // Try to convert to pixel size
+ sizeOrGroup = sizeStr.toInt(&ok);
+ if (!ok) {
+ // If failed, then try to map the string to one of tne enums
+ const auto size = sizeOrGroupLookup.constFind(sizeStr);
+ if (size == sizeOrGroupLookup.cend()) {
+ // If it's not a valid size string, assume it's an alt text
+ altText = sizeStr;
+ } else {
+ sizeOrGroup = (*size);
+ }
+ }
+ }
+ if (partsSize == 4) {
+ altText = parts.at(3);
+ }
+
+ return new IconNode(parts.at(1), sizeOrGroup, altText);
+}
+
+
+
+IconNode::IconNode(QObject* parent)
+ : Grantlee::Node(parent)
+{
+}
+
+IconNode::IconNode(const QString &iconName, int sizeOrGroup, const QString &altText, \
QObject *parent) + : Grantlee::Node(parent)
+ , mIconName(iconName)
+ , mAltText(altText)
+ , mSizeOrGroup(sizeOrGroup)
+{
+}
+
+IconNode::~IconNode()
+{
+}
+
+void IconNode::render(Grantlee::OutputStream *stream, Grantlee::Context *c) const
+{
+ Q_UNUSED(c);
+
+ QString iconName = mIconName;
+ if (iconName.startsWith(QLatin1Char('"')) && \
iconName.endsWith(QLatin1Char('"'))) { + iconName = iconName.mid(1, \
iconName.size() - 2); + } else {
+ iconName = Grantlee::Variable(mIconName).resolve(c).toString();
+ }
+
+ QString altText;
+ if (!mAltText.isEmpty()) {
+ if (mAltText.startsWith(QLatin1Char('"')) && \
mAltText.endsWith(QLatin1Char('"'))) { + altText = mAltText.mid(1, \
mAltText.size() - 2); + } else {
+ const QVariant v = Grantlee::Variable(mAltText).resolve(c);
+ if (v.isValid()) {
+ if (v.canConvert<Grantlee::SafeString>()) {
+ altText = v.value<Grantlee::SafeString>().get();
+ } else {
+ altText = v.toString();
+ }
+ }
+ }
+ }
+
+ const QString html = QStringLiteral("<img src=\"file://%1\" align=\"top\" \
height=\"%2\" width=\"%2\" alt=\"%3\" title=\"%4\" />") + \
.arg(KIconLoader::global()->iconPath(iconName, mSizeOrGroup)) + \
.arg(mSizeOrGroup < KIconLoader::LastGroup ? + \
IconSize(static_cast<KIconLoader::Group>(mSizeOrGroup)) + \
: mSizeOrGroup) + .arg(altText.isEmpty() ? iconName : \
altText) + .arg(altText); // title is intentionally blank \
if no alt is provided + (*stream) << Grantlee::SafeString(html, \
Grantlee::SafeString::IsSafe); +}
diff --git a/src/grantlee_plugin/icon.h b/src/grantlee_plugin/icon.h
new file mode 100644
index 0000000..5b32802
--- /dev/null
+++ b/src/grantlee_plugin/icon.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2015 Daniel Vrátil <dvratil@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef ICON_H
+#define ICON_H
+
+#include <grantlee/node.h>
+
+/**
+ * @name icon tag
+ * @brief Provides {% icon %} tag for inserting themed icons
+ *
+ * The syntax is:
+ * @code
+ * {% icon "icon-name"|var-with-icon-name [ sizeOrGroup ] [ alt text ] %}
+ * @endcode
+ *
+ * Where @p icon-name is a string literal with icon name, @p var-with-icon-name
+ * is a variable that contains a string with the icon name. @p sizeOrGrop is
+ * one of the KIconLoader::Group or KIconLoader::StdSizes enum values. The value
+ * is case-insensitive.
+ *
+ * The tag generates a full <img> HTML code:
+ * @code
+ * <img src="/usr/share/icons/[theme]/[type]/[size]/[icon-name].png" width="[width]" \
height="[height]"> + * @endcode
+ *
+ * The full path to the icon is resolved using KIconLoader::iconPath(). The
+ * @p width and @p height attributes are calculated based on current settings
+ * for icon sizes in KDE.
+ *
+ * @note Support for nested variables inside tags is non-standard for Grantlee
+ * tags, but makes it easier to use {% icon %} in sub-templates.
+ */
+
+
+class IconTag : public Grantlee::AbstractNodeFactory
+{
+public:
+ explicit IconTag(QObject *parent = Q_NULLPTR);
+ ~IconTag();
+
+ Grantlee::Node *getNode(const QString &tagContent, Grantlee::Parser *p) const \
Q_DECL_OVERRIDE; +};
+
+class IconNode : public Grantlee::Node
+{
+ Q_OBJECT
+public:
+ explicit IconNode(QObject *parent = Q_NULLPTR);
+ IconNode(const QString &iconName, int sizeOrGroup, const QString &altText, \
QObject *parent = Q_NULLPTR); + ~IconNode();
+
+ void render(Grantlee::OutputStream *stream, Grantlee::Context *c) const \
Q_DECL_OVERRIDE; +
+private:
+ QString mIconName;
+ QString mAltText;
+ int mSizeOrGroup;
+};
+
+
+#endif // ICON_H
diff --git a/src/grantlee_plugin/kcalendargrantleeplugin.cpp \
b/src/grantlee_plugin/kcalendargrantleeplugin.cpp new file mode 100644
index 0000000..8f39754
--- /dev/null
+++ b/src/grantlee_plugin/kcalendargrantleeplugin.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 Daniel Vrátil <dvratil@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "kcalendargrantleeplugin.h"
+#include "icon.h"
+#include "datetimefilters.h"
+
+KCalendarGrantleePlugin::KCalendarGrantleePlugin(QObject *parent)
+ : QObject(parent)
+ , Grantlee::TagLibraryInterface()
+{
+}
+
+KCalendarGrantleePlugin::~KCalendarGrantleePlugin()
+{
+}
+
+QHash<QString, Grantlee::AbstractNodeFactory *> \
KCalendarGrantleePlugin::nodeFactories(const QString &name) +{
+ Q_UNUSED(name);
+
+ QHash<QString, Grantlee::AbstractNodeFactory *> nodeFactories;
+ nodeFactories[QStringLiteral("icon")] = new IconTag();
+
+ return nodeFactories;
+}
+
+QHash<QString, Grantlee::Filter *> KCalendarGrantleePlugin::filters(const QString& \
name) +{
+ Q_UNUSED(name);
+
+ QHash<QString, Grantlee::Filter *> filters;
+ filters[QStringLiteral("kdate")] = new KDateFilter();
+ filters[QStringLiteral("ktime")] = new KTimeFilter();
+ filters[QStringLiteral("kdatetime")] = new KDateTimeFilter();
+
+ return filters;
+}
diff --git a/src/grantlee_plugin/kcalendargrantleeplugin.h \
b/src/grantlee_plugin/kcalendargrantleeplugin.h new file mode 100644
index 0000000..d6c1e14
--- /dev/null
+++ b/src/grantlee_plugin/kcalendargrantleeplugin.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 Daniel Vrátil <dvratil@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef KCALENDARGRANTLEEPLUGIN_H
+#define KCALENDARGRANTLEEPLUGIN_H
+
+#include <grantlee/taglibraryinterface.h>
+
+class KCalendarGrantleePlugin : public QObject
+ , public Grantlee::TagLibraryInterface
+{
+ Q_OBJECT
+ Q_INTERFACES(Grantlee::TagLibraryInterface)
+ Q_PLUGIN_METADATA(IID "org.kde.KCalendarGrantleePlugin")
+
+public:
+ explicit KCalendarGrantleePlugin(QObject *parent = Q_NULLPTR);
+ ~KCalendarGrantleePlugin();
+
+ QHash<QString, Grantlee::Filter *> filters(const QString &name) \
Q_DECL_OVERRIDE; + QHash<QString, Grantlee::AbstractNodeFactory *> \
nodeFactories(const QString &name) Q_DECL_OVERRIDE; +};
+
+#endif // KCALENDARGRANTLEEPLUGIN_H
diff --git a/src/grantleeki18nlocalizer.cpp b/src/grantleeki18nlocalizer.cpp
new file mode 100644
index 0000000..21a3c79
--- /dev/null
+++ b/src/grantleeki18nlocalizer.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2015 Daniel Vrátil <dvratil@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "grantleeki18nlocalizer_p.h"
+#include "kcalutils_debug.h"
+
+#include <QLocale>
+#include <QDate>
+
+#include <grantlee/safestring.h>
+
+#include <KLocalizedString>
+
+GrantleeKi18nLocalizer::GrantleeKi18nLocalizer()
+ : Grantlee::QtLocalizer()
+{
+}
+
+GrantleeKi18nLocalizer::~GrantleeKi18nLocalizer()
+{
+}
+
+
+QString GrantleeKi18nLocalizer::processArguments(const KLocalizedString &kstr,
+ const QVariantList &arguments) \
const +{
+ KLocalizedString str = kstr;
+ for (auto iter = arguments.cbegin(), end = arguments.cend(); iter != end; \
++iter) { + switch (iter->type()) {
+ case QVariant::String:
+ str = str.subs(iter->toString());
+ break;
+ case QVariant::Int:
+ str = str.subs(iter->toInt());
+ break;
+ case QVariant::UInt:
+ str = str.subs(iter->toUInt());
+ break;
+ case QVariant::LongLong:
+ str = str.subs(iter->toLongLong());
+ break;
+ case QVariant::ULongLong:
+ str = str.subs(iter->toULongLong());
+ break;
+ case QVariant::Char:
+ str = str.subs(iter->toChar());
+ break;
+ case QVariant::Double:
+ str = str.subs(iter->toDouble());
+ break;
+ case QVariant::UserType:
+ if (iter->canConvert<Grantlee::SafeString>()) {
+ str = str.subs(iter->value<Grantlee::SafeString>().get());
+ break;
+ }
+ // fall-through
+ default:
+ qCWarning(KCALUTILS_LOG) << "Unknown type" << iter->typeName() << "(" << \
iter->type() << ")"; + break;
+ }
+ }
+
+ // Return localized in the currenctly active locale
+ return str.toString({ currentLocale() });
+}
+
+QString GrantleeKi18nLocalizer::localizeContextString(const QString &string, const \
QString &context, const QVariantList &arguments) const +{
+ const KLocalizedString str = kxi18nc(qPrintable(context), qPrintable(string));
+ return processArguments(str, arguments);
+}
+
+QString GrantleeKi18nLocalizer::localizeString(const QString &string, const \
QVariantList &arguments) const +{
+ const KLocalizedString str = kxi18n(qPrintable(string));
+ return processArguments(str, arguments);
+}
+
+QString GrantleeKi18nLocalizer::localizePluralContextString(const QString &string, \
const QString &pluralForm, + \
const QString &context, const QVariantList &arguments) const +{
+ const KLocalizedString str = kxi18ncp(qPrintable(context), qPrintable(string), \
qPrintable(pluralForm)); + return processArguments(str, arguments);
+}
+
+QString GrantleeKi18nLocalizer::localizePluralString(const QString &string, const \
QString &pluralForm, + const \
QVariantList &arguments) const +{
+ const KLocalizedString str = kxi18np(qPrintable(string), \
qPrintable(pluralForm)); + return processArguments(str, arguments);
+}
+
+QString GrantleeKi18nLocalizer::localizeMonetaryValue(qreal value, const QString \
¤cySymbol) const +{
+ return QLocale(currentLocale()).toCurrencyString(value, currencySymbol);
+}
diff --git a/src/grantleeki18nlocalizer_p.h b/src/grantleeki18nlocalizer_p.h
new file mode 100644
index 0000000..edd1d90
--- /dev/null
+++ b/src/grantleeki18nlocalizer_p.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 Daniel Vrátil <dvratil@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef GRANTLEEKI18NLOCALIZER_H
+#define GRANTLEEKI18NLOCALIZER_H
+
+#include <grantlee/qtlocalizer.h>
+
+#include <QLocale>
+
+class KLocalizedString;
+
+class GrantleeKi18nLocalizer : public Grantlee::QtLocalizer
+{
+public:
+ explicit GrantleeKi18nLocalizer();
+ ~GrantleeKi18nLocalizer();
+
+ // Only reimplement string localization to use KLocalizedString instead of
+ // tr(), the remaining methods use QLocale internally, so we can reuse them
+ QString localizeContextString(const QString &string, const QString &context,
+ const QVariantList &arguments) const \
Q_DECL_OVERRIDE; + QString localizeString(const QString &string, const \
QVariantList &arguments) const Q_DECL_OVERRIDE; + QString \
localizePluralContextString(const QString &string, const QString &pluralForm, + \
const QString &context, const QVariantList &arguments) const Q_DECL_OVERRIDE; + \
QString localizePluralString(const QString &string, const QString &pluralForm, + \
const QVariantList &arguments) const Q_DECL_OVERRIDE; +
+ // Only exception, Grantlee's implementation is not using QLocale for this
+ // for some reason
+ QString localizeMonetaryValue(qreal value, const QString ¤ctCode) const \
Q_DECL_OVERRIDE; +
+private:
+ QString processArguments(const KLocalizedString &str,
+ const QVariantList &arguments) const;
+};
+
+#endif // GRANTLEEKI18NLOCALIZER_H
diff --git a/src/grantleetemplatemanager.cpp b/src/grantleetemplatemanager.cpp
new file mode 100644
index 0000000..fa81fd2
--- /dev/null
+++ b/src/grantleetemplatemanager.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2015 Daniel Vrátil <dvratil@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "grantleetemplatemanager_p.h"
+#include "grantleeki18nlocalizer_p.h"
+
+#include <QString>
+#include <QStandardPaths>
+#include <QDebug>
+
+#include <grantlee/engine.h>
+#include <grantlee/template.h>
+#include <grantlee/templateloader.h>
+
+#include <KLocalizedString>
+
+GrantleeTemplateManager *GrantleeTemplateManager::sInstance = Q_NULLPTR;
+
+GrantleeTemplateManager::GrantleeTemplateManager()
+ : mEngine(new Grantlee::Engine)
+ , mLoader(new Grantlee::FileSystemTemplateLoader)
+ , mLocalizer(new GrantleeKi18nLocalizer)
+{
+ const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, \
QStringLiteral("kcalendar/templates"), + \
QStandardPaths::LocateDirectory); + if (path.isEmpty()) {
+ qFatal("Cannot find KCalendarUtils templates, check your instalation");
+ }
+
+ mLoader->setTemplateDirs({ path });
+ mLoader->setTheme(QStringLiteral("default"));
+ mEngine->addTemplateLoader(mLoader);
+ mEngine->addDefaultLibrary(QStringLiteral("grantlee_i18ntags"));
+ mEngine->addDefaultLibrary(QStringLiteral("kcalendar_grantlee_plugin"));
+ mEngine->setSmartTrimEnabled(true);
+}
+
+GrantleeTemplateManager::~GrantleeTemplateManager()
+{
+ delete mEngine;
+}
+
+GrantleeTemplateManager * GrantleeTemplateManager::instance()
+{
+ if (!sInstance) {
+ sInstance = new GrantleeTemplateManager;
+ }
+ return sInstance;
+}
+
+void GrantleeTemplateManager::setTemplatePath(const QString &path)
+{
+ mLoader->setTemplateDirs({ path });
+ mLoader->setTheme(QString());
+}
+
+void GrantleeTemplateManager::setPluginPath(const QString &path)
+{
+ QStringList pluginPaths = mEngine->pluginPaths();
+ pluginPaths.prepend(path);
+ mEngine->setPluginPaths(pluginPaths);
+}
+
+Grantlee::Context GrantleeTemplateManager::createContext(const QVariantHash &hash) \
const +{
+ Grantlee::Context ctx;
+ ctx.insert(QStringLiteral("incidence"), hash);
+ ctx.setLocalizer(mLocalizer);
+ return ctx;
+}
+
+QString GrantleeTemplateManager::errorTemplate(const QString &reason,
+ const QString &origTemplateName,
+ const Grantlee::Template &failedTemplate) \
const +{
+ Grantlee::Template tpl = mEngine->newTemplate(
+ QStringLiteral("<h1>{{ error }}</h1>\n"
+ "<b>%1:</b> {{ templateName }}<br>\n"
+ "<b>%2:</b> {{ errorMessage }}")
+ .arg(i18n("Template"))
+ .arg(i18n("Error message")),
+ QStringLiteral("TemplateError"));
+
+ Grantlee::Context ctx = createContext();
+ ctx.insert(QStringLiteral("error"), reason);
+ ctx.insert(QStringLiteral("templateName"), origTemplateName);
+ ctx.insert(QStringLiteral("errorMessage"), failedTemplate->errorString());
+ return tpl->render(&ctx);
+}
+
+QString GrantleeTemplateManager::render(const QString &templateName, const \
QVariantHash &data) const +{
+ if (!mLoader->canLoadTemplate(templateName)) {
+ qWarning() << "Cannot load template" << templateName << ", please check your \
installation"; + return QString();
+ }
+
+ Grantlee::Template tpl = mLoader->loadByName(templateName, mEngine);
+ if (tpl->error()) {
+ return errorTemplate(i18n("Template parsing error"), templateName, tpl);
+ }
+
+ Grantlee::Context ctx = createContext(data);
+ const QString result = tpl->render(&ctx);
+ if (tpl->error()) {
+ return errorTemplate(i18n("Template rendering error"), templateName, tpl);
+ }
+
+ return result;
+}
diff --git a/src/grantleetemplatemanager_p.h b/src/grantleetemplatemanager_p.h
new file mode 100644
index 0000000..473d4a2
--- /dev/null
+++ b/src/grantleetemplatemanager_p.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 Daniel Vrátil <dvratil@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef GRANTLEETEMPLATEMANAGER_H_P
+#define GRANTLEETEMPLATEMANAGER_H_P
+
+#include <QSharedPointer>
+
+namespace Grantlee {
+class Engine;
+class FileSystemTemplateLoader;
+class TemplateImpl;
+class Context;
+typedef QSharedPointer<TemplateImpl> Template;
+}
+
+class QString;
+class GrantleeKi18nLocalizer;
+
+class GrantleeTemplateManager
+{
+public:
+ ~GrantleeTemplateManager();
+
+ static GrantleeTemplateManager *instance();
+
+ void setTemplatePath(const QString &path);
+ void setPluginPath(const QString &path);
+
+ QString render(const QString &templateName, const QVariantHash &data) const;
+
+private:
+ GrantleeTemplateManager();
+
+ QString errorTemplate(const QString &reason,
+ const QString &origTemplateName,
+ const Grantlee::Template &failedTemplate) const;
+ Grantlee::Context createContext(const QVariantHash &hash = QVariantHash()) \
const; +
+ Grantlee::Engine *mEngine;
+ QSharedPointer<Grantlee::FileSystemTemplateLoader> mLoader;
+ QSharedPointer<GrantleeKi18nLocalizer> mLocalizer;
+
+ static GrantleeTemplateManager *sInstance;
+};
+
+#endif // TEMPLATEMANAGER_H_P
diff --git a/src/incidenceformatter.cpp b/src/incidenceformatter.cpp
index c58e961..052c042 100644
--- a/src/incidenceformatter.cpp
+++ b/src/incidenceformatter.cpp
@@ -35,6 +35,7 @@
*/
#include "incidenceformatter.h"
#include "stringify.h"
+#include "grantleetemplatemanager_p.h"
#include <kcalcore/event.h>
#include <kcalcore/freebusy.h>
@@ -274,34 +275,32 @@ static QString firstAttendeeName(const Incidence::Ptr \
&incidence, const QString return name;
}
-static QString rsvpStatusIconPath(Attendee::PartStat status)
+static QString rsvpStatusIconName(Attendee::PartStat status)
{
- QString iconPath;
switch (status) {
case Attendee::Accepted:
- iconPath = KIconLoader::global()->iconPath(QStringLiteral("dialog-ok-apply"), \
KIconLoader::Small); + return QStringLiteral("dialog-ok-apply");
break;
case Attendee::Declined:
- iconPath = KIconLoader::global()->iconPath(QStringLiteral("dialog-cancel"), \
KIconLoader::Small); + return QStringLiteral("dialog-cancel");
break;
case Attendee::NeedsAction:
- iconPath = KIconLoader::global()->iconPath(QStringLiteral("help-about"), \
KIconLoader::Small); + return QStringLiteral("help-about");
break;
case Attendee::InProcess:
- iconPath = KIconLoader::global()->iconPath(QStringLiteral("help-about"), \
KIconLoader::Small); + return QStringLiteral("help-about");
break;
case Attendee::Tentative:
- iconPath = KIconLoader::global()->iconPath(QStringLiteral("dialog-ok"), \
KIconLoader::Small); + return QStringLiteral("dialog-ok");
break;
case Attendee::Delegated:
- iconPath = KIconLoader::global()->iconPath(QStringLiteral("mail-forward"), \
KIconLoader::Small); + return QStringLiteral("mail-forward");
break;
case Attendee::Completed:
- iconPath = KIconLoader::global()->iconPath(QStringLiteral("mail-mark-read"), \
KIconLoader::Small); + return QStringLiteral("mail-mark-read");
default:
- break;
+ return QString();
}
- return iconPath;
}
//@endcond
@@ -311,39 +310,41 @@ static QString rsvpStatusIconPath(Attendee::PartStat status)
*******************************************************************/
//@cond PRIVATE
-static QString displayViewFormatPerson(const QString &email, const QString &name,
- const QString &uid, const QString &iconPath)
+static QVariantHash displayViewFormatPerson(const QString &email, const QString \
&name, + const QString &uid, const QString \
&iconName) {
// Search for new print name or uid, if needed.
QPair<QString, QString> s = searchNameAndUid(email, name, uid);
const QString printName = s.first;
const QString printUid = s.second;
- QString personString;
- if (!iconPath.isEmpty()) {
- personString += QLatin1String("<img valign=\"top\" src=\"") + iconPath + \
QLatin1String("\">") + QLatin1String(" ");
- }
-
- // Make the uid link
- if (!printUid.isEmpty()) {
- personString += htmlAddUidLink(email, printName, printUid);
- } else {
- // No UID, just show some text
- personString += (printName.isEmpty() ? email : printName);
- }
+ QVariantHash personData;
+ personData[QStringLiteral("icon")] = iconName;
+ personData[QStringLiteral("uid")] = printUid;
+ personData[QStringLiteral("name")] = printName;
+ personData[QStringLiteral("email")] = email;
// Make the mailto link
if (!email.isEmpty()) {
- personString += QLatin1String(" ") + htmlAddMailtoLink(email, \
printName); + Person person(name, email);
+ QString path = person.fullName().simplified();
+ if (path.isEmpty() || path.startsWith(QLatin1Char('"'))) {
+ path = email;
+ }
+ QUrl mailto;
+ mailto.setScheme(QStringLiteral("mailto"));
+ mailto.setPath(path);
+
+ personData[QStringLiteral("mailto")] = mailto.url();
}
- return personString;
+ return personData;
}
-static QString displayViewFormatPerson(const QString &email, const QString &name,
- const QString &uid, Attendee::PartStat \
status) +static QVariantHash displayViewFormatPerson(const QString &email, const \
QString &name, + const QString &uid, \
Attendee::PartStat status) {
- return displayViewFormatPerson(email, name, uid, rsvpStatusIconPath(status));
+ return displayViewFormatPerson(email, name, uid, rsvpStatusIconName(status));
}
static bool incOrganizerOwnsCalendar(const Calendar::Ptr &calendar,
@@ -358,32 +359,28 @@ static bool incOrganizerOwnsCalendar(const Calendar::Ptr \
&calendar,
static QString displayViewFormatDescription(const Incidence::Ptr &incidence)
{
- QString tmpStr;
if (!incidence->description().isEmpty()) {
- QString descStr;
if (!incidence->descriptionIsRich() &&
!incidence->description().startsWith(QLatin1String("<!DOCTYPE \
HTML"))) {
- descStr = string2HTML(incidence->description());
+ return string2HTML(incidence->description());
+ } else if (!incidence->description().startsWith(QLatin1String("<!DOCTYPE \
HTML"))) { + return incidence->richDescription();
} else {
- if (!incidence->description().startsWith(QLatin1String("<!DOCTYPE \
HTML"))) {
- descStr = incidence->richDescription();
- } else {
- descStr = incidence->description();
- }
+ return incidence->description();
}
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") + i18n("Description:") + \
QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") + descStr + QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
}
- return tmpStr;
+
+ return QString();
}
-static QString displayViewFormatAttendeeRoleList(const Incidence::Ptr &incidence, \
Attendee::Role role,
- bool showStatus)
+static QVariantList displayViewFormatAttendeeRoleList(const Incidence::Ptr \
&incidence, + Attendee::Role \
role, + bool showStatus)
{
- QString tmpStr;
+ QVariantList attendeeDataList;
+ attendeeDataList.reserve(incidence->attendeeCount());
+
Attendee::List::ConstIterator it;
Attendee::List attendees = incidence->attendees();
@@ -397,26 +394,50 @@ static QString displayViewFormatAttendeeRoleList(const \
Incidence::Ptr &incidence // skip attendee that is also the organizer
continue;
}
- tmpStr += displayViewFormatPerson(a->email(), a->name(), a->uid(),
- showStatus ? a->status() : \
Attendee::None); + QVariantHash attendeeData = \
displayViewFormatPerson(a->email(), a->name(), a->uid(), + \
showStatus ? a->status() : Attendee::None); if (!a->delegator().isEmpty()) {
- tmpStr += i18n(" (delegated by %1)", a->delegator());
+ attendeeData[QStringLiteral("delegator")] = a->delegator();
}
if (!a->delegate().isEmpty()) {
- tmpStr += i18n(" (delegated to %1)", a->delegate());
+ attendeeData[QStringLiteral("delegate")] = a->delegate();
}
- tmpStr += QLatin1String("<br>");
- }
- if (tmpStr.endsWith(QLatin1String("<br>"))) {
- tmpStr.chop(4);
+ if (showStatus) {
+ switch (a->status()) {
+ case Attendee::NeedsAction:
+ attendeeData[QStringLiteral("status")] = i18n("Needs action");
+ break;
+ case Attendee::Accepted:
+ attendeeData[QStringLiteral("status")] = i18n("Accepted");
+ break;
+ case Attendee::Declined:
+ attendeeData[QStringLiteral("status")] = i18n("Declined");
+ break;
+ case Attendee::Tentative:
+ attendeeData[QStringLiteral("status")] = i18n("Tentative");
+ break;
+ case Attendee::Delegated:
+ attendeeData[QStringLiteral("status")] = i18n("Delegated");
+ break;
+ case Attendee::Completed:
+ attendeeData[QStringLiteral("status")] = i18n("Completed");
+ break;
+ case Attendee::InProcess:
+ attendeeData[QStringLiteral("status")] = i18n("In Process");
+ break;
+ case Attendee::None:
+ break;
+ }
+ }
+
+ attendeeDataList << attendeeData;
}
- return tmpStr;
+
+ return attendeeDataList;
}
-static QString displayViewFormatAttendees(const Calendar::Ptr &calendar, const \
Incidence::Ptr &incidence) +static QVariantHash displayViewFormatOrganizer(const \
Incidence::Ptr &incidence) {
- QString tmpStr, str;
-
// Add organizer link
int attendeeCount = incidence->attendees().count();
if (attendeeCount > 1 ||
@@ -426,67 +447,24 @@ static QString displayViewFormatAttendees(const Calendar::Ptr \
&calendar, const I
QPair<QString, QString> s = \
searchNameAndUid(incidence->organizer()->email(), incidence->organizer()->name(),
QString());
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") + i18n("Organizer:") + \
QLatin1String("</b></td>");
- const QString iconPath =
- KIconLoader::global()->iconPath(QStringLiteral("meeting-organizer"), \
KIconLoader::Small);
- tmpStr += QLatin1String("<td>") + \
displayViewFormatPerson(incidence->organizer()->email(),
- s.first, s.second, iconPath) +
- QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- }
-
- // Show the attendee status if the incidence's organizer owns the resource \
calendar,
- // which means they are running the show and have all the up-to-date response \
info.
- bool showStatus = incOrganizerOwnsCalendar(calendar, incidence);
-
- // Add "chair"
- str = displayViewFormatAttendeeRoleList(incidence, Attendee::Chair, showStatus);
- if (!str.isEmpty()) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") + i18n("Chair:") + \
QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") + str + QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
+ return displayViewFormatPerson(incidence->organizer()->email(), s.first, \
s.second, + \
QStringLiteral("meeting-organizer")); }
- // Add required participants
- str = displayViewFormatAttendeeRoleList(incidence, Attendee::ReqParticipant, \
showStatus);
- if (!str.isEmpty()) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") + i18n("Required Participants:") + \
QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") + str + QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- }
-
- // Add optional participants
- str = displayViewFormatAttendeeRoleList(incidence, Attendee::OptParticipant, \
showStatus);
- if (!str.isEmpty()) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") + i18n("Optional Participants:") + \
QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") + str + QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- }
-
- // Add observers
- str = displayViewFormatAttendeeRoleList(incidence, Attendee::NonParticipant, \
showStatus);
- if (!str.isEmpty()) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") + i18n("Observers:") + \
QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") + str + QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- }
-
- return tmpStr;
+ return QVariantHash();
}
-static QString displayViewFormatAttachments(const Incidence::Ptr &incidence)
+static QVariantList displayViewFormatAttachments(const Incidence::Ptr &incidence)
{
- QString tmpStr;
- Attachment::List as = incidence->attachments();
- Attachment::List::ConstIterator it;
+ const Attachment::List as = incidence->attachments();
+
+ QVariantList dataList;
+ dataList.reserve(as.count());
+
int count = 0;
- for (it = as.constBegin(); it != as.constEnd(); ++it) {
+ for (auto it = as.cbegin(), end = as.cend(); it != end; ++it) {
count++;
+ QVariantHash attData;
if ((*it)->isUri()) {
QString name;
if ((*it)->uri().startsWith(QLatin1String("kmail:"))) {
@@ -498,91 +476,53 @@ static QString displayViewFormatAttachments(const \
Incidence::Ptr &incidence) name = (*it)->label();
}
}
- tmpStr += htmlAddLink((*it)->uri(), name);
+ attData[QStringLiteral("uri")] = (*it)->uri();
+ attData[QStringLiteral("label")] = name;
} else {
- tmpStr += htmlAddLink(QStringLiteral("ATTACH:%1").
- \
arg(QString::fromUtf8((*it)->label().toUtf8().toBase64())),
- (*it)->label());
- }
- if (count < as.count()) {
- tmpStr += QLatin1String("<br>");
+ attData[QStringLiteral("uri")] = \
QStringLiteral("ATTACH:%1").arg(QString::fromUtf8((*it)->label().toUtf8().toBase64()));
+ attData[QStringLiteral("label")] = (*it)->label();
}
+ dataList << attData;
}
- return tmpStr;
+ return dataList;
}
-static QString displayViewFormatCategories(const Incidence::Ptr &incidence)
-{
- // We do not use Incidence::categoriesStr() since it does not have whitespace
- return incidence->categories().join(QStringLiteral(", "));
-}
-
-static QString displayViewFormatCreationDate(const Incidence::Ptr &incidence, const \
KDateTime::Spec &spec)
-{
- KDateTime kdt = incidence->created().toTimeSpec(spec);
- return i18n("Creation date: %1", dateTimeToString(incidence->created(), false, \
true, spec));
-}
-
-static QString displayViewFormatBirthday(const Event::Ptr &event)
+static QVariantHash displayViewFormatBirthday(const Event::Ptr &event)
{
if (!event) {
- return QString();
- }
- if (event->customProperty("KABC", "BIRTHDAY") != QLatin1String("YES") &&
- event->customProperty("KABC", "ANNIVERSARY") != QLatin1String("YES")) {
- return QString();
+ return QVariantHash();
}
+ // It's callees duty to ensure this
+ Q_ASSERT(event->customProperty("KABC", "BIRTHDAY") == QLatin1String("YES") ||
+ event->customProperty("KABC", "ANNIVERSARY") == QLatin1String("YES"));
+
const QString uid_1 = event->customProperty("KABC", "UID-1");
const QString name_1 = event->customProperty("KABC", "NAME-1");
const QString email_1 = event->customProperty("KABC", "EMAIL-1");
- KCalCore::Person::Ptr p = Person::fromFullName(email_1);
- const QString tmpStr = displayViewFormatPerson(p->email(), name_1, uid_1, \
QString());
- return tmpStr;
+ const KCalCore::Person::Ptr p = Person::fromFullName(email_1);
+ return displayViewFormatPerson(p->email(), name_1, uid_1, QString());
}
-static QString displayViewFormatHeader(const Incidence::Ptr &incidence)
+static QVariantHash incidenceTemplateHeader(const Incidence::Ptr &incidence)
{
- QString tmpStr = QStringLiteral("<table><tr>");
-
- // show icons
- KIconLoader *iconLoader = KIconLoader::global();
- tmpStr += QLatin1String("<td>");
-
+ QVariantHash incidenceData;
QString iconPath;
if (incidence->customProperty("KABC", "BIRTHDAY") == QLatin1String("YES")) {
- iconPath = iconLoader->iconPath(QStringLiteral("view-calendar-birthday"), \
KIconLoader::Small); + incidenceData[QStringLiteral("icon")] = \
QStringLiteral("view-calendar-birthday");
} else if (incidence->customProperty("KABC", "ANNIVERSARY") == \
QLatin1String("YES")) {
- iconPath = iconLoader->iconPath(QStringLiteral("view-calendar-wedding-anniversary"), \
KIconLoader::Small); + incidenceData[QStringLiteral("icon")] = \
QStringLiteral("view-calendar-wedding-anniversary"); } else {
- iconPath = iconLoader->iconPath(incidence->iconName(), KIconLoader::Small);
+ incidenceData[QStringLiteral("icon")] = incidence->iconName();
}
- tmpStr += QLatin1String("<img valign=\"top\" src=\"") + iconPath + \
QLatin1String("\">");
- if (incidence->hasEnabledAlarms()) {
- tmpStr += QLatin1String("<img valign=\"top\" src=\"") +
- iconLoader->iconPath(QStringLiteral("preferences-desktop-notification-bell"), \
KIconLoader::Small) +
- QLatin1String("\">");
- }
- if (incidence->recurs()) {
- tmpStr += QLatin1String("<img valign=\"top\" src=\"") +
- iconLoader->iconPath(QStringLiteral("edit-redo"), \
KIconLoader::Small) +
- QLatin1String("\">");
- }
- if (incidence->isReadOnly()) {
- tmpStr += QLatin1String("<img valign=\"top\" src=\"") +
- iconLoader->iconPath(QStringLiteral("object-locked"), \
KIconLoader::Small) +
- QLatin1String("\">");
- }
- tmpStr += QLatin1String("</td>");
-
- tmpStr += QLatin1String("<td>");
- tmpStr += QLatin1String("<b><u>") + incidence->richSummary() + \
QLatin1String("</u></b>");
- tmpStr += QLatin1String("</td>");
-
- tmpStr += QLatin1String("</tr></table>");
+ incidenceData[QStringLiteral("hasEnabledAlarms")] = \
incidence->hasEnabledAlarms(); + incidenceData[QStringLiteral("recurs")] = \
incidence->recurs(); + incidenceData[QStringLiteral("isReadOnly")] = \
incidence->isReadOnly(); + incidenceData[QStringLiteral("summary")] = \
incidence->summary(); + incidenceData[QStringLiteral("allDay")] = \
incidence->allDay();
- return tmpStr;
+ return incidenceData;
}
static QString displayViewFormatEvent(const Calendar::Ptr &calendar, const QString \
&sourceName, @@ -593,26 +533,11 @@ static QString displayViewFormatEvent(const \
Calendar::Ptr &calendar, const QStri return QString();
}
- QString tmpStr = displayViewFormatHeader(event);
-
- tmpStr += QLatin1String("<table>");
- tmpStr += QLatin1String("<col width=\"25%\"/>");
- tmpStr += QLatin1String("<col width=\"75%\"/>");
+ QVariantHash incidence = incidenceTemplateHeader(event);
- const QString calStr = calendar ? resourceString(calendar, event) : sourceName;
- if (!calStr.isEmpty()) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") + i18n("Calendar:") + \
QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") + calStr + QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- }
+ incidence[QStringLiteral("calendar")] = calendar ? resourceString(calendar, \
event) : sourceName; + incidence[QStringLiteral("location")] = \
event->richLocation();
- if (!event->location().isEmpty()) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") + i18n("Location:") + \
QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") + event->richLocation() + \
QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- }
KDateTime startDt = event->dtStart();
KDateTime endDt = event->dtEnd();
@@ -632,133 +557,47 @@ static QString displayViewFormatEvent(const Calendar::Ptr \
&calendar, const QStri }
}
- tmpStr += QLatin1String("<tr>");
- if (event->allDay()) {
- if (event->isMultiDay()) {
- tmpStr += QLatin1String("<td><b>") + i18n("Date:") + \
QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") +
- i18nc("<beginTime> - <endTime>", "%1 - %2",
- dateToString(startDt, false, spec),
- dateToString(endDt, false, spec)) +
- QLatin1String("</td>");
- } else {
- tmpStr += QLatin1String("<td><b>") + i18n("Date:") + \
QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") +
- i18nc("date as string", "%1",
- dateToString(startDt, false, spec)) +
- QLatin1String("</td>");
- }
- } else {
- if (event->isMultiDay()) {
- tmpStr += QLatin1String("<td><b>") + i18n("Date:") + \
QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") +
- i18nc("<beginTime> - <endTime>", "%1 - %2",
- dateToString(startDt, false, spec),
- dateToString(endDt, false, spec)) +
- QLatin1String("</td>");
- } else {
- tmpStr += QLatin1String("<td><b>") + i18n("Date:") + \
QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") +
- i18nc("date as string", "%1",
- dateToString(startDt, false, spec)) +
- QLatin1String("</td>");
-
- tmpStr += QLatin1String("</tr><tr>");
- tmpStr += QLatin1String("<td><b>") + i18n("Time:") + \
QLatin1String("</b></td>");
- if (event->hasEndDate() && startDt != endDt) {
- tmpStr += QLatin1String("<td>") +
- i18nc("<beginTime> - <endTime>", "%1 - %2",
- timeToString(startDt, true, spec),
- timeToString(endDt, true, spec)) +
- QLatin1String("</td>");
- } else {
- tmpStr += QLatin1String("<td>") +
- timeToString(startDt, true, spec) +
- QLatin1String("</td>");
- }
- }
- }
- tmpStr += QLatin1String("</tr>");
-
- QString durStr = durationString(event);
- if (!durStr.isEmpty()) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") + i18n("Duration:") + \
QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") + durStr + QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
+ if (spec.isValid()) {
+ startDt = startDt.toTimeSpec(spec);
+ endDt = endDt.toTimeSpec(spec);
}
- if (event->recurs() || event->hasRecurrenceId()) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") + i18n("Recurrence:") + \
QLatin1String("</b></td>");
-
- QString str;
- if (event->hasRecurrenceId()) {
- str = i18n("Exception");
- } else {
- str = recurrenceString(event);
- }
+ incidence[QStringLiteral("isAllDay")] = event->allDay();
+ incidence[QStringLiteral("isMultiDay")] = event->isMultiDay();
+ incidence[QStringLiteral("startDate")] = startDt.date();
+ incidence[QStringLiteral("endDate")] = endDt.date();
+ incidence[QStringLiteral("startTime")] = startDt.time();
+ incidence[QStringLiteral("endTime")] = endDt.time();
+ incidence[QStringLiteral("duration")] = durationString(event);
+ incidence[QStringLiteral("isException")] = event->hasRecurrenceId();
+ incidence[QStringLiteral("recurrence")] = recurrenceString(event);
- tmpStr += QLatin1String("<td>") + str +
- QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
+ if (event->customProperty("KABC", "BIRTHDAY") == QLatin1String("YES")) {
+ incidence[QStringLiteral("birthday")] = displayViewFormatBirthday(event);
}
- const bool isBirthday = event->customProperty("KABC", "BIRTHDAY") == \
QLatin1String("YES");
- const bool isAnniversary = event->customProperty("KABC", "ANNIVERSARY") == \
QLatin1String("YES");
-
- if (isBirthday || isAnniversary) {
- tmpStr += QLatin1String("<tr>");
- if (isAnniversary) {
- tmpStr += QLatin1String("<td><b>") + i18n("Anniversary:") + \
QLatin1String("</b></td>");
- } else {
- tmpStr += QLatin1String("<td><b>") + i18n("Birthday:") + \
QLatin1String("</b></td>");
- }
- tmpStr += QLatin1String("<td>") + displayViewFormatBirthday(event) + \
QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- tmpStr += QLatin1String("</table>");
- return tmpStr;
+ if (event->customProperty("KABC", "ANNIVERSARY") == QLatin1String("YES")) {
+ incidence[QStringLiteral("anniversary")] = displayViewFormatBirthday(event);
}
- tmpStr += displayViewFormatDescription(event);
+ incidence[QStringLiteral("description")] = displayViewFormatDescription(event);
// TODO: print comments?
- int reminderCount = event->alarms().count();
- if (reminderCount > 0 && event->hasEnabledAlarms()) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") +
- i18np("Reminder:", "Reminders:", reminderCount) +
- QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") + \
reminderStringList(event).join(QStringLiteral("<br>")) + \
QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- }
-
- tmpStr += displayViewFormatAttendees(calendar, event);
+ incidence[QStringLiteral("reminders")] = reminderStringList(event);
- int categoryCount = event->categories().count();
- if (categoryCount > 0) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>");
- tmpStr += i18np("Category:", "Categories:", categoryCount) +
- QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") + displayViewFormatCategories(event) + \
QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- }
+ incidence[QStringLiteral("organizer")] = displayViewFormatOrganizer(event);
+ const bool showStatus = incOrganizerOwnsCalendar(calendar, event);
+ incidence[QStringLiteral("chair")] = displayViewFormatAttendeeRoleList(event, \
Attendee::Chair, showStatus); + incidence[QStringLiteral("requiredParticipants")] \
= displayViewFormatAttendeeRoleList(event, Attendee::ReqParticipant, showStatus); + \
incidence[QStringLiteral("optionalParticipants")] = \
displayViewFormatAttendeeRoleList(event, Attendee::OptParticipant, showStatus); + \
incidence[QStringLiteral("observers")] = displayViewFormatAttendeeRoleList(event, \
Attendee::NonParticipant, showStatus);
- int attachmentCount = event->attachments().count();
- if (attachmentCount > 0) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") +
- i18np("Attachment:", "Attachments:", attachmentCount) +
- QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") + displayViewFormatAttachments(event) + \
QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- }
- tmpStr += QLatin1String("</table>");
+ incidence[QStringLiteral("categories")] = event->categories();
- tmpStr += QLatin1String("<p><em>") + displayViewFormatCreationDate(event, spec) \
+ QLatin1String("</em>"); + incidence[QStringLiteral("attachments")] = \
displayViewFormatAttachments(event); + incidence[QStringLiteral("creationDate")] = \
event->created().toTimeSpec(spec).dateTime();
- return tmpStr;
+ return GrantleeTemplateManager::instance()->render(QStringLiteral("event.html"), \
incidence); }
static QString displayViewFormatTodo(const Calendar::Ptr &calendar, const QString \
&sourceName, @@ -770,26 +609,10 @@ static QString displayViewFormatTodo(const \
Calendar::Ptr &calendar, const QStrin return QString();
}
- QString tmpStr = displayViewFormatHeader(todo);
+ QVariantHash incidence = incidenceTemplateHeader(todo);
- tmpStr += QLatin1String("<table>");
- tmpStr += QLatin1String("<col width=\"25%\"/>");
- tmpStr += QLatin1String("<col width=\"75%\"/>");
-
- const QString calStr = calendar ? resourceString(calendar, todo) : sourceName;
- if (!calStr.isEmpty()) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") + i18n("Calendar:") + \
QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") + calStr + QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- }
-
- if (!todo->location().isEmpty()) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") + i18n("Location:") + \
QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") + todo->richLocation() + \
QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- }
+ incidence[QStringLiteral("calendar")] = calendar ? resourceString(calendar, \
todo) : sourceName; + incidence[QStringLiteral("location")] = \
todo->richLocation();
const bool hastStartDate = todo->hasStartDate();
const bool hasDueDate = todo->hasDueDate();
@@ -811,14 +634,10 @@ static QString displayViewFormatTodo(const Calendar::Ptr \
&calendar, const QStrin startDt.setDate(ocurrenceDueDate);
}
}
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") +
- i18nc("to-do start date/time", "Start:") +
- QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") +
- dateTimeToString(startDt, todo->allDay(), false, spec) +
- QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
+ if (spec.isValid()) {
+ startDt = startDt.toTimeSpec(spec);
+ }
+ incidence[QStringLiteral("startDate")] = startDt.dateTime();
}
if (hasDueDate) {
@@ -830,100 +649,42 @@ static QString displayViewFormatTodo(const Calendar::Ptr \
&calendar, const QStrin
dueDt.setDate(todo->recurrence()->getNextDateTime(kdt).date());
}
}
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") +
- i18nc("to-do due date/time", "Due:") +
- QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") +
- dateTimeToString(dueDt, todo->allDay(), false, spec) +
- QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- }
-
- QString durStr = durationString(todo);
- if (!durStr.isEmpty()) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") + i18n("Duration:") + \
QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") + durStr + QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- }
-
- if (todo->recurs() || todo->hasRecurrenceId()) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") + i18n("Recurrence:") + \
QLatin1String("</b></td>");
- QString str;
- if (todo->hasRecurrenceId()) {
- str = i18n("Exception");
- } else {
- str = recurrenceString(todo);
+ if (spec.isValid()) {
+ dueDt = dueDt.toTimeSpec(spec);
}
- tmpStr += QLatin1String("<td>") +
- str +
- QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- }
-
- tmpStr += displayViewFormatDescription(todo);
- // TODO: print comments?
-
- int reminderCount = todo->alarms().count();
- if (reminderCount > 0 && todo->hasEnabledAlarms()) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") +
- i18np("Reminder:", "Reminders:", reminderCount) +
- QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") + \
reminderStringList(todo).join(QStringLiteral("<br>")) + \
QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- }
-
- tmpStr += displayViewFormatAttendees(calendar, todo);
-
- int categoryCount = todo->categories().count();
- if (categoryCount > 0) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") +
- i18np("Category:", "Categories:", categoryCount) +
- QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") + displayViewFormatCategories(todo) + \
QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- }
-
- if (todo->priority() > 0) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") + i18n("Priority:") + \
QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>");
- tmpStr += QString::number(todo->priority());
- tmpStr += QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
+ incidence[QStringLiteral("dueDate")] = dueDt.dateTime();
}
- tmpStr += QLatin1String("<tr>");
- if (todo->isCompleted()) {
- tmpStr += QLatin1String("<td><b>") + i18nc("Completed: date", "Completed:") \
+ QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>");
- tmpStr += Stringify::todoCompletedDateTime(todo);
- } else {
- tmpStr += QLatin1String("<td><b>") + i18n("Percent Done:") + \
QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>");
- tmpStr += i18n("%1%", todo->percentComplete());
+ incidence[QStringLiteral("duration")] = durationString(todo);
+ incidence[QStringLiteral("isException")] = todo->hasRecurrenceId();
+ if (todo->recurs()) {
+ incidence[QStringLiteral("recurrence")] = recurrenceString(todo);
}
- tmpStr += QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- int attachmentCount = todo->attachments().count();
- if (attachmentCount > 0) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") +
- i18np("Attachment:", "Attachments:", attachmentCount) +
- QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") + displayViewFormatAttachments(todo) + \
QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- }
- tmpStr += QLatin1String("</table>");
+ incidence[QStringLiteral("description")] = displayViewFormatDescription(todo);
- tmpStr += QLatin1String("<p><em>") + displayViewFormatCreationDate(todo, spec) + \
QLatin1String("</em>"); + // TODO: print comments?
- return tmpStr;
+ incidence[QStringLiteral("reminders")] = reminderStringList(todo);
+
+ incidence[QStringLiteral("organizer")] = displayViewFormatOrganizer(todo);
+ const bool showStatus = incOrganizerOwnsCalendar(calendar, todo);
+ incidence[QStringLiteral("chair")] = displayViewFormatAttendeeRoleList(todo, \
Attendee::Chair, showStatus); + incidence[QStringLiteral("requiredParticipants")] \
= displayViewFormatAttendeeRoleList(todo, Attendee::ReqParticipant, showStatus); + \
incidence[QStringLiteral("optionalParticipants")] = \
displayViewFormatAttendeeRoleList(todo, Attendee::OptParticipant, showStatus); + \
incidence[QStringLiteral("observers")] = displayViewFormatAttendeeRoleList(todo, \
Attendee::NonParticipant, showStatus); +
+ incidence[QStringLiteral("categories")] = todo->categories();
+ incidence[QStringLiteral("priority")] = todo->priority();
+ if (todo->isCompleted()) {
+ incidence[QStringLiteral("completedDate")] = todo->completed().dateTime();
+ } else {
+ incidence[QStringLiteral("percent")] = todo->percentComplete();
+ }
+ incidence[QStringLiteral("attachments")] = displayViewFormatAttachments(todo);
+ incidence[QStringLiteral("creationDate")] = \
todo->created().toTimeSpec(spec).dateTime(); +
+ return GrantleeTemplateManager::instance()->render(QStringLiteral("todo.html"), \
incidence); }
static QString displayViewFormatJournal(const Calendar::Ptr &calendar, const QString \
&sourceName, @@ -933,44 +694,14 @@ static QString displayViewFormatJournal(const \
Calendar::Ptr &calendar, const QSt return QString();
}
- QString tmpStr = displayViewFormatHeader(journal);
+ QVariantHash incidence = incidenceTemplateHeader(journal);
+ incidence[QStringLiteral("calendar")] = calendar ? resourceString(calendar, \
journal) : sourceName; + incidence[QStringLiteral("date")] = \
journal->dtStart().toTimeSpec(spec).dateTime(); + \
incidence[QStringLiteral("description")] = displayViewFormatDescription(journal); + \
incidence[QStringLiteral("categories")] = journal->categories(); + \
incidence[QStringLiteral("creationDate")] = \
journal->created().toTimeSpec(spec).dateTime();
- tmpStr += QLatin1String("<table>");
- tmpStr += QLatin1String("<col width=\"25%\"/>");
- tmpStr += QLatin1String("<col width=\"75%\"/>");
-
- const QString calStr = calendar ? resourceString(calendar, journal) : \
sourceName;
- if (!calStr.isEmpty()) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") + i18n("Calendar:") + \
QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") + calStr + QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- }
-
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") + i18n("Date:") + QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") +
- dateToString(journal->dtStart(), false, spec) +
- QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
-
- tmpStr += displayViewFormatDescription(journal);
-
- int categoryCount = journal->categories().count();
- if (categoryCount > 0) {
- tmpStr += QLatin1String("<tr>");
- tmpStr += QLatin1String("<td><b>") +
- i18np("Category:", "Categories:", categoryCount) +
- QLatin1String("</b></td>");
- tmpStr += QLatin1String("<td>") + displayViewFormatCategories(journal) + \
QLatin1String("</td>");
- tmpStr += QLatin1String("</tr>");
- }
-
- tmpStr += QLatin1String("</table>");
-
- tmpStr += QLatin1String("<p><em>") + displayViewFormatCreationDate(journal, \
spec) + QLatin1String("</em>");
-
- return tmpStr;
+ return GrantleeTemplateManager::instance()->render(QStringLiteral("journal.html"), \
incidence); }
static QString displayViewFormatFreeBusy(const Calendar::Ptr &calendar, const \
QString &sourceName, @@ -982,23 +713,17 @@ static QString \
displayViewFormatFreeBusy(const Calendar::Ptr &calendar, const QS return QString();
}
- QString tmpStr(
- htmlAddTag(
- QStringLiteral("h2"), i18n("Free/Busy information for %1", \
fb->organizer()->fullName())));
-
- tmpStr += htmlAddTag(QStringLiteral("h4"),
- i18n("Busy times in date range %1 - %2:",
- dateToString(fb->dtStart(), true, spec),
- dateToString(fb->dtEnd(), true, spec)));
-
- QString text =
- htmlAddTag(QStringLiteral("em"),
- htmlAddTag(QStringLiteral("b"), i18nc("tag for busy periods \
list", "Busy:"))); + QVariantHash fbData;
+ fbData[QStringLiteral("organizer")] = fb->organizer()->fullName();
+ fbData[QStringLiteral("start")] = fb->dtStart().toTimeSpec(spec).date();
+ fbData[QStringLiteral("end")] = fb->dtEnd().toTimeSpec(spec).date();
Period::List periods = fb->busyPeriods();
- Period::List::iterator it;
- for (it = periods.begin(); it != periods.end(); ++it) {
- Period per = *it;
+ QVariantList periodsData;
+ periodsData.reserve(periods.size());
+ for ( auto it = periods.cbegin(), end = periods.cend(); it != end; ++it) {
+ const Period per = *it;
+ QVariantHash periodData;
if (per.hasDuration()) {
int dur = per.duration().asSeconds();
QString cont;
@@ -1013,26 +738,27 @@ static QString displayViewFormatFreeBusy(const Calendar::Ptr \
&calendar, const QS if (dur > 0) {
cont += i18ncp("seconds part of duration", "1 second", "%1 seconds", \
dur); }
- text += i18nc("startDate for duration", "%1 for %2",
- dateTimeToString(per.start(), false, true, spec),
- cont);
- text += QLatin1String("<br>");
+ periodData[QStringLiteral("dtStart")] = \
per.start().toTimeSpec(spec).dateTime(); + \
periodData[QStringLiteral("duration")] = cont; } else {
+ const KDateTime pStart = per.start().toTimeSpec(spec);
+ const KDateTime pEnd = per.end().toTimeSpec(spec);
if (per.start().date() == per.end().date()) {
- text += i18nc("date, fromTime - toTime ", "%1, %2 - %3",
- dateToString(per.start(), true, spec),
- timeToString(per.start(), true, spec),
- timeToString(per.end(), true, spec));
+ periodData[QStringLiteral("date")] = pStart.date();
+ periodData[QStringLiteral("start")] = pStart.time();
+ periodData[QStringLiteral("end")] = pEnd.time();
} else {
- text += i18nc("fromDateTime - toDateTime", "%1 - %2",
- dateTimeToString(per.start(), false, true, spec),
- dateTimeToString(per.end(), false, true, spec));
+ periodData[QStringLiteral("start")] = pStart.dateTime();
+ periodData[QStringLiteral("end")] = pEnd.dateTime();
}
- text += QLatin1String("<br>");
}
+
+ periodsData << periodData;
}
- tmpStr += htmlAddTag(QStringLiteral("p"), text);
- return tmpStr;
+
+ fbData[QStringLiteral("periods")] = periodsData;
+
+ return GrantleeTemplateManager::instance()->render(QStringLiteral("freebusy.html"), \
fbData); }
//@endcond
@@ -3579,7 +3305,7 @@ static QString tooltipPerson(const QString &email, const \
QString &name, Attendee const QString printName = searchName(email, name);
// Get the icon corresponding to the attendee participation status.
- const QString iconPath = rsvpStatusIconPath(status);
+ const QString iconPath = \
KIconLoader::global()->iconPath(rsvpStatusIconName(status), KIconLoader::Small);
// Make the return string.
QString personString;
diff --git a/templates/CMakeLists.txt b/templates/CMakeLists.txt
new file mode 100644
index 0000000..02bff77
--- /dev/null
+++ b/templates/CMakeLists.txt
@@ -0,0 +1,10 @@
+
+install(FILES event.html
+ freebusy.html
+ incidence_header.html
+ journal.html
+ template_base.html
+ todo.html
+ attendee_row.html
+ DESTINATION ${KDE_INSTALL_DATADIR}/kcalendar/templates/default
+)
diff --git a/templates/attendee_row.html b/templates/attendee_row.html
new file mode 100644
index 0000000..95224a0
--- /dev/null
+++ b/templates/attendee_row.html
@@ -0,0 +1,28 @@
+{% if attendee.icon %}
+ {% icon attendee.icon small attendee.status %}
+{% endif %}
+
+{% if attendee.uid %}
+ <a href="uid:{{ attendee.uid }}">
+ {% if attendee.name %}
+ {{ attendee.name }}
+ {% else %}
+ {{ attendee.email }}
+ {% endif %}
+ </a>
+{% else %}
+ {% if attendee.name %}
+ {{ attendee.name }}
+ {% else %}
+ {{ attendee.email }}
+ {% endif %}
+{% endif %}
+{% if attendee.delegator %}
+ {% i18n "(delegated by %1)" attendee.delegator %}
+{% endif %}
+{% if attendee.delegate %}
+ {% i18n "(delegated to %1)" attendee.delegate %}\
+{% endif %}
+{% if attendee.mailto %}
+ <a href="{{ attendee.mailto }}">{% icon "mail-message-new" small _("Send email") \
%}</a> +{% endif %}
diff --git a/templates/event.html b/templates/event.html
new file mode 100644
index 0000000..9c9681d
--- /dev/null
+++ b/templates/event.html
@@ -0,0 +1,198 @@
+{% extends "template_base.html" %}
+
+
+{% block body %}
+{% with _("Event") as type %}
+{% include "incidence_header.html" %}
+{% endwith %}
+
+<table>
+ <!-- Calendar name -->
+ {% if incidence.calendar %}
+ <tr>
+ <th>{% i18n "Calendar:" %}</th>
+ <td>{{ incidence.calendar }}</td>
+ </tr>
+ {% endif %}
+
+ <!-- Location -->
+ {% if incidence.location %}
+ <tr>
+ <th>{% i18n "Location:" %}</th>
+ <td>{{ incidence.location|safe }}</td>
+ </tr>
+ {% endif %}
+
+ <!-- Start/end -->
+ {% if incidence.isAllDay %}
+ <tr>
+ {% if incidence.isMultiDay %}
+ <th>{% i18n "Date:" %}</th>
+ <td>{% i18nc "<beginDate> - <endDate>" "%1 - %2" incidence.startDate|kdate \
incidence.endDate|kdate %}</td> + {% else %}
+ <th>{% i18n "Date:" %}</th>
+ <td>{% i18nc "Date as string" "%1" incidence.startDate|kdate %}</td>
+ {% endif %}
+ </tr>
+ {% else %}
+ <tr>
+ {% if incidence.isMultiDay %}
+ <th>{% i18n "Date:" %}</th>
+ <td>{% i18nc "<beginDate> - <endDate>" "%1 - %2" incidence.startDate|kdate \
incidence.endDate|kdate %}</td> + {% else %}
+ <th>{% i18n "Date:" %}</th>
+ <td>{% i18nc "Date as string" "%1" incidence.startDate|kdate %}</td>
+ </tr>
+ <tr>
+ <th>{% i18n "Time:" %}</th>
+ {% if incidence.hasEnd and incidence.overnight %}
+ <td>{% i18nc "<beginTime> - <endTime>" "%1 - %2" \
incidence.startTime|ktime:"short" incidence.endTime|ktime:"short" %}</td> + \
{% else %} + <td>{% i18nc "Time as string" "%1" \
incidence.startTime|ktime:"short" %}</td> + {% endif %}
+ {% endif %}
+ </tr>
+ {% endif %}
+
+ <!-- Duration -->
+ {% if incidence.duration %}
+ <tr>
+ <th>{% i18n "Duration:" %}</th>
+ <td>{{ incidence.duration }}</td>
+ </tr>
+ {% endif %}
+
+ <!-- Recurrence -->
+ {% if incidence.recurs or incidence.isException %}
+ <tr>
+ <th>{% i18n "Recurrence:" %}</th>
+ {% if incidence.isException %}
+ <td>{% i18nc "Exception in event recurrence" "Exception" %}</td>
+ {% else %}
+ <td>{{ incidence.recurrence }}</td>
+ {% endif %}
+ </tr>
+ {% endif %}
+
+ <!-- Birthday -->
+ {% if incidence.birthday %}
+ <tr>
+ <th>{% i18n "Birthday:" %}</th>
+ <td>{{ incidence.birthday }}</td>
+ </tr>
+ {% endif %}
+
+ <!-- Anniversary -->
+ {% if incidence.anniversary %}
+ <tr>
+ <th>{% i18n "Anniversary:" %}</th>
+ <td>{{ incidence.anniversary }}</td>
+ </tr>
+ {% endif %}
+
+ <!-- Description -->
+ {% if incidence.description %}
+ <tr>
+ <th>{% i18n "Description:" %}</th>
+ <td>{{ incidence.description|safe }}</td>
+ </tr>
+ {% endif %}
+
+ <!-- Alarms -->
+ {% if incidence.reminders %}
+ <tr>
+ <th>{% i18np "Reminder:" "Reminders:" incidence.reminders|length %}</th>
+ <td>{{ incidence.reminders|join:"<br/>" }}</td>
+ </tr>
+ {% endif %}
+
+ <!-- Organizer -->
+ {% if incidence.organizer %}
+ <tr>
+ <th>{% i18n "Organizer:" %}</th>
+ <td>
+ {% with incidence.organizer as attendee %}
+ {% include "attendee_row.html" %}
+ {% endwith %}
+ </td>
+ </tr>
+ {% endif %}
+
+ <!-- Attendees - Chair -->
+ {% if incidence.chairs %}
+ <tr>
+ <th>{% i18n "Chair:" %}</th>
+ <td>
+ {% for attendee in incidence.chair %}
+ {% include "attendee_row.html" %}
+ {% if not forloop.last %}<br/>{% endif %}
+ {% endfor %}
+ </td>
+ </tr>
+ {% endif %}
+
+ <!-- Attendees - Required Participants -->
+ {% if incidence.requiredParticipants %}
+ <tr>
+ <th>{% i18n "Required Participants:" %}</th>
+ <td>
+ {% for attendee in incidence.requiredParticipants %}
+ {% include "attendee_row.html" %}
+ {% if not forloop.last %}<br/>{% endif %}
+ {% endfor %}
+ </td>
+ </tr>
+ {% endif %}
+
+ <!-- Attendees - Optional Participants -->
+ {% if incidence.optionalParticipants %}
+ <tr>
+ <th>{% i18n "Optional participants:" %}</th>
+ <td>
+ {% for attendee in incidence.optionalParticipants %}
+ {% include "attendee_row.html" %}
+ {% if not forloop.last %}<br/>{% endif %}
+ {% endfor %}
+ </td>
+ </tr>
+ {% endif %}
+
+ <!-- Attendees - Observers -->
+ {% if incidence.observers %}
+ <tr>
+ <th>{% i18n "Observers:" %}</th>
+ <td>
+ {% for attendee in incidence.chair %}
+ {% include "attendee_row.html" %}
+ {% if not forloop.last %}<br/>{% endif %}
+ {% endfor %}
+ </td>
+ </tr>
+ {% endif %}
+
+ <!-- Categories -->
+ {% if incidence.categories %}
+ <tr>
+ <th>{% i18np "Category:" "Categories:" incidence.categories|length %}</th>
+ <td>{{ incidence.categories|join:", " }}</td>
+ </tr>
+ {% endif %}
+
+ <!-- Attachments -->
+ {% if incidence.attachments %}
+ <tr>
+ <th>{% i18np "Attachment:" "Attachments:" incidence.attachments|length \
%}</th> + <td>{% for attachment in incidence.attachments %}
+ <a href="{{ attachment.uri }}">{{ attachment.label }}</a>
+ {% if not forloop.last %}<br/>{% endif %}
+ {% endfor %}
+ </td>
+ </tr>
+ {% endif %}
+
+</table>
+
+<!-- Creation -->
+<p><em>{% i18n "Creation date: %1" incidence.creationDate|kdatetime %}</em></p>
+
+{% endblock body %}
diff --git a/templates/freebusy.html b/templates/freebusy.html
new file mode 100644
index 0000000..a4ea0d6
--- /dev/null
+++ b/templates/freebusy.html
@@ -0,0 +1,28 @@
+{% extends "template_base.html" %}
+
+{% block body %}
+
+<h2>{% i18n "Free/Busy information for %1" incidence.organizer %}</h2>
+<h4>{% i18n "Busy times in date range %1 - %2:" incidence.start|kdate:"short" \
incidence.end|kdate:"short" %}</h4> +
+<p>
+<em><b>{% i18nc "tag for busy period list" "Busy:" %}</b></em>
+
+{% for period in incidence.periods %}
+ {% if period.duration %}
+ {% i18nc "startDate for duration" "%1 for %2" period.dtStart|kdate:"short" \
period.duration %} + {% else %}
+ {% if period.date %}
+ {% i18nc "date, fromTime - toTime" "%1, %2 - %3" \
period.date|kdate:"short" period.start|ktime:"short" period.end|ktime:"short" %} + \
{% else %} + {% i18nc "fromDateTime - endDateTime" "%1 - %2" \
period.start|kdatetime:"short" period.end|kdatetime:"short" %} + {% endif %}
+ {% endif %}
+ {% if not forloop.last %}
+ <br/>
+ {% endif %}
+{% endfor %}
+</p>
+
+{% endblock body %}
+
diff --git a/templates/incidence_header.html b/templates/incidence_header.html
new file mode 100644
index 0000000..5f5b60e
--- /dev/null
+++ b/templates/incidence_header.html
@@ -0,0 +1,24 @@
+<table class="header">
+ <tr>
+ <td>
+ {% if incidence.icon %}
+ {% icon incidence.icon small type %}
+ {% endif %}
+
+ {% if incidence.hasEnabledAlarms %}
+ {% icon "preferences-desktop-notification-bell" small _("Incidence with \
a reminder") %} + {% endif %}
+
+ {% if incidence.recurs %}
+ {% icon "edit-redo" small _("Recurring incidence") %}
+ {% endif %}
+
+ {% if incidence.isReadOnly %}
+ {% icon "object-locked" small _("Incidence is read only") %}
+ {% endif %}
+ </td>
+ <td>
+ <b><u>{{ incidence.summary }}</u></b>
+ </td>
+ </tr>
+</table>
diff --git a/templates/journal.html b/templates/journal.html
new file mode 100644
index 0000000..b8f20a4
--- /dev/null
+++ b/templates/journal.html
@@ -0,0 +1,48 @@
+{% extends "template_base.html" %}
+
+
+{% block body %}
+{% with _("Journal") as type %}
+{% include "incidence_header.html" %}
+{% endwith %}
+
+<table>
+
+ <!-- Calendar -->
+ {% if incidence.calendar %}
+ <tr>
+ <th>{% i18n "Calendar:" %}</th>
+ <td>{{ incidence.calendar }}</td>
+ </tr>
+ {% endif %}
+
+ <!-- Date -->
+ {% if incidence.date %}
+ <tr>
+ <th>{% i18n "Date:" %}</th>
+ <td>{{ incidence.date|kdate }}</td>
+ </tr>
+ {% endif %}
+
+ <!-- Description -->
+ {% if incidence.description %}
+ <tr>
+ <th>{% i18n "Description:" %}</th>
+ <td>{{ incidence.description|safe }}</td>
+ </tr>
+ {% endif %}
+
+ <!-- Categories -->
+ {% if incidence.categories %}
+ <tr>
+ <th>{% i18np "Category:" "Categories:" incidence.categories|length %}</th>
+ <td>{{ incidence.categories|join:", " }}</td>
+ </tr>
+ {% endif %}
+
+</table>
+
+<!-- Creation date -->
+<p><em>{% i18n "Creation date: %1" incidence.creationDate|kdatetime %}</em></p>
+
+{% endblock body %}
diff --git a/templates/template_base.html b/templates/template_base.html
new file mode 100644
index 0000000..07871df
--- /dev/null
+++ b/templates/template_base.html
@@ -0,0 +1,22 @@
+<style type="text/css">
+{# The Qt CSS parser is rather dumb and does not handle more complex #}
+{# selectors like "table.main th" (despite what documentation says) #}
+th {
+ text-align: left;
+ width: 25%;
+ white-space: nowrap;
+ font-weight: bold;
+}
+td {
+ padding-left: 5px;
+}
+
+{% block style %}
+{# Custom style extension provided by subtemplates #}
+{% endblock %}
+</style>
+
+
+{% block body %}
+{# Actual content of the subtemplate #}
+{% endblock %}
diff --git a/templates/todo.html b/templates/todo.html
new file mode 100644
index 0000000..478b7eb
--- /dev/null
+++ b/templates/todo.html
@@ -0,0 +1,203 @@
+{% extends "template_base.html" %}
+
+
+{% block body %}
+{% with _("Todo") as type %}
+{% include "incidence_header.html" %}
+{% endwith %}
+
+<table class="main">
+
+ <!-- Calendar -->
+ {% if incidence.calendar %}
+ <tr>
+ <th>{% i18n "Calendar:" %}</th>
+ <td>{{ incidence.calendar }}</td>
+ </tr>
+ {% endif %}
+
+ <!-- Location -->
+ {% if incidence.location %}
+ <tr>
+ <th>{% i18n "Location:" %}</th>
+ <td>{{ incidence.location|safe }}</td>
+ </tr>
+ {% endif %}
+
+ <!-- Start date -->
+ {% if incidence.startDate %}
+ <tr>
+ <th>{% i18nc "to-do start date/time" "Start:" %}</th>
+ <td>{{ incidence.startDate|kdatetime }}</td>
+ </tr>
+ {% endif %}
+
+ <!-- Due date -->
+ {% if incidence.dueDate %}
+ <tr>
+ <th>{% i18nc "to-do due date/time" "Due:" %}</th>
+ {% if incidence.allDay %}
+ <td>{{ incidence.dueDate|kdatetime:"dateonly" }}</td>
+ {% else %}
+ <td>{{ incidence.dueDate|kdatetime }}</td>
+ {% endif %}
+ </tr>
+ {% endif %}
+
+ <!-- Duration -->
+ {% if incidence.duration %}
+ <tr>
+ <th>{% i18n "Duration:" %}</th>
+ <td>{{ incidence.duration }}</td>
+ </tr>
+ {% endif %}
+
+ <!-- Recurrence -->
+ {% if incidence.recurs or incidence.isException %}
+ <tr>
+ <th>{% i18n "Recurrence:" %}</th>
+ {% if incidence.isException %}
+ <td>{% i18n "Exception" %}</td>
+ {% else %}
+ <td>{{ incidence.recurrence }}</td>
+ {% endif %}
+ </tr>
+ {% endif %}
+
+ <!-- Organizer -->
+ {# TODO #}
+
+ <!-- Attendee -->
+ {# TODO #}
+
+ <!-- Description -->
+ {% if incidence.description %}
+ <tr>
+ <th>{% i18n "Description:" %}</th>
+ <td>{{ incidence.description|safe }}</td>
+ </tr>
+ {% endif %}
+
+ <!-- Comments -->
+ {# TODO #}
+
+ <!-- Reminders -->
+ {% if incidence.reminders %}
+ <tr>
+ <th>{% i18np "Reminder:" "Reminders:" incidence.reminders|length %}</th>
+ <td>{{ incidence.reminders|join:"<br/>" }}</td>
+ </tr>
+ {% endif %}
+
+ <!-- Organizer -->
+ {% if incidence.organizer %}
+ <tr>
+ <th>{% i18n "Organizer:" %}</th>
+ <td>
+ {% with incidence.organizer as attendee %}
+ {% include "attendee_row.html" %}
+ {% endwith %}
+ </td>
+ </tr>
+ {% endif %}
+
+ <!-- Attendees - Chair -->
+ {% if incidence.chairs %}
+ <tr>
+ <th>{% i18n "Chair:" %}</th>
+ <td>
+ {% for attendee in incidence.chair %}
+ {% include "attendee_row.html" %}
+ {% if not forloop.last %}<br/>{% endif %}
+ {% endfor %}
+ </td>
+ </tr>
+ {% endif %}
+
+ <!-- Attendees - Required Participants -->
+ {% if incidence.requiredParticipants %}
+ <tr>
+ <th>{% i18n "Required Participants:" %}</th>
+ <td>
+ {% for attendee in incidence.requiredParticipants %}
+ {% include "attendee_row.html" %}
+ {% if not forloop.last %}<br/>{% endif %}
+ {% endfor %}
+ </td>
+ </tr>
+ {% endif %}
+
+ <!-- Attendees - Optional Participants -->
+ {% if incidence.optionalParticipants %}
+ <tr>
+ <th>{% i18n "Optional participants:" %}</th>
+ <td>
+ {% for attendee in incidence.optionalParticipants %}
+ {% include "attendee_row.html" %}
+ {% if not forloop.last %}<br/>{% endif %}
+ {% endfor %}
+ </td>
+ </tr>
+ {% endif %}
+
+ <!-- Attendees - Observers -->
+ {% if incidence.observers %}
+ <tr>
+ <th>{% i18n "Observers:" %}</th>
+ <td>
+ {% for attendee in incidence.chair %}
+ {% include "attendee_row.html" %}
+ {% if not forloop.last %}<br/>{% endif %}
+ {% endfor %}
+ </td>
+ </tr>
+ {% endif %}
+
+ <!-- Categories -->
+ {% if incidence.categories %}
+ <tr>
+ <th>{% i18np "Category:" "Categories:" incidence.categories|length %}</th>
+ <td>{{ incidence.categories|join:", " }}</td>
+ </tr>
+ {% endif %}
+
+ <!-- Priority -->
+ {% ifnotequal incidence.priority 0 %}
+ <tr>
+ <th>{% i18n "Priority:" %}</th>
+ <td>{{ incidence.priority }}</td>
+ </tr>
+ {% endifnotequal %}
+
+ <!-- Completed -->
+ {% if incidence.completedDate %}
+ <tr>
+ <th>{% i18nc "Completed: date" "Completed:" %}</th>
+ <td>{{ incidence.completedDate|kdate }}</td>
+ </tr>
+ {% else %}
+ <tr>
+ <th>{% i18n "Percent done:" %}</th>
+ <td>{% i18n "%1%" incidence.percent %}</td>
+ </tr>
+ {% endif %}
+
+ <!-- Attachments -->
+ {% if incidence.attachments %}
+ <tr>
+ <th>{% i18np "Attachment:" "Attachments:" incidence.attachments|length \
%}</th> + <td>{% for attachment in incidence.attachments %}
+ <a href="{{ attachment.uri }}">{{ attachment.label }}</a>
+ {% if not forloop.last %}
+ <br/>
+ {% endif %}
+ {% endfor %}
+ </td>
+ </tr>
+ {% endif %}
+</table>
+
+<!-- Creation date -->
+<p><em>{% i18n "Creation date: %1" incidence.creationDate|kdatetime %}</em></p>
+
+{% endblock body %}
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic