24 #include "htmlexportsettings.h"
27 #include <calendarsupport/utils.h>
29 #include <Akonadi/Contact/ContactSearchJob>
31 #include <KCalCore/Attendee>
33 #include <KCalUtils/IncidenceFormatter>
35 #include <KCalendarSystem>
37 #include <KLocalizedString>
38 #include <KMessageBox>
39 #include <KTemporaryFile>
40 #include <KIO/NetAccess>
42 #include <QApplication>
44 #include <QTextStream>
51 class KOrg::HtmlExportJob::Private
54 Private(
const Akonadi::ETMCalendar::Ptr &calendar,
55 KOrg::HTMLExportSettings *
settings,
bool autoMode,
57 : mCalendar( calendar ),
58 mSettings( settings ),
59 mAutoMode( autoMode ),
60 mMainWindow( mainWindow ),
61 mParentWidget( parent ),
65 Akonadi::ETMCalendar::Ptr mCalendar;
66 KOrg::HTMLExportSettings *mSettings;
71 qulonglong mSubJobCount;
77 KOrg::HTMLExportSettings *settings,
bool autoMode,
79 :
KJob( parent ),
d( new Private( calendar, settings, autoMode, mainWindow, parent ) )
91 bool canExportItem =
false;
93 const KCalCore::Event::List events =
d->mCalendar->events();
94 foreach (
const KCalCore::Event::Ptr &event, events ) {
96 const KCalCore::Attendee::List attendees =
event->attendees();
97 if ( !attendees.isEmpty() ) {
98 Akonadi::ContactSearchJob *job =
new Akonadi::ContactSearchJob(
this );
99 job->setQuery( Akonadi::ContactSearchJob::Email, event->organizer()->email() );
100 job->setProperty(
"incidenceUid", event->uid() );
101 connect( job, SIGNAL(result(
KJob*)), SLOT(receivedOrganizerInfo(
KJob*)) );
105 canExportItem =
true;
109 const KCalCore::Todo::List todos =
d->mCalendar->todos();
110 foreach (
const KCalCore::Todo::Ptr &todo, todos ) {
112 const KCalCore::Attendee::List attendees = todo->attendees();
113 if ( !attendees.isEmpty() ) {
114 Akonadi::ContactSearchJob *job =
new Akonadi::ContactSearchJob(
this );
115 job->setQuery( Akonadi::ContactSearchJob::Email, todo->organizer()->email() );
116 job->setProperty(
"incidenceUid", todo->uid() );
117 connect( job, SIGNAL(result(
KJob*)), SLOT(receivedOrganizerInfo(
KJob*)) );
121 canExportItem =
true;
124 if( !canExportItem ) {
129 void HtmlExportJob::receivedOrganizerInfo(
KJob *job )
133 if ( !job->error() ) {
134 Akonadi::ContactSearchJob *searchJob = qobject_cast<Akonadi::ContactSearchJob*>( job );
135 const KABC::Addressee::List contacts = searchJob->contacts();
136 if ( !contacts.isEmpty() ) {
137 d->mOrganizersMap.insert(
138 searchJob->property(
"incidenceUid" ).toString(), contacts.first() );
141 if (
d->mSubJobCount == 0 ) {
146 void HtmlExportJob::finishExport()
152 Q_ASSERT(
d->mSettings );
153 KUrl dest(
d->mSettings->outputFile() );
154 if ( dest.isLocalFile() ) {
155 saveStatus = save( dest.toLocalFile() );
156 errorMessage = i18n(
"Unable to write the output file." );
161 saveStatus = save( tfile );
162 errorMessage = i18n(
"Unable to write the temporary file for uploading." );
163 if ( !KIO::NetAccess::upload( tfile, dest,
d->mParentWidget ) ) {
165 errorMessage = i18n(
"Unable to upload the export file." );
174 if (dest.isLocalFile())
175 urlStr = dest.toLocalFile();
177 urlStr = dest.prettyUrl();
178 saveMessage = i18n(
"Web page successfully written to \"%1\"", urlStr );
180 saveMessage = i18n(
"Export failed. %1", errorMessage );
183 if ( !
d->mAutoMode ) {
184 KMessageBox::information(
187 i18nc(
"@title:window",
"Export Status" ) );
189 d->mMainWindow->showStatusMessage(
190 i18nc(
"@info:status",
191 "Automatic Export: %1", saveMessage ) );
196 bool HtmlExportJob::save(
const QString &fileName )
199 if ( fn.isEmpty() &&
d->mSettings ) {
200 fn =
d->mSettings->outputFile();
202 if ( !
d->mSettings || fn.isEmpty() ) {
206 if ( !f.open( QIODevice::WriteOnly ) ) {
210 bool success = save( &ts );
217 if ( !
d->mSettings ) {
222 *ts <<
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ";
223 *ts <<
"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" << endl;
225 *ts <<
"<html><head>" << endl;
226 *ts <<
" <meta http-equiv=\"Content-Type\" content=\"text/html; charset=";
227 *ts <<
"UTF-8\" />" << endl;
228 if ( !
d->mSettings->pageTitle().isEmpty() ) {
229 *ts <<
" <title>" <<
d->mSettings->pageTitle() <<
"</title>" << endl;
231 *ts <<
" <style type=\"text/css\">" << endl;
233 *ts <<
" </style>" << endl;
234 *ts <<
"</head><body>" << endl;
239 if (
d->mSettings->eventView() ||
d->mSettings->monthView() ||
d->mSettings->weekView() ) {
240 if ( !
d->mSettings->eventTitle().isEmpty() ) {
241 *ts <<
"<h1>" <<
d->mSettings->eventTitle() <<
"</h1>" << endl;
245 if (
d->mSettings->weekView() ) {
249 if (
d->mSettings->monthView() ) {
253 if (
d->mSettings->eventView() ) {
259 if (
d->mSettings->todoView() ) {
260 if ( !
d->mSettings->todoListTitle().isEmpty() ) {
261 *ts <<
"<h1>" <<
d->mSettings->todoListTitle() <<
"</h1>" << endl;
267 if (
d->mSettings->journalView() ) {
268 if ( !
d->mSettings->journalTitle().isEmpty() ) {
269 *ts <<
"<h1>" <<
d->mSettings->journalTitle() <<
"</h1>" << endl;
275 if (
d->mSettings->freeBusyView() ) {
276 if ( !
d->mSettings->freeBusyTitle().isEmpty() ) {
277 *ts <<
"<h1>" <<
d->mSettings->freeBusyTitle() <<
"</h1>" << endl;
285 *ts <<
"</body></html>" << endl;
297 int startmonth = start.
month();
298 int startyear = start.
year();
300 while ( start <
toDate() ) {
306 << i18nc(
"@title month and year",
"%1 %2", hMon, hYear )
308 if ( KGlobal::locale()->weekStartDay() == 1 ) {
315 *ts <<
"<table border=\"1\">" << endl;
319 for (
int i=0; i < 7; ++i ) {
320 *ts <<
"<th>" << KGlobal::locale()->calendar()->weekDayName( start.
addDays(i) ) <<
"</th>";
322 *ts <<
"</tr>" << endl;
325 while ( start <= end ) {
326 *ts <<
" <tr>" << endl;
327 for (
int i=0; i < 7; ++i ) {
328 *ts <<
" <td valign=\"top\"><table border=\"0\">";
331 if (
d->mHolidayMap.contains( start ) || start.
dayOfWeek() == 7 ) {
332 *ts <<
"class=\"dateholiday\"";
334 *ts <<
"class=\"date\"";
338 if (
d->mHolidayMap.contains( start ) ) {
339 *ts <<
" <em>" <<
d->mHolidayMap[
start] <<
"</em>";
342 *ts <<
"</td></tr><tr><td valign=\"top\">";
346 KCalCore::Event::List events =
d->mCalendar->events( start,
d->mCalendar->timeSpec(),
347 KCalCore::EventSortStartDate,
348 KCalCore::SortDirectionAscending );
349 if ( events.count() ) {
351 foreach (
const KCalCore::Event::Ptr &event, events ) {
363 *ts <<
"</td></tr></table></td>" << endl;
366 *ts <<
" </tr>" << endl;
368 *ts <<
"</table>" << endl;
370 if ( startmonth > 12 ) {
374 start.
setYMD( startyear, startmonth, 1 );
382 *ts <<
"<table border=\"0\" cellpadding=\"3\" cellspacing=\"3\">" << endl;
383 *ts <<
" <tr>" << endl;
384 *ts <<
" <th class=\"sum\">" << i18nc(
"@title:column event start time",
385 "Start Time" ) <<
"</th>" << endl;
386 *ts <<
" <th>" << i18nc(
"@title:column event end time",
387 "End Time" ) <<
"</th>" << endl;
388 *ts <<
" <th>" << i18nc(
"@title:column event description",
389 "Event" ) <<
"</th>" << endl;
390 if (
d->mSettings->eventLocation() ) {
391 *ts <<
" <th>" << i18nc(
"@title:column event locatin",
392 "Location" ) <<
"</th>" << endl;
395 if (
d->mSettings->eventCategories() ) {
396 *ts <<
" <th>" << i18nc(
"@title:column event categories",
397 "Categories" ) <<
"</th>" << endl;
400 if (
d->mSettings->eventAttendees() ) {
401 *ts <<
" <th>" << i18nc(
"@title:column event attendees",
402 "Attendees" ) <<
"</th>" << endl;
406 *ts <<
" </tr>" << endl;
409 kDebug() <<
"Getting events for" << dt.toString();
410 KCalCore::Event::List events =
d->mCalendar->events( dt,
d->mCalendar->timeSpec(),
411 KCalCore::EventSortStartDate,
412 KCalCore::SortDirectionAscending );
413 if ( !events.isEmpty() ) {
415 <<
"\" class=\"datehead\"><i>"
416 << KGlobal::locale()->formatDate( dt )
417 <<
"</i></td></tr>" << endl;
419 foreach (
const KCalCore::Event::Ptr &event, events ) {
428 *ts <<
"</table>" << endl;
432 QDate date,
bool withDescription )
434 kDebug() <<
event->summary();
435 *ts <<
" <tr>" << endl;
437 if ( !event->allDay() ) {
438 if ( event->isMultiDay(
d->mCalendar->timeSpec() ) && ( event->dtStart().date() != date ) ) {
439 *ts <<
" <td> </td>" << endl;
441 *ts <<
" <td valign=\"top\">"
442 << KCalUtils::IncidenceFormatter::timeToString( event->dtStart(),
443 true,
d->mCalendar->timeSpec() )
446 if ( event->isMultiDay(
d->mCalendar->timeSpec() ) && ( event->dtEnd().date() != date ) ) {
447 *ts <<
" <td> </td>" << endl;
449 *ts <<
" <td valign=\"top\">"
450 << KCalUtils::IncidenceFormatter::timeToString( event->dtEnd(),
451 true,
d->mCalendar->timeSpec() )
455 *ts <<
" <td> </td><td> </td>" << endl;
458 *ts <<
" <td class=\"sum\">" << endl;
459 *ts <<
" <b>" <<
cleanChars( event->summary() ) <<
"</b>" << endl;
460 if ( withDescription && !event->description().isEmpty() ) {
463 *ts <<
" </td>" << endl;
465 if (
d->mSettings->eventLocation() ) {
466 *ts <<
" <td>" << endl;
468 *ts <<
" </td>" << endl;
471 if (
d->mSettings->eventCategories() ) {
472 *ts <<
" <td>" << endl;
474 *ts <<
" </td>" << endl;
477 if (
d->mSettings->eventAttendees() ) {
478 *ts <<
" <td>" << endl;
480 *ts <<
" </td>" << endl;
483 *ts <<
" </tr>" << endl;
488 KCalCore::Todo::List rawTodoList =
d->mCalendar->todos();
491 while ( index < rawTodoList.count() ) {
492 const KCalCore::Todo::Ptr todo = rawTodoList.value( index );
494 const Akonadi::Item parentItem =
d->mCalendar->item( todo->relatedTo() );
501 KCalCore::Todo::List todoList;
502 KCalCore::Todo::List::ConstIterator it;
503 for (
int i = 1; i <= 9; ++i ) {
504 foreach (
const KCalCore::Todo::Ptr &todo, rawTodoList ) {
506 todoList.append( todo );
510 foreach (
const KCalCore::Todo::Ptr &todo, rawTodoList ) {
512 todoList.append( todo );
517 *ts <<
"<table border=\"0\" cellpadding=\"3\" cellspacing=\"3\">" << endl;
518 *ts <<
" <tr>" << endl;
519 *ts <<
" <th class=\"sum\">" << i18nc(
"@title:column",
"To-do" ) <<
"</th>" << endl;
520 *ts <<
" <th>" << i18nc(
"@title:column to-do priority",
"Priority" ) <<
"</th>" << endl;
521 *ts <<
" <th>" << i18nc(
"@title:column to-do percent completed",
522 "Completed" ) <<
"</th>" << endl;
523 if (
d->mSettings->taskDueDate() ) {
524 *ts <<
" <th>" << i18nc(
"@title:column to-do due date",
"Due Date" ) <<
"</th>" << endl;
527 if (
d->mSettings->taskLocation() ) {
528 *ts <<
" <th>" << i18nc(
"@title:column to-do location",
"Location" ) <<
"</th>" << endl;
531 if (
d->mSettings->taskCategories() ) {
532 *ts <<
" <th>" << i18nc(
"@title:column to-do categories",
"Categories" ) <<
"</th>" << endl;
535 if (
d->mSettings->taskAttendees() ) {
536 *ts <<
" <th>" << i18nc(
"@title:column to-do attendees",
"Attendees" ) <<
"</th>" << endl;
539 *ts <<
" </tr>" << endl;
542 for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) {
543 const QString parentUid = (*it)->relatedTo();
554 for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) {
556 Akonadi::Item item =
d->mCalendar->item( ( *it )->uid() );
557 const Akonadi::Item::List relations =
d->mCalendar->childItems( item.id() );
559 if ( !relations.isEmpty() ) {
561 *ts <<
" <tr>" << endl;
562 *ts <<
" <td class=\"subhead\" colspan=";
564 *ts <<
"><a name=\"sub" << (*it)->uid() <<
"\"></a>"
565 << i18nc(
"@title:column sub-to-dos of the parent to-do",
566 "Sub-To-dos of: " ) <<
"<a href=\"#"
567 << (*it)->uid() <<
"\"><b>" <<
cleanChars( (*it)->summary() )
568 <<
"</b></a></td>" << endl;
569 *ts <<
" </tr>" << endl;
571 KCalCore::Todo::List sortedList;
574 for (
int i = 1; i <= 9; ++i ) {
575 foreach (
const Akonadi::Item &item, relations ) {
576 KCalCore::Todo::Ptr ev3 = CalendarSupport::todo( item );
577 if ( ev3 && ev3->priority() == i ) {
578 sortedList.append( ev3 );
583 foreach (
const Akonadi::Item &item, relations ) {
584 KCalCore::Todo::Ptr ev3 = CalendarSupport::todo( item );
585 if ( ev3 && ev3->priority() == 0 ) {
586 sortedList.append( ev3 );
590 KCalCore::Todo::List::ConstIterator it3;
591 for ( it3 = sortedList.constBegin(); it3 != sortedList.constEnd(); ++it3 ) {
597 *ts <<
"</table>" << endl;
604 const bool completed = todo->isCompleted();
606 Akonadi::Item it =
d->mCalendar->item( todo->uid() );
607 Akonadi::Item::List relations =
d->mCalendar->childItems( it.id() );
609 *ts <<
"<tr>" << endl;
611 *ts <<
" <td class=\"sum";
612 if (completed) *ts <<
"done";
613 *ts <<
"\">" << endl;
614 *ts <<
" <a name=\"" << todo->uid() <<
"\"></a>" << endl;
615 *ts <<
" <b>" <<
cleanChars( todo->summary() ) <<
"</b>" << endl;
616 if ( !todo->description().isEmpty() ) {
619 if ( relations.count() ) {
620 *ts <<
" <div align=\"right\"><a href=\"#sub" << todo->uid()
621 <<
"\">" << i18nc(
"@title:column sub-to-dos of the parent to-do",
622 "Sub-To-dos" ) <<
"</a></div>" << endl;
624 *ts <<
" </td>" << endl;
628 *ts <<
" class=\"done\"";
631 *ts <<
" " << todo->priority() << endl;
632 *ts <<
" </td>" << endl;
636 *ts <<
" class=\"done\"";
639 *ts <<
" " << i18nc(
"@info/plain to-do percent complete",
640 "%1 %", todo->percentComplete() ) << endl;
641 *ts <<
" </td>" << endl;
643 if (
d->mSettings->taskDueDate() ) {
646 *ts <<
" class=\"done\"";
649 if ( todo->hasDueDate() ) {
650 *ts <<
" " << KCalUtils::IncidenceFormatter::dateToString( todo->dtDue(
true ) ) << endl;
652 *ts <<
" " << endl;
654 *ts <<
" </td>" << endl;
657 if (
d->mSettings->taskLocation() ) {
660 *ts <<
" class=\"done\"";
664 *ts <<
" </td>" << endl;
667 if (
d->mSettings->taskCategories() ) {
670 *ts <<
" class=\"done\"";
674 *ts <<
" </td>" << endl;
677 if (
d->mSettings->taskAttendees() ) {
680 *ts <<
" class=\"done\"";
684 *ts <<
" </td>" << endl;
687 *ts <<
"</tr>" << endl;
711 int secrecy = incidence->secrecy();
712 if ( secrecy == KCalCore::Incidence::SecrecyPublic ) {
715 if ( secrecy == KCalCore::Incidence::SecrecyPrivate && !
d->mSettings->excludePrivate() ) {
718 if ( secrecy == KCalCore::Incidence::SecrecyConfidential &&
719 !
d->mSettings->excludeConfidential() ) {
727 if ( !incidence->location().isEmpty() ) {
728 *ts <<
" " <<
cleanChars( incidence->location() ) << endl;
730 *ts <<
" " << endl;
736 if ( !incidence->categoriesStr().isEmpty() ) {
737 *ts <<
" " <<
cleanChars( incidence->categoriesStr() ) << endl;
739 *ts <<
" " << endl;
745 KCalCore::Attendee::List attendees = incidence->attendees();
746 if ( attendees.count() ) {
748 const KABC::Addressee organizer =
d->mOrganizersMap.value( incidence->uid() );
749 if ( !organizer.isEmpty() ) {
750 *ts <<
"<a href=\"mailto:" << incidence->organizer()->email() <<
"\">";
751 *ts <<
cleanChars( organizer.formattedName() ) <<
"</a>" << endl;
753 *ts << incidence->organizer()->fullName();
755 *ts <<
"</em><br />";
756 KCalCore::Attendee::List::ConstIterator it;
757 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
758 KCalCore::Attendee::Ptr a = *it;
759 if ( !a->email().isEmpty() ) {
760 *ts <<
"<a href=\"mailto:" << a->email();
761 *ts <<
"\">" <<
cleanChars( a->name() ) <<
"</a>";
765 *ts <<
"<br />" << endl;
768 *ts <<
" " << endl;
781 for (
int i = 0; i <= number; ++i ) {
783 tmp = tmpText.
left( pos );
784 tmpText = tmpText.
right( tmpText.
length() - pos - 1 );
794 QString trailer = i18nc(
"@info/plain",
"This page was created " );
800 if ( !
d->mSettings->eMail().isEmpty() ) {
801 if ( !
d->mSettings->name().isEmpty() ) {
802 trailer += i18nc(
"@info/plain page creator email link with name",
803 "by <link url='mailto:%1'>%2</link> ",
804 d->mSettings->eMail(),
d->mSettings->name() );
806 trailer += i18nc(
"@info/plain page creator email link",
807 "by <link url='mailto:%1'>%2</link> ",
808 d->mSettings->eMail(),
d->mSettings->eMail() );
811 if ( !
d->mSettings->name().isEmpty() ) {
812 trailer += i18nc(
"@info/plain page creator name only",
813 "by %1 ",
d->mSettings->name() );
816 if ( !
d->mSettings->creditName().isEmpty() ) {
817 if ( !
d->mSettings->creditURL().isEmpty() ) {
818 trailer += i18nc(
"@info/plain page credit with name and link",
819 "with <link url='%1'>%2</link>",
820 d->mSettings->creditURL(),
d->mSettings->creditName() );
822 trailer += i18nc(
"@info/plain page credit name only",
823 "with %1",
d->mSettings->creditName() );
826 *ts <<
"<p>" << trailer <<
"</p>" << endl;
851 if ( !
d->mSettings->styleSheet().isEmpty() ) {
852 return d->mSettings->styleSheet();
858 css +=
QLatin1String(
" body { background-color:white; color:black; direction: rtl }\n");
859 css +=
QLatin1String(
" td { text-align:center; background-color:#eee }\n");
860 css +=
QLatin1String(
" th { text-align:center; background-color:#228; color:white }\n");
861 css +=
QLatin1String(
" td.sumdone { background-color:#ccc }\n");
862 css +=
QLatin1String(
" td.done { background-color:#ccc }\n");
863 css +=
QLatin1String(
" td.subhead { text-align:center; background-color:#ccf }\n");
864 css +=
QLatin1String(
" td.datehead { text-align:center; background-color:#ccf }\n");
865 css +=
QLatin1String(
" td.space { background-color:white }\n");
868 css +=
QLatin1String(
" body { background-color:white; color:black }\n");
869 css +=
QLatin1String(
" td { text-align:center; background-color:#eee }\n");
870 css +=
QLatin1String(
" th { text-align:center; background-color:#228; color:white }\n");
872 css +=
QLatin1String(
" td.sumdone { text-align:left; background-color:#ccc }\n");
873 css +=
QLatin1String(
" td.done { background-color:#ccc }\n");
874 css +=
QLatin1String(
" td.subhead { text-align:center; background-color:#ccf }\n");
875 css +=
QLatin1String(
" td.datehead { text-align:center; background-color:#ccf }\n");
876 css +=
QLatin1String(
" td.space { background-color:white }\n");
878 css +=
QLatin1String(
" td.dateholiday { text-align:left; color:red }\n");
886 if (
d->mHolidayMap[date].isEmpty() ) {
887 d->mHolidayMap[date] = name;
889 d->mHolidayMap[date] = i18nc(
"@info/plain holiday by date and name",
890 "%1, %2",
d->mHolidayMap[date], name );
896 return d->mSettings->dateStart().date();
901 return d->mSettings->dateEnd().date();
void setCodec(QTextCodec *codec)
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const
void createEvent(QTextStream *ts, const KCalCore::Event::Ptr &event, QDate date, bool withDescription=true)
void createTodo(QTextStream *ts, const KCalCore::Todo::Ptr &todo)
void createTodoList(QTextStream *ts)
void formatLocation(QTextStream *ts, const KCalCore::Incidence::Ptr &incidence)
static QString cleanChars(const QString &txt)
QString styleSheet() const
QString number(int n, int base)
QString fromUtf8(const char *str, int size)
bool setYMD(int y, int m, int d)
void addHoliday(const QDate &date, const QString &name)
HtmlExportJob(const Akonadi::ETMCalendar::Ptr &calendar, HTMLExportSettings *settings, bool autoMode, KOrg::MainWindow *mainWindow, QWidget *parent=0)
Create new HTML exporter for calendar.
void createMonthView(QTextStream *ts)
void createFooter(QTextStream *ts)
void createJournalView(QTextStream *ts)
void setOverrideCursor(const QCursor &cursor)
void restoreOverrideCursor()
interface for korganizer main window
bool checkSecrecy(const KCalCore::Incidence::Ptr &incidence)
QString right(int n) const
HTMLExportSettings * settings() const
void formatAttendees(QTextStream *ts, const KCalCore::Incidence::Ptr &incidence)
QString & replace(int position, int n, QChar after)
void createFreeBusyView(QTextStream *ts)
void createWeekView(QTextStream *ts)
void formatCategories(QTextStream *ts, const KCalCore::Incidence::Ptr &incidence)
QString left(int n) const
QDate addDays(int ndays) const
void createEventList(QTextStream *ts)
QString breakString(const QString &text)