1 /*
2   SPDX-FileCopyrightText: 1998 Preston Brown <pbrown@kde.org>
3   SPDX-FileCopyrightText: 2003 Reinhold Kainhofer <reinhold@kainhofer.com>
4   SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org>
5   SPDX-FileCopyrightText: 2008 Ron Goodheart <rong.dev@gmail.com>
6   SPDX-FileCopyrightText: 2010-2021 Laurent Montel <montel@kde.org>
7   SPDX-FileCopyrightText: 2012-2013 Allen Winter <winter@kde.org>
8 
9   SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
10 */
11 
12 #include "calprintdefaultplugins.h"
13 #include "kcalprefs.h"
14 #include "utils.h"
15 
16 #include <cmath>
17 
18 #include <Akonadi/Item>
19 
20 #include <KCalendarCore/Visitor>
21 
22 #include <KCalUtils/IncidenceFormatter>
23 #include <KCalUtils/Stringify>
24 
25 #include <KConfigGroup>
26 
27 #include <QPainter>
28 
29 using namespace CalendarSupport;
30 
31 /**************************************************************
32  *           Print Incidence
33  **************************************************************/
34 
CalPrintIncidence()35 CalPrintIncidence::CalPrintIncidence()
36     : CalPrintPluginBase()
37 {
38 }
39 
~CalPrintIncidence()40 CalPrintIncidence::~CalPrintIncidence()
41 {
42 }
43 
createConfigWidget(QWidget * w)44 QWidget *CalPrintIncidence::createConfigWidget(QWidget *w)
45 {
46     return new CalPrintIncidenceConfig(w);
47 }
48 
readSettingsWidget()49 void CalPrintIncidence::readSettingsWidget()
50 {
51     auto cfg = dynamic_cast<CalPrintIncidenceConfig *>((QWidget *)mConfigWidget);
52     if (cfg) {
53         mUseColors = cfg->mColors->isChecked();
54         mPrintFooter = cfg->mPrintFooter->isChecked();
55         mShowOptions = cfg->mShowDetails->isChecked();
56         mShowSubitemsNotes = cfg->mShowSubitemsNotes->isChecked();
57         mShowAttendees = cfg->mShowAttendees->isChecked();
58         mShowAttachments = cfg->mShowAttachments->isChecked();
59         mShowNoteLines = cfg->mShowNoteLines->isChecked();
60     }
61 }
62 
setSettingsWidget()63 void CalPrintIncidence::setSettingsWidget()
64 {
65     auto cfg = dynamic_cast<CalPrintIncidenceConfig *>((QWidget *)mConfigWidget);
66     if (cfg) {
67         cfg->mColors->setChecked(mUseColors);
68         cfg->mPrintFooter->setChecked(mPrintFooter);
69         cfg->mShowDetails->setChecked(mShowOptions);
70         cfg->mShowSubitemsNotes->setChecked(mShowSubitemsNotes);
71         cfg->mShowAttendees->setChecked(mShowAttendees);
72         cfg->mShowAttachments->setChecked(mShowAttachments);
73         cfg->mShowNoteLines->setChecked(mShowNoteLines);
74     }
75 }
76 
doLoadConfig()77 void CalPrintIncidence::doLoadConfig()
78 {
79     CalPrintPluginBase::doLoadConfig();
80     if (mConfig) {
81         KConfigGroup grp(mConfig, groupName());
82         mShowOptions = grp.readEntry("Show Options", false);
83         mShowSubitemsNotes = grp.readEntry("Show Subitems and Notes", false);
84         mShowAttendees = grp.readEntry("Use Attendees", false);
85         mShowAttachments = grp.readEntry("Use Attachments", false);
86     }
87     setSettingsWidget();
88 }
89 
doSaveConfig()90 void CalPrintIncidence::doSaveConfig()
91 {
92     readSettingsWidget();
93     if (mConfig) {
94         KConfigGroup grp(mConfig, groupName());
95         grp.writeEntry("Show Options", mShowOptions);
96         grp.writeEntry("Show Subitems and Notes", mShowSubitemsNotes);
97         grp.writeEntry("Use Attendees", mShowAttendees);
98         grp.writeEntry("Use Attachments", mShowAttachments);
99     }
100     CalPrintPluginBase::doSaveConfig();
101 }
102 
103 class TimePrintStringsVisitor : public KCalendarCore::Visitor
104 {
105 public:
TimePrintStringsVisitor()106     TimePrintStringsVisitor()
107     {
108     }
109 
act(KCalendarCore::IncidenceBase::Ptr incidence)110     bool act(KCalendarCore::IncidenceBase::Ptr incidence)
111     {
112         return incidence->accept(*this, incidence);
113     }
114 
115     QString mStartCaption, mStartString;
116     QString mEndCaption, mEndString;
117     QString mDurationCaption, mDurationString;
118 
119 protected:
visit(const KCalendarCore::Event::Ptr & event)120     bool visit(const KCalendarCore::Event::Ptr &event) override
121     {
122         if (event->dtStart().isValid()) {
123             mStartCaption = i18n("Start date: ");
124             mStartString = KCalUtils::IncidenceFormatter::dateTimeToString(event->dtStart(), event->allDay(), false);
125         } else {
126             mStartCaption = i18n("No start date");
127             mStartString.clear();
128         }
129 
130         if (event->hasEndDate()) {
131             mEndCaption = i18n("End date: ");
132             mEndString = KCalUtils::IncidenceFormatter::dateTimeToString(event->dtEnd(), event->allDay(), false);
133         } else if (event->hasDuration()) {
134             mEndCaption = i18n("Duration: ");
135             int mins = event->duration().asSeconds() / 60;
136             if (mins >= 60) {
137                 mEndString += i18np("1 hour ", "%1 hours ", mins / 60);
138             }
139             if (mins % 60 > 0) {
140                 mEndString += i18np("1 minute ", "%1 minutes ", mins % 60);
141             }
142         } else {
143             mEndCaption = i18n("No end date");
144             mEndString.clear();
145         }
146         return true;
147     }
148 
visit(const KCalendarCore::Todo::Ptr & todo)149     bool visit(const KCalendarCore::Todo::Ptr &todo) override
150     {
151         if (todo->hasStartDate()) {
152             mStartCaption = i18n("Start date: ");
153             mStartString = KCalUtils::IncidenceFormatter::dateTimeToString(todo->dtStart(), todo->allDay(), false);
154         } else {
155             mStartCaption = i18n("No start date");
156             mStartString.clear();
157         }
158 
159         if (todo->hasDueDate()) {
160             mEndCaption = i18n("Due date: ");
161             mEndString = KCalUtils::IncidenceFormatter::dateTimeToString(todo->dtDue(), todo->allDay(), false);
162         } else {
163             mEndCaption = i18n("No due date");
164             mEndString.clear();
165         }
166         return true;
167     }
168 
visit(const KCalendarCore::Journal::Ptr & journal)169     bool visit(const KCalendarCore::Journal::Ptr &journal) override
170     {
171         mStartCaption = i18n("Start date: ");
172         mStartString = KCalUtils::IncidenceFormatter::dateTimeToString(journal->dtStart(), journal->allDay(), false);
173         mEndCaption.clear();
174         mEndString.clear();
175         return true;
176     }
177 
visit(const KCalendarCore::FreeBusy::Ptr & fb)178     bool visit(const KCalendarCore::FreeBusy::Ptr &fb) override
179     {
180         Q_UNUSED(fb)
181         return true;
182     }
183 };
184 
printCaptionAndText(QPainter & p,QRect box,const QString & caption,const QString & text,const QFont & captionFont,const QFont & textFont)185 int CalPrintIncidence::printCaptionAndText(QPainter &p, QRect box, const QString &caption, const QString &text, const QFont &captionFont, const QFont &textFont)
186 {
187     QFontMetrics captionFM(captionFont);
188     int textWd = captionFM.horizontalAdvance(caption);
189     QRect textRect(box);
190 
191     QFont oldFont(p.font());
192     p.setFont(captionFont);
193     p.drawText(box, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine, caption);
194 
195     if (!text.isEmpty()) {
196         textRect.setLeft(textRect.left() + textWd);
197         p.setFont(textFont);
198         p.drawText(textRect, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine, text);
199     }
200     p.setFont(oldFont);
201     return textRect.bottom();
202 }
203 
print(QPainter & p,int width,int height)204 void CalPrintIncidence::print(QPainter &p, int width, int height)
205 {
206     QFont oldFont(p.font());
207     QFont textFont(QStringLiteral("sans-serif"), 11, QFont::Normal);
208     QFont captionFont(QStringLiteral("sans-serif"), 11, QFont::Bold);
209     p.setFont(textFont);
210     int lineHeight = p.fontMetrics().lineSpacing();
211     QString cap;
212     QString txt;
213 
214     KCalendarCore::Incidence::List::ConstIterator it;
215     for (it = mSelectedIncidences.constBegin(); it != mSelectedIncidences.constEnd(); ++it) {
216         // don't do anything on a 0-pointer!
217         if (!(*it)) {
218             continue;
219         }
220         if (it != mSelectedIncidences.constBegin()) {
221             mPrinter->newPage();
222         }
223 
224         const bool isJournal = ((*it)->type() == KCalendarCore::Incidence::TypeJournal);
225 
226         //  PAGE Layout (same for landscape and portrait! astonishingly, it looks good with both!):
227         //  +-----------------------------------+
228         //  | Header:  Summary                  |
229         //  +===================================+
230         //  | start: ______   end: _________    |
231         //  | repeats: ___________________      |
232         //  | reminder: __________________      |
233         //  +-----------------------------------+
234         //  | Location: ______________________  |
235         //  +------------------------+----------+
236         //  | Description:           | Notes or |
237         //  |                        | Subitems |
238         //  |                        |          |
239         //  |                        |          |
240         //  |                        |          |
241         //  |                        |          |
242         //  |                        |          |
243         //  |                        |          |
244         //  |                        |          |
245         //  |                        |          |
246         //  +------------------------+----------+
247         //  | Attachments:           | Settings |
248         //  |                        |          |
249         //  +------------------------+----------+
250         //  | Attendees:                        |
251         //  |                                   |
252         //  +-----------------------------------+
253         //  | Categories: _____________________ |
254         //  +-----------------------------------+
255 
256         QRect box(0, 0, width, height);
257         QRect titleBox(box);
258         titleBox.setHeight(headerHeight());
259         QColor headerColor = mUseColors ? categoryBgColor(*it) : QColor();
260         // Draw summary as header, no small calendars in title bar, expand height if needed
261         int titleBottom = drawHeader(p, (*it)->summary(), QDate(), QDate(), titleBox, true, headerColor);
262         titleBox.setBottom(titleBottom);
263 
264         QRect timesBox(titleBox);
265         timesBox.setTop(titleBox.bottom() + padding());
266         timesBox.setHeight(height / 8);
267 
268         TimePrintStringsVisitor stringVis;
269         int h = timesBox.top();
270         if (stringVis.act(*it)) {
271             QRect textRect(timesBox.left() + padding(), timesBox.top() + padding(), 0, lineHeight);
272             textRect.setRight(timesBox.center().x());
273             h = printCaptionAndText(p, textRect, stringVis.mStartCaption, stringVis.mStartString, captionFont, textFont);
274 
275             textRect.setLeft(textRect.right());
276             textRect.setRight(timesBox.right() - padding());
277             h = qMax(printCaptionAndText(p, textRect, stringVis.mEndCaption, stringVis.mEndString, captionFont, textFont), h);
278         }
279 
280         // Recurrence Printing
281         if ((*it)->recurs()) {
282             QRect recurBox(timesBox.left() + padding(), h + padding(), timesBox.right() - padding(), lineHeight);
283             KCalendarCore::Recurrence *recurs = (*it)->recurrence();
284             QString displayString = KCalUtils::IncidenceFormatter::recurrenceString((*it));
285             // exception dates
286             QString exceptString;
287             if (!recurs->exDates().isEmpty()) {
288                 exceptString = i18nc("except for listed dates", " except");
289                 for (int i = 0; i < recurs->exDates().size(); ++i) {
290                     exceptString.append(QLatin1Char(' '));
291                     exceptString.append(QLocale::system().toString(recurs->exDates().at(i), QLocale::ShortFormat));
292                 }
293             }
294             displayString.append(exceptString);
295             h = qMax(printCaptionAndText(p, recurBox, i18n("Repeats: "), displayString, captionFont, textFont), h);
296         }
297 
298         if (!isJournal) {
299             // Alarms Printing
300             QRect alarmBox(timesBox.left() + padding(), h + padding(), timesBox.right() - padding(), lineHeight);
301             KCalendarCore::Alarm::List alarms = (*it)->alarms();
302             if (alarms.isEmpty()) {
303                 cap = i18n("No reminders");
304                 txt.clear();
305             } else {
306                 cap = i18np("Reminder: ", "%1 reminders: ", alarms.count());
307 
308                 QStringList alarmStrings;
309                 KCalendarCore::Alarm::List::ConstIterator it;
310                 alarmStrings.reserve(alarms.count());
311                 for (it = alarms.constBegin(); it != alarms.constEnd(); ++it) {
312                     KCalendarCore::Alarm::Ptr alarm = *it;
313 
314                     // Alarm offset, copied from koeditoralarms.cpp:
315                     KLocalizedString offsetstr;
316                     int offset = 0;
317                     if (alarm->hasStartOffset()) {
318                         offset = alarm->startOffset().asSeconds();
319                         if (offset < 0) {
320                             offsetstr = ki18nc("N days/hours/minutes before/after the start/end", "%1 before the start");
321                             offset = -offset;
322                         } else {
323                             offsetstr = ki18nc("N days/hours/minutes before/after the start/end", "%1 after the start");
324                         }
325                     } else if (alarm->hasEndOffset()) {
326                         offset = alarm->endOffset().asSeconds();
327                         if (offset < 0) {
328                             offsetstr = ki18nc("N days/hours/minutes before/after the start/end", "%1 before the end");
329                             offset = -offset;
330                         } else {
331                             offsetstr = ki18nc("N days/hours/minutes before/after the start/end", "%1 after the end");
332                         }
333                     }
334 
335                     offset = offset / 60; // make minutes
336                     int useoffset = 0;
337 
338                     if (offset % (24 * 60) == 0 && offset > 0) { // divides evenly into days?
339                         useoffset = offset / (24 * 60);
340                         offsetstr = offsetstr.subs(i18np("1 day", "%1 days", useoffset));
341                     } else if (offset % 60 == 0 && offset > 0) { // divides evenly into hours?
342                         useoffset = offset / 60;
343                         offsetstr = offsetstr.subs(i18np("1 hour", "%1 hours", useoffset));
344                     } else {
345                         useoffset = offset;
346                         offsetstr = offsetstr.subs(i18np("1 minute", "%1 minutes", useoffset));
347                     }
348                     alarmStrings << offsetstr.toString();
349                 }
350                 txt = alarmStrings.join(i18nc("Spacer for the joined list of categories/tags", ", "));
351             }
352             h = qMax(printCaptionAndText(p, alarmBox, cap, txt, captionFont, textFont), h);
353         }
354         QRect organizerBox(timesBox.left() + padding(), h + padding(), timesBox.right() - padding(), lineHeight);
355         h = qMax(printCaptionAndText(p, organizerBox, i18n("Organizer: "), (*it)->organizer().fullName(), captionFont, textFont), h);
356 
357         // Finally, draw the frame around the time information...
358         timesBox.setBottom(qMax(timesBox.bottom(), h + padding()));
359         drawBox(p, BOX_BORDER_WIDTH, timesBox);
360 
361         QRect locationBox(timesBox);
362         locationBox.setTop(timesBox.bottom() + padding());
363         locationBox.setHeight(0);
364         int locationBottom = 0;
365         if (!isJournal) {
366             locationBottom = drawBoxWithCaption(p,
367                                                 locationBox,
368                                                 i18n("Location: "),
369                                                 (*it)->location(),
370                                                 /*sameLine=*/true,
371                                                 /*expand=*/true,
372                                                 captionFont,
373                                                 textFont);
374         }
375         locationBox.setBottom(locationBottom);
376 
377         // Now start constructing the boxes from the bottom:
378         QRect footerBox(locationBox);
379         footerBox.setBottom(box.bottom());
380         footerBox.setTop(footerBox.bottom() - lineHeight - 2 * padding());
381 
382         QRect categoriesBox(footerBox);
383         categoriesBox.setBottom(footerBox.top());
384         categoriesBox.setTop(categoriesBox.bottom() - lineHeight - 2 * padding());
385         QRect attendeesBox(box.left(), categoriesBox.top() - padding() - box.height() / 9, box.width(), box.height() / 9);
386         QRect attachmentsBox(box.left(), attendeesBox.top() - padding() - box.height() / 9, box.width() * 3 / 4 - padding(), box.height() / 9);
387         QRect optionsBox(isJournal ? box.left() : attachmentsBox.right() + padding(), attachmentsBox.top(), 0, 0);
388         optionsBox.setRight(box.right());
389         optionsBox.setBottom(attachmentsBox.bottom());
390         QRect notesBox(optionsBox.left(), isJournal ? (timesBox.bottom() + padding()) : (locationBox.bottom() + padding()), optionsBox.width(), 0);
391         notesBox.setBottom(optionsBox.top() - padding());
392         QRect descriptionBox(notesBox);
393         descriptionBox.setLeft(box.left());
394         descriptionBox.setRight(attachmentsBox.right());
395 
396         // Adjust boxes depending on the show options...
397         if (!mShowSubitemsNotes || isJournal) {
398             descriptionBox.setRight(box.right());
399         }
400         if (!mShowAttachments || !mShowAttendees) {
401             descriptionBox.setBottom(attachmentsBox.bottom());
402             optionsBox.setTop(attendeesBox.top());
403             optionsBox.setBottom(attendeesBox.bottom());
404             notesBox.setBottom(attachmentsBox.bottom());
405             if (mShowOptions) {
406                 attendeesBox.setRight(attachmentsBox.right());
407             }
408             if (!mShowAttachments && !mShowAttendees) {
409                 if (mShowSubitemsNotes) {
410                     descriptionBox.setBottom(attendeesBox.bottom());
411                 }
412                 if (!mShowOptions) {
413                     descriptionBox.setBottom(attendeesBox.bottom());
414                     notesBox.setBottom(attendeesBox.bottom());
415                 }
416             }
417         }
418         if (mShowAttachments && !isJournal) {
419             if (!mShowOptions) {
420                 attachmentsBox.setRight(box.right());
421                 attachmentsBox.setRight(box.right());
422             }
423             if (!mShowAttendees) {
424                 attachmentsBox.setTop(attendeesBox.top());
425                 attachmentsBox.setBottom(attendeesBox.bottom());
426             }
427         }
428         int newBottom = drawBoxWithCaption(p,
429                                            descriptionBox,
430                                            i18n("Description:"),
431                                            (*it)->description(),
432                                            /*sameLine=*/false,
433                                            /*expand=*/false,
434                                            captionFont,
435                                            textFont,
436                                            (*it)->descriptionIsRich());
437         if (mShowNoteLines) {
438             drawNoteLines(p, descriptionBox, newBottom);
439         }
440 
441         Akonadi::Item item = mCalendar->item((*it)->uid());
442         Akonadi::Item::List relations = mCalendar->childItems(item.id());
443 
444         if (mShowSubitemsNotes && !isJournal) {
445             if (relations.isEmpty() || (*it)->type() != KCalendarCore::Incidence::TypeTodo) {
446                 int notesPosition = drawBoxWithCaption(p,
447                                                        notesBox,
448                                                        i18n("Notes:"),
449                                                        QString(),
450                                                        /*sameLine=*/false,
451                                                        /*expand=*/false,
452                                                        captionFont,
453                                                        textFont);
454                 if (mShowNoteLines) {
455                     drawNoteLines(p, notesBox, notesPosition);
456                 }
457             } else {
458                 QString subitemCaption;
459                 if (relations.isEmpty()) {
460                     subitemCaption = i18n("No Subitems");
461                     txt.clear();
462                 } else {
463                     subitemCaption = i18np("1 Subitem:", "%1 Subitems:", relations.count());
464                 }
465 
466                 QString subitemString;
467                 QString statusString;
468                 QString datesString;
469                 int count = 0;
470                 for (const Akonadi::Item &item : std::as_const(relations)) {
471                     KCalendarCore::Todo::Ptr todo = CalendarSupport::todo(item);
472                     ++count;
473                     if (!todo) { // defensive, skip any zero pointers
474                         continue;
475                     }
476                     // format the status
477                     statusString = KCalUtils::Stringify::incidenceStatus(todo->status());
478                     if (statusString.isEmpty()) {
479                         if (todo->status() == KCalendarCore::Incidence::StatusNone) {
480                             statusString = i18nc("no status", "none");
481                         } else {
482                             statusString = i18nc("unknown status", "unknown");
483                         }
484                     }
485                     // format the dates if provided
486                     datesString.clear();
487                     if (todo->dtStart().isValid()) {
488                         datesString +=
489                             i18nc("subitem start date", "Start Date: %1\n", QLocale().toString(todo->dtStart().toLocalTime().date(), QLocale::ShortFormat));
490                         if (!todo->allDay()) {
491                             datesString +=
492                                 i18nc("subitem start time", "Start Time: %1\n", QLocale().toString(todo->dtStart().toLocalTime().time(), QLocale::ShortFormat));
493                         }
494                     }
495                     if (todo->dateTime(KCalendarCore::Incidence::RoleEnd).isValid()) {
496                         subitemString +=
497                             i18nc("subitem due date",
498                                   "Due Date: %1\n",
499                                   QLocale().toString(todo->dateTime(KCalendarCore::Incidence ::RoleEnd).toLocalTime().date(), QLocale::ShortFormat));
500 
501                         if (!todo->allDay()) {
502                             subitemString +=
503                                 i18nc("subitem due time",
504                                       "Due Time: %1\n",
505                                       QLocale().toString(todo->dateTime(KCalendarCore::Incidence::RoleEnd).toLocalTime().time(), QLocale::ShortFormat));
506                         }
507                     }
508                     subitemString += i18nc("subitem counter", "%1: ", count);
509                     subitemString += todo->summary();
510                     subitemString += QLatin1Char('\n');
511                     if (!datesString.isEmpty()) {
512                         subitemString += datesString;
513                         subitemString += QLatin1Char('\n');
514                     }
515                     subitemString += i18nc("subitem Status: statusString", "Status: %1\n", statusString);
516                     subitemString += KCalUtils::IncidenceFormatter::recurrenceString(todo) + QLatin1Char('\n');
517                     subitemString += i18nc("subitem Priority: N", "Priority: %1\n", QString::number(todo->priority()));
518                     subitemString += i18nc("subitem Secrecy: secrecyString", "Secrecy: %1\n", KCalUtils::Stringify::incidenceSecrecy(todo->secrecy()));
519                     subitemString += QLatin1Char('\n');
520                 }
521                 drawBoxWithCaption(p,
522                                    notesBox,
523                                    subitemCaption,
524                                    subitemString,
525                                    /*sameLine=*/false,
526                                    /*expand=*/false,
527                                    captionFont,
528                                    textFont);
529             }
530         }
531 
532         if (mShowAttachments && !isJournal) {
533             const KCalendarCore::Attachment::List attachments = (*it)->attachments();
534             QString attachmentCaption;
535             if (attachments.isEmpty()) {
536                 attachmentCaption = i18n("No Attachments");
537                 txt.clear();
538             } else {
539                 attachmentCaption = i18np("1 Attachment:", "%1 Attachments:", attachments.count());
540             }
541             QString attachmentString;
542             KCalendarCore::Attachment::List::ConstIterator ait = attachments.constBegin();
543             for (; ait != attachments.constEnd(); ++ait) {
544                 if (!attachmentString.isEmpty()) {
545                     attachmentString += i18nc("Spacer for list of attachments", "  ");
546                 }
547                 attachmentString.append((*ait).label());
548             }
549             drawBoxWithCaption(p,
550                                attachmentsBox,
551                                attachmentCaption,
552                                attachmentString,
553                                /*sameLine=*/false,
554                                /*expand=*/false,
555                                captionFont,
556                                textFont);
557         }
558         if (mShowAttendees) {
559             const KCalendarCore::Attendee::List attendees = (*it)->attendees();
560             QString attendeeCaption;
561             if (attendees.isEmpty()) {
562                 attendeeCaption = i18n("No Attendees");
563             } else {
564                 attendeeCaption = i18np("1 Attendee:", "%1 Attendees:", attendees.count());
565             }
566             QString attendeeString;
567             KCalendarCore::Attendee::List::ConstIterator ait = attendees.constBegin();
568             for (; ait != attendees.constEnd(); ++ait) {
569                 if (!attendeeString.isEmpty()) {
570                     attendeeString += QLatin1Char('\n');
571                 }
572                 attendeeString += i18nc(
573                     "Formatting of an attendee: "
574                     "'Name (Role): Status', e.g. 'Reinhold Kainhofer "
575                     "<reinhold@kainhofer.com> (Participant): Awaiting Response'",
576                     "%1 (%2): %3",
577                     (*ait).fullName(),
578                     KCalUtils::Stringify::attendeeRole((*ait).role()),
579                     KCalUtils::Stringify::attendeeStatus((*ait).status()));
580             }
581             drawBoxWithCaption(p,
582                                attendeesBox,
583                                attendeeCaption,
584                                attendeeString,
585                                /*sameLine=*/false,
586                                /*expand=*/false,
587                                captionFont,
588                                textFont);
589         }
590 
591         if (mShowOptions) {
592             QString optionsString;
593             if (!KCalUtils::Stringify::incidenceStatus((*it)->status()).isEmpty()) {
594                 optionsString += i18n("Status: %1", KCalUtils::Stringify::incidenceStatus((*it)->status()));
595                 optionsString += QLatin1Char('\n');
596             }
597             if (!KCalUtils::Stringify::incidenceSecrecy((*it)->secrecy()).isEmpty()) {
598                 optionsString += i18n("Secrecy: %1", KCalUtils::Stringify::incidenceSecrecy((*it)->secrecy()));
599                 optionsString += QLatin1Char('\n');
600             }
601             if ((*it)->type() == KCalendarCore::Incidence::TypeEvent) {
602                 KCalendarCore::Event::Ptr e = (*it).staticCast<KCalendarCore::Event>();
603                 if (e->transparency() == KCalendarCore::Event::Opaque) {
604                     optionsString += i18n("Show as: Busy");
605                 } else {
606                     optionsString += i18n("Show as: Free");
607                 }
608                 optionsString += QLatin1Char('\n');
609             } else if ((*it)->type() == KCalendarCore::Incidence::TypeTodo) {
610                 KCalendarCore::Todo::Ptr t = (*it).staticCast<KCalendarCore::Todo>();
611                 if (t->isOverdue()) {
612                     optionsString += i18n("This task is overdue!");
613                     optionsString += QLatin1Char('\n');
614                 }
615             } else if ((*it)->type() == KCalendarCore::Incidence::TypeJournal) {
616                 // TODO: Anything Journal-specific?
617             }
618             drawBoxWithCaption(p, optionsBox, i18n("Settings: "), optionsString, /*sameLine=*/false, /*expand=*/false, captionFont, textFont);
619         }
620 
621         drawBoxWithCaption(p,
622                            categoriesBox,
623                            i18n("Tags: "),
624                            (*it)->categories().join(i18nc("Spacer for the joined list of categories/tags", ", ")),
625                            /*sameLine=*/true,
626                            /*expand=*/false,
627                            captionFont,
628                            textFont);
629 
630         if (mPrintFooter) {
631             drawFooter(p, footerBox);
632         }
633     }
634     p.setFont(oldFont);
635 }
636 
637 /**************************************************************
638  *           Print Timetables
639  **************************************************************/
640 
CalPrintTimetable()641 CalPrintTimetable::CalPrintTimetable()
642     : CalPrintPluginBase()
643 {
644 }
645 
~CalPrintTimetable()646 CalPrintTimetable::~CalPrintTimetable()
647 {
648 }
649 
doLoadConfig()650 void CalPrintTimetable::doLoadConfig()
651 {
652     CalPrintPluginBase::doLoadConfig();
653     if (mConfig) {
654         KConfigGroup grp(mConfig, groupName());
655         QDate dt = QDate::currentDate(); // any valid QDate will do
656         QTime tm1(dayStart());
657         QDateTime startTm(dt, tm1);
658         QDateTime endTm(dt, tm1.addSecs(12 * 60 * 60));
659         mStartTime = grp.readEntry("Start time", startTm).time();
660         mEndTime = grp.readEntry("End time", endTm).time();
661         mIncludeDescription = grp.readEntry("Include description", false);
662         mIncludeCategories = grp.readEntry("Include categories", false);
663         mIncludeTodos = grp.readEntry("Include todos", false);
664         mIncludeAllEvents = grp.readEntry("Include all events", false);
665         mSingleLineLimit = grp.readEntry("Single line limit", false);
666         mExcludeTime = grp.readEntry("Exclude time", false);
667     }
668 }
669 
doSaveConfig()670 void CalPrintTimetable::doSaveConfig()
671 {
672     if (mConfig) {
673         KConfigGroup grp(mConfig, groupName());
674         QDateTime dt = QDateTime::currentDateTime(); // any valid QDateTime will do
675         dt.setTime(mStartTime);
676         grp.writeEntry("Start time", dt);
677         dt.setTime(mEndTime);
678         grp.writeEntry("End time", dt);
679         grp.writeEntry("Include description", mIncludeDescription);
680         grp.writeEntry("Include categories", mIncludeCategories);
681         grp.writeEntry("Include todos", mIncludeTodos);
682         grp.writeEntry("Include all events", mIncludeAllEvents);
683         grp.writeEntry("Single line limit", mSingleLineLimit);
684         grp.writeEntry("Exclude time", mExcludeTime);
685     }
686     CalPrintPluginBase::doSaveConfig();
687 }
688 
cleanString(const QString & instr)689 static QString cleanString(const QString &instr)
690 {
691     QString ret = instr;
692     return ret.replace(QLatin1Char('\n'), QLatin1Char(' '));
693 }
694 
drawAllDayBox(QPainter & p,const KCalendarCore::Event::List & eventList,QDate qd,QRect box,const QList<QDate> & workDays)695 void CalPrintTimetable::drawAllDayBox(QPainter &p,
696                                       const KCalendarCore::Event::List &eventList,
697                                       QDate qd,
698                                       QRect box,
699                                       const QList<QDate> &workDays)
700 {
701     int lineSpacing = p.fontMetrics().lineSpacing();
702 
703     if (!workDays.contains(qd)) {
704         drawShadedBox(p, BOX_BORDER_WIDTH, sHolidayBackground, box);
705     } else {
706         drawBox(p, BOX_BORDER_WIDTH, box);
707     }
708 
709     QRect eventBox(box);
710     eventBox.setTop(box.top() + padding());
711     eventBox.setBottom(eventBox.top() + lineSpacing);
712 
713     for (const KCalendarCore::Event::Ptr &currEvent : std::as_const(eventList)) {
714         if (!currEvent
715             || !currEvent->allDay()
716             || (mExcludeConfidential && currEvent->secrecy() == KCalendarCore::Incidence::SecrecyConfidential)
717             || (mExcludePrivate && currEvent->secrecy() == KCalendarCore::Incidence::SecrecyPrivate)) {
718             continue;
719         }
720         QString str;
721         if (currEvent->location().isEmpty()) {
722             str = cleanString(currEvent->summary());
723         } else {
724             str = i18nc("summary, location", "%1, %2", cleanString(currEvent->summary()), cleanString(currEvent->location()));
725         }
726         if (mIncludeCategories && !currEvent->categoriesStr().isEmpty()) {
727                 str = i18nc("summary, categories", "%1, %2", str, currEvent->categoriesStr());
728         }
729         printEventString(p, eventBox, str);
730         eventBox.setTop(eventBox.bottom());
731         eventBox.setBottom(eventBox.top() + lineSpacing);
732     }
733 }
734 
drawTimeTable(QPainter & p,QDate fromDate,QDate toDate,QRect box)735 void CalPrintTimetable::drawTimeTable(QPainter &p,
736                                        QDate fromDate,
737                                        QDate toDate,
738                                        QRect box)
739 {
740     QTime myFromTime = mStartTime;
741     QTime myToTime = mEndTime;
742     int maxAllDayEvents = 0;
743     QDate curDate(fromDate);
744     while (curDate <= toDate) {
745         KCalendarCore::Event::List eventList = mCalendar->events(curDate, QTimeZone::systemTimeZone());
746         const auto holidays = holiday(curDate);
747         int allDayEvents = holiday(curDate).isEmpty() ? 0 : 1;
748         for (const KCalendarCore::Event::Ptr &event : std::as_const(eventList)) {
749             Q_ASSERT(event);
750             if (!event
751                 || (mExcludeConfidential && event->secrecy() == KCalendarCore::Incidence::SecrecyConfidential)
752                 || (mExcludePrivate && event->secrecy() == KCalendarCore::Incidence::SecrecyPrivate)) {
753                 continue;
754             }
755             if (event->allDay()) {
756                 allDayEvents += 1;
757             } else if (mIncludeAllEvents) {
758                 if (event->dtStart().time() < myFromTime) {
759                     myFromTime = event->dtStart().time();
760                 }
761                 if (event->dtEnd().time() > myToTime) {
762                     myToTime = event->dtEnd().time();
763                 }
764             }
765         }
766         if (allDayEvents > maxAllDayEvents) {
767             maxAllDayEvents = allDayEvents;
768         }
769         curDate = curDate.addDays(1);
770     }
771 
772     p.setFont(QFont(QStringLiteral("sans-serif"), 11, QFont::Normal));
773     const int lineSpacing = p.fontMetrics().lineSpacing();
774 
775     int timelineWidth = TIMELINE_WIDTH + padding();
776 
777     QRect dowBox(box);
778     dowBox.setLeft(box.left() + timelineWidth);
779     dowBox.setHeight(mSubHeaderHeight);
780     drawDaysOfWeek(p, fromDate, toDate, dowBox);
781 
782     int tlTop = dowBox.bottom();
783 
784     int alldayHeight = 0;
785     if (maxAllDayEvents > 0) {
786         // Draw the side bar for all-day events.
787         const auto alldayLabel =  i18nc("label for timetable all-day boxes", "All day");
788         const QFont oldFont(p.font());
789         p.setFont(QFont(QStringLiteral("sans-serif"), 9, QFont::Normal));
790         const auto labelHeight = p.fontMetrics().horizontalAdvance(alldayLabel) + 2*padding();
791         alldayHeight = std::max(maxAllDayEvents*lineSpacing + 2*padding(), labelHeight);
792         drawVerticalBox(p,
793                         BOX_BORDER_WIDTH,
794                         QRect(0, tlTop, TIMELINE_WIDTH, alldayHeight),
795                         alldayLabel,
796                         Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextWordWrap);
797         p.setFont(oldFont);
798         tlTop += alldayHeight +padding();
799     }
800 
801     QRect tlBox(box);
802     tlBox.setWidth(TIMELINE_WIDTH);
803     tlBox.setTop(tlTop);
804     drawTimeLine(p, myFromTime, myToTime, tlBox);
805 
806     // draw each day
807     curDate = fromDate;
808     int i = 0;
809     double cellWidth = double(dowBox.width() - 1) / double(fromDate.daysTo(toDate) + 1);
810     QRect allDayBox(dowBox.left(), dowBox.bottom(), cellWidth, alldayHeight);
811     const QList<QDate> workDays = CalendarSupport::workDays(fromDate, toDate);
812     while (curDate <= toDate) {
813         KCalendarCore::Event::List eventList =
814             mCalendar->events(curDate, QTimeZone::systemTimeZone(), KCalendarCore::EventSortStartDate, KCalendarCore::SortDirectionAscending);
815 
816         allDayBox.setLeft(dowBox.left() + int(i * cellWidth));
817         allDayBox.setRight(dowBox.left() + int((i + 1) * cellWidth));
818         if (maxAllDayEvents > 0) {
819             if (const auto h = holidayEvent(curDate)) {
820                 eventList.prepend(h);
821             }
822             drawAllDayBox(p, eventList, curDate, allDayBox, workDays);
823         }
824 
825         QRect dayBox(allDayBox);
826         dayBox.setTop(tlTop);
827         dayBox.setBottom(box.bottom());
828         drawAgendaDayBox(p,
829                          eventList,
830                          curDate,
831                          false,
832                          myFromTime,
833                          myToTime,
834                          dayBox,
835                          mIncludeDescription,
836                          mIncludeCategories,
837                          mExcludeTime,
838                          workDays);
839 
840         ++i;
841         curDate = curDate.addDays(1);
842     }
843 }
844 
845 /**************************************************************
846  *           Print Day
847  **************************************************************/
848 
CalPrintDay()849 CalPrintDay::CalPrintDay()
850     : CalPrintTimetable()
851 {
852 }
853 
~CalPrintDay()854 CalPrintDay::~CalPrintDay()
855 {
856 }
857 
createConfigWidget(QWidget * w)858 QWidget *CalPrintDay::createConfigWidget(QWidget *w)
859 {
860     return new CalPrintDayConfig(w);
861 }
862 
readSettingsWidget()863 void CalPrintDay::readSettingsWidget()
864 {
865     auto cfg = dynamic_cast<CalPrintDayConfig *>((QWidget *)mConfigWidget);
866     if (cfg) {
867         mFromDate = cfg->mFromDate->date();
868         mToDate = cfg->mToDate->date();
869 
870         if (cfg->mPrintTypeFilofax->isChecked()) {
871             mDayPrintType = Filofax;
872         } else if (cfg->mPrintTypeTimetable->isChecked()) {
873             mDayPrintType = Timetable;
874         } else {
875             mDayPrintType = SingleTimetable;
876         }
877 
878         mStartTime = cfg->mFromTime->time();
879         mEndTime = cfg->mToTime->time();
880         mIncludeAllEvents = cfg->mIncludeAllEvents->isChecked();
881 
882         mIncludeDescription = cfg->mIncludeDescription->isChecked();
883         mIncludeCategories = cfg->mIncludeCategories->isChecked();
884         mSingleLineLimit = cfg->mSingleLineLimit->isChecked();
885         mIncludeTodos = cfg->mIncludeTodos->isChecked();
886         mUseColors = cfg->mColors->isChecked();
887         mPrintFooter = cfg->mPrintFooter->isChecked();
888         mShowNoteLines = cfg->mShowNoteLines->isChecked();
889         mExcludeTime = cfg->mExcludeTime->isChecked();
890         mExcludeConfidential = cfg->mExcludeConfidential->isChecked();
891         mExcludePrivate = cfg->mExcludePrivate->isChecked();
892     }
893 }
894 
setSettingsWidget()895 void CalPrintDay::setSettingsWidget()
896 {
897     auto cfg = dynamic_cast<CalPrintDayConfig *>((QWidget *)mConfigWidget);
898     if (cfg) {
899         cfg->mFromDate->setDate(mFromDate);
900         cfg->mToDate->setDate(mToDate);
901 
902         cfg->mPrintTypeFilofax->setChecked(mDayPrintType == Filofax);
903         cfg->mPrintTypeTimetable->setChecked(mDayPrintType == Timetable);
904         cfg->mPrintTypeSingleTimetable->setChecked(mDayPrintType == SingleTimetable);
905 
906         cfg->mFromTime->setTime(mStartTime);
907         cfg->mToTime->setTime(mEndTime);
908         cfg->mIncludeAllEvents->setChecked(mIncludeAllEvents);
909 
910         cfg->mIncludeDescription->setChecked(mIncludeDescription);
911         cfg->mIncludeCategories->setChecked(mIncludeCategories);
912         cfg->mSingleLineLimit->setChecked(mSingleLineLimit);
913         cfg->mIncludeTodos->setChecked(mIncludeTodos);
914         cfg->mColors->setChecked(mUseColors);
915         cfg->mPrintFooter->setChecked(mPrintFooter);
916         cfg->mShowNoteLines->setChecked(mShowNoteLines);
917         cfg->mExcludeTime->setChecked(mExcludeTime);
918         cfg->mExcludeConfidential->setChecked(mExcludeConfidential);
919         cfg->mExcludePrivate->setChecked(mExcludePrivate);
920     }
921 }
922 
doLoadConfig()923 void CalPrintDay::doLoadConfig()
924 {
925     CalPrintTimetable::doLoadConfig();
926     if (mConfig) {
927         KConfigGroup grp(mConfig, groupName());
928         mDayPrintType = static_cast<eDayPrintType>(grp.readEntry("Print type", static_cast<int>(Timetable)));
929     }
930     setSettingsWidget();
931 }
932 
doSaveConfig()933 void CalPrintDay::doSaveConfig()
934 {
935     readSettingsWidget();
936     if (mConfig) {
937         KConfigGroup grp(mConfig, groupName());
938         grp.writeEntry("Print type", int(mDayPrintType));
939     }
940     CalPrintTimetable::doSaveConfig();
941 }
942 
setDateRange(const QDate & from,const QDate & to)943 void CalPrintDay::setDateRange(const QDate &from, const QDate &to)
944 {
945     CalPrintPluginBase::setDateRange(from, to);
946     auto cfg = dynamic_cast<CalPrintDayConfig *>((QWidget *)mConfigWidget);
947     if (cfg) {
948         cfg->mFromDate->setDate(from);
949         cfg->mToDate->setDate(to);
950     }
951 }
952 
drawDays(QPainter & p,QRect box)953 void CalPrintDay::drawDays(QPainter &p, QRect box)
954 {
955     const int numberOfDays = mFromDate.daysTo(mToDate) + 1;
956     int vcells;
957     const bool portrait = (box.height() > box.width());
958     int cellWidth;
959     if (portrait) {
960         // 2 columns
961         vcells = std::ceil(static_cast<double>(numberOfDays) / 2.0);
962         if (numberOfDays > 1) {
963             cellWidth = box.width() / 2;
964         } else {
965             cellWidth = box.width();
966         }
967     } else {
968         // landscape: N columns
969         vcells = 1;
970         cellWidth = box.width() / numberOfDays;
971     }
972     const int cellHeight = box.height() / vcells;
973     QDate weekDate = mFromDate;
974     for (int i = 0; i < numberOfDays; ++i, weekDate = weekDate.addDays(1)) {
975         const int hpos = i / vcells;
976         const int vpos = i % vcells;
977         const QRect dayBox(box.left() + cellWidth * hpos, box.top() + cellHeight * vpos, cellWidth, cellHeight);
978         drawDayBox(p,
979                    weekDate,
980                    mStartTime,
981                    mEndTime,
982                    dayBox,
983                    true,
984                    true,
985                    true,
986                    mSingleLineLimit,
987                    mIncludeDescription,
988                    mIncludeCategories);
989     } // for i through all selected days
990 }
991 
print(QPainter & p,int width,int height)992 void CalPrintDay::print(QPainter &p, int width, int height)
993 {
994     QDate curDay(mFromDate);
995 
996     QRect headerBox(0, 0, width, headerHeight());
997     QRect footerBox(0, height - footerHeight(), width, footerHeight());
998     height -= footerHeight();
999     QRect daysBox(headerBox);
1000     daysBox.setTop(headerBox.bottom() + padding());
1001     daysBox.setBottom(height);
1002 
1003     auto local = QLocale::system();
1004 
1005     switch (mDayPrintType) {
1006     case Filofax:
1007     case SingleTimetable: {
1008         QString line1 = local.toString(mFromDate, QLocale::ShortFormat);
1009         QString line2 = local.toString(mToDate, QLocale::ShortFormat);
1010         QString title;
1011         if (mFromDate == mToDate) {
1012             title = line1;
1013         } else {
1014             title =  i18nc("date from-to", "%1\u2013%2", line1, line2);
1015         }
1016         drawHeader(p, title, mFromDate, QDate(), headerBox);
1017         if (mDayPrintType == Filofax) {
1018             drawDays(p, daysBox);
1019         } else if (mDayPrintType == SingleTimetable) {
1020             drawTimeTable(p, mFromDate, mToDate, daysBox);
1021         }
1022         if (mPrintFooter) {
1023             drawFooter(p, footerBox);
1024         }
1025         break;
1026     }
1027 
1028     case Timetable:
1029     default:
1030         do {
1031             QTime curStartTime(mStartTime);
1032             QTime curEndTime(mEndTime);
1033 
1034             // For an invalid time range, simply show one hour, starting at the hour
1035             // before the given start time
1036             if (curEndTime <= curStartTime) {
1037                 curStartTime = QTime(curStartTime.hour(), 0, 0);
1038                 curEndTime = curStartTime.addSecs(3600);
1039             }
1040 
1041             drawHeader(p, local.toString(curDay, QLocale::ShortFormat), curDay, QDate(), headerBox);
1042             drawTimeTable(p, curDay, curDay, daysBox);
1043             if (mPrintFooter) {
1044                 drawFooter(p, footerBox);
1045             }
1046 
1047             curDay = curDay.addDays(1);
1048             if (curDay <= mToDate) {
1049                 mPrinter->newPage();
1050             }
1051         } while (curDay <= mToDate);
1052     } // switch
1053 }
1054 
1055 /**************************************************************
1056  *           Print Week
1057  **************************************************************/
1058 
CalPrintWeek()1059 CalPrintWeek::CalPrintWeek()
1060     : CalPrintTimetable()
1061 {
1062 }
1063 
~CalPrintWeek()1064 CalPrintWeek::~CalPrintWeek()
1065 {
1066 }
1067 
createConfigWidget(QWidget * w)1068 QWidget *CalPrintWeek::createConfigWidget(QWidget *w)
1069 {
1070     return new CalPrintWeekConfig(w);
1071 }
1072 
readSettingsWidget()1073 void CalPrintWeek::readSettingsWidget()
1074 {
1075     auto cfg = dynamic_cast<CalPrintWeekConfig *>((QWidget *)mConfigWidget);
1076     if (cfg) {
1077         mFromDate = cfg->mFromDate->date();
1078         mToDate = cfg->mToDate->date();
1079 
1080         if (cfg->mPrintTypeFilofax->isChecked()) {
1081             mWeekPrintType = Filofax;
1082         } else if (cfg->mPrintTypeTimetable->isChecked()) {
1083             mWeekPrintType = Timetable;
1084         } else if (cfg->mPrintTypeSplitWeek->isChecked()) {
1085             mWeekPrintType = SplitWeek;
1086         } else {
1087             mWeekPrintType = Timetable;
1088         }
1089 
1090         mStartTime = cfg->mFromTime->time();
1091         mEndTime = cfg->mToTime->time();
1092         mIncludeAllEvents = cfg->mIncludeAllEvents->isChecked();
1093 
1094         mShowNoteLines = cfg->mShowNoteLines->isChecked();
1095         mSingleLineLimit = cfg->mSingleLineLimit->isChecked();
1096         mIncludeTodos = cfg->mIncludeTodos->isChecked();
1097         mUseColors = cfg->mColors->isChecked();
1098         mPrintFooter = cfg->mPrintFooter->isChecked();
1099         mIncludeDescription = cfg->mIncludeDescription->isChecked();
1100         mIncludeCategories = cfg->mIncludeCategories->isChecked();
1101         mExcludeTime = cfg->mExcludeTime->isChecked();
1102         mExcludeConfidential = cfg->mExcludeConfidential->isChecked();
1103         mExcludePrivate = cfg->mExcludePrivate->isChecked();
1104     }
1105 }
1106 
setSettingsWidget()1107 void CalPrintWeek::setSettingsWidget()
1108 {
1109     auto cfg = dynamic_cast<CalPrintWeekConfig *>((QWidget *)mConfigWidget);
1110     if (cfg) {
1111         cfg->mFromDate->setDate(mFromDate);
1112         cfg->mToDate->setDate(mToDate);
1113 
1114         cfg->mPrintTypeFilofax->setChecked(mWeekPrintType == Filofax);
1115         cfg->mPrintTypeTimetable->setChecked(mWeekPrintType == Timetable);
1116         cfg->mPrintTypeSplitWeek->setChecked(mWeekPrintType == SplitWeek);
1117 
1118         cfg->mFromTime->setTime(mStartTime);
1119         cfg->mToTime->setTime(mEndTime);
1120         cfg->mIncludeAllEvents->setChecked(mIncludeAllEvents);
1121 
1122         cfg->mShowNoteLines->setChecked(mShowNoteLines);
1123         cfg->mSingleLineLimit->setChecked(mSingleLineLimit);
1124         cfg->mIncludeTodos->setChecked(mIncludeTodos);
1125         cfg->mColors->setChecked(mUseColors);
1126         cfg->mPrintFooter->setChecked(mPrintFooter);
1127         cfg->mIncludeDescription->setChecked(mIncludeDescription);
1128         cfg->mIncludeCategories->setChecked(mIncludeCategories);
1129         cfg->mExcludeTime->setChecked(mExcludeTime);
1130         cfg->mExcludeConfidential->setChecked(mExcludeConfidential);
1131         cfg->mExcludePrivate->setChecked(mExcludePrivate);
1132     }
1133     CalPrintTimetable::setSettingsWidget();
1134 }
1135 
doLoadConfig()1136 void CalPrintWeek::doLoadConfig()
1137 {
1138     CalPrintTimetable::doLoadConfig();
1139     if (mConfig) {
1140         KConfigGroup grp(mConfig, groupName());
1141         mWeekPrintType = (eWeekPrintType)(grp.readEntry("Print type", (int)Filofax));
1142     }
1143     setSettingsWidget();
1144 }
1145 
doSaveConfig()1146 void CalPrintWeek::doSaveConfig()
1147 {
1148     readSettingsWidget();
1149     if (mConfig) {
1150         KConfigGroup grp(mConfig, groupName());
1151         grp.writeEntry("Print type", int(mWeekPrintType));
1152     }
1153     CalPrintTimetable::doSaveConfig();
1154 }
1155 
defaultOrientation() const1156 QPageLayout::Orientation CalPrintWeek::defaultOrientation() const
1157 {
1158     if (mWeekPrintType == Filofax) {
1159         return QPageLayout::Portrait;
1160     } else if (mWeekPrintType == SplitWeek) {
1161         return QPageLayout::Portrait;
1162     } else {
1163         return QPageLayout::Landscape;
1164     }
1165 }
1166 
setDateRange(const QDate & from,const QDate & to)1167 void CalPrintWeek::setDateRange(const QDate &from, const QDate &to)
1168 {
1169     CalPrintPluginBase::setDateRange(from, to);
1170     auto cfg = dynamic_cast<CalPrintWeekConfig *>((QWidget *)mConfigWidget);
1171     if (cfg) {
1172         cfg->mFromDate->setDate(from);
1173         cfg->mToDate->setDate(to);
1174     }
1175 }
1176 
drawWeek(QPainter & p,QDate qd,QRect box)1177 void CalPrintWeek::drawWeek(QPainter &p,
1178                                   QDate qd,
1179                                   QRect box)
1180 {
1181     QDate weekDate = qd;
1182     const bool portrait = (box.height() > box.width());
1183     int cellWidth;
1184     int vcells;
1185     if (portrait) {
1186         cellWidth = box.width() / 2;
1187         vcells = 3;
1188     } else {
1189         cellWidth = box.width() / 6;
1190         vcells = 1;
1191     }
1192     const int cellHeight = box.height() / vcells;
1193 
1194     // correct begin of week
1195     int weekdayCol = weekdayColumn(qd.dayOfWeek());
1196     weekDate = qd.addDays(-weekdayCol);
1197 
1198     for (int i = 0; i < 7; ++i, weekDate = weekDate.addDays(1)) {
1199         // Saturday and sunday share a cell, so we have to special-case sunday
1200         int hpos = ((i < 6) ? i : (i - 1)) / vcells;
1201         int vpos = ((i < 6) ? i : (i - 1)) % vcells;
1202         QRect dayBox(box.left() + cellWidth * hpos,
1203                      box.top() + cellHeight * vpos + ((i == 6) ? (cellHeight / 2) : 0),
1204                      cellWidth,
1205                      (i < 5) ? (cellHeight) : (cellHeight / 2));
1206         drawDayBox(p,
1207                    weekDate,
1208                    mStartTime,
1209                    mEndTime,
1210                    dayBox,
1211                    true,
1212                    true,
1213                    true,
1214                    mSingleLineLimit,
1215                    mIncludeDescription,
1216                    mIncludeCategories);
1217     } // for i through all weekdays
1218 }
1219 
print(QPainter & p,int width,int height)1220 void CalPrintWeek::print(QPainter &p, int width, int height)
1221 {
1222     QDate curWeek;
1223     QDate fromWeek;
1224     QDate toWeek;
1225 
1226     // correct begin and end to first and last day of week
1227     int weekdayCol = weekdayColumn(mFromDate.dayOfWeek());
1228     fromWeek = mFromDate.addDays(-weekdayCol);
1229     weekdayCol = weekdayColumn(mToDate.dayOfWeek());
1230     toWeek = mToDate.addDays(6 - weekdayCol);
1231 
1232     curWeek = fromWeek.addDays(6);
1233     auto local = QLocale::system();
1234 
1235     QString line1;
1236     QString line2;
1237     QString title;
1238     QRect headerBox(0, 0, width, headerHeight());
1239     QRect footerBox(0, height - footerHeight(), width, footerHeight());
1240     height -= footerHeight();
1241 
1242     QRect weekBox(headerBox);
1243     weekBox.setTop(headerBox.bottom() + padding());
1244     weekBox.setBottom(height);
1245 
1246     switch (mWeekPrintType) {
1247     case Filofax:
1248         do {
1249             line1 = local.toString(curWeek.addDays(-6), QLocale::ShortFormat);
1250             line2 = local.toString(curWeek, QLocale::ShortFormat);
1251             title =  i18nc("date from-to", "%1\u2013%2", line1, line2);
1252             drawHeader(p, title, curWeek.addDays(-6), QDate(), headerBox);
1253 
1254             drawWeek(p, curWeek, weekBox);
1255 
1256             if (mPrintFooter) {
1257                 drawFooter(p, footerBox);
1258             }
1259 
1260             curWeek = curWeek.addDays(7);
1261             if (curWeek <= toWeek) {
1262                 mPrinter->newPage();
1263             }
1264         } while (curWeek <= toWeek);
1265         break;
1266 
1267     case Timetable:
1268     default:
1269         do {
1270             line1 = local.toString(curWeek.addDays(-6), QLocale::ShortFormat);
1271             line2 = local.toString(curWeek, QLocale::ShortFormat);
1272             if (orientation() == QPageLayout::Landscape) {
1273                 title = i18nc("date from - to (week number)", "%1\u2013%2 (Week %3)", line1, line2, curWeek.weekNumber());
1274             } else {
1275                 title = i18nc("date from - to\\n(week number)", "%1\u2013%2\n(Week %3)", line1, line2, curWeek.weekNumber());
1276             }
1277             drawHeader(p, title, curWeek, QDate(), headerBox);
1278 
1279             drawTimeTable(p, fromWeek, curWeek, weekBox);
1280 
1281             if (mPrintFooter) {
1282                 drawFooter(p, footerBox);
1283             }
1284 
1285             fromWeek = fromWeek.addDays(7);
1286             curWeek = fromWeek.addDays(6);
1287             if (curWeek <= toWeek) {
1288                 mPrinter->newPage();
1289             }
1290         } while (curWeek <= toWeek);
1291         break;
1292 
1293     case SplitWeek: {
1294         QRect weekBox1(weekBox);
1295         // On the left side there are four days (mo-th) plus the timeline,
1296         // on the right there are only three days (fr-su) plus the timeline. Don't
1297         // use the whole width, but rather give them the same width as on the left.
1298         weekBox1.setRight(int((width - TIMELINE_WIDTH) * 3. / 4. + TIMELINE_WIDTH));
1299         do {
1300             QDate endLeft(fromWeek.addDays(3));
1301             int hh = headerHeight();
1302 
1303             drawSplitHeaderRight(p, fromWeek, curWeek, QDate(), width, hh);
1304             drawTimeTable(p, fromWeek, endLeft, weekBox);
1305             if (mPrintFooter) {
1306                 drawFooter(p, footerBox);
1307             }
1308             mPrinter->newPage();
1309             drawSplitHeaderRight(p, fromWeek, curWeek, QDate(), width, hh);
1310             drawTimeTable(p, endLeft.addDays(1), curWeek, weekBox1);
1311 
1312             if (mPrintFooter) {
1313                 drawFooter(p, footerBox);
1314             }
1315 
1316             fromWeek = fromWeek.addDays(7);
1317             curWeek = fromWeek.addDays(6);
1318             if (curWeek <= toWeek) {
1319                 mPrinter->newPage();
1320             }
1321         } while (curWeek <= toWeek);
1322         break;
1323     }
1324     }
1325 }
1326 
1327 /**************************************************************
1328  *           Print Month
1329  **************************************************************/
1330 
CalPrintMonth()1331 CalPrintMonth::CalPrintMonth()
1332     : CalPrintPluginBase()
1333 {
1334 }
1335 
~CalPrintMonth()1336 CalPrintMonth::~CalPrintMonth()
1337 {
1338 }
1339 
createConfigWidget(QWidget * w)1340 QWidget *CalPrintMonth::createConfigWidget(QWidget *w)
1341 {
1342     return new CalPrintMonthConfig(w);
1343 }
1344 
readSettingsWidget()1345 void CalPrintMonth::readSettingsWidget()
1346 {
1347     auto cfg = dynamic_cast<CalPrintMonthConfig *>((QWidget *)mConfigWidget);
1348 
1349     if (cfg) {
1350         mFromDate = QDate(cfg->mFromYear->value(), cfg->mFromMonth->currentIndex() + 1, 1);
1351         mToDate = QDate(cfg->mToYear->value(), cfg->mToMonth->currentIndex() + 1, 1);
1352 
1353         mWeekNumbers = cfg->mWeekNumbers->isChecked();
1354         mRecurDaily = cfg->mRecurDaily->isChecked();
1355         mRecurWeekly = cfg->mRecurWeekly->isChecked();
1356         mIncludeTodos = cfg->mIncludeTodos->isChecked();
1357         mShowNoteLines = cfg->mShowNoteLines->isChecked();
1358         mSingleLineLimit = cfg->mSingleLineLimit->isChecked();
1359         mUseColors = cfg->mColors->isChecked();
1360         mPrintFooter = cfg->mPrintFooter->isChecked();
1361         mIncludeDescription = cfg->mIncludeDescription->isChecked();
1362         mIncludeCategories = cfg->mIncludeCategories->isChecked();
1363         mExcludeConfidential = cfg->mExcludeConfidential->isChecked();
1364         mExcludePrivate = cfg->mExcludePrivate->isChecked();
1365     }
1366 }
1367 
setSettingsWidget()1368 void CalPrintMonth::setSettingsWidget()
1369 {
1370     auto cfg = dynamic_cast<CalPrintMonthConfig *>((QWidget *)mConfigWidget);
1371 
1372     if (cfg) {
1373         setDateRange(mFromDate, mToDate);
1374 
1375         cfg->mWeekNumbers->setChecked(mWeekNumbers);
1376         cfg->mRecurDaily->setChecked(mRecurDaily);
1377         cfg->mRecurWeekly->setChecked(mRecurWeekly);
1378         cfg->mIncludeTodos->setChecked(mIncludeTodos);
1379         cfg->mShowNoteLines->setChecked(mShowNoteLines);
1380         cfg->mSingleLineLimit->setChecked(mSingleLineLimit);
1381         cfg->mColors->setChecked(mUseColors);
1382         cfg->mPrintFooter->setChecked(mPrintFooter);
1383         cfg->mIncludeDescription->setChecked(mIncludeDescription);
1384         cfg->mIncludeCategories->setChecked(mIncludeCategories);
1385         cfg->mExcludeConfidential->setChecked(mExcludeConfidential);
1386         cfg->mExcludePrivate->setChecked(mExcludePrivate);
1387     }
1388 }
1389 
doLoadConfig()1390 void CalPrintMonth::doLoadConfig()
1391 {
1392     CalPrintPluginBase::doLoadConfig();
1393     if (mConfig) {
1394         KConfigGroup grp(mConfig, groupName());
1395         mWeekNumbers = grp.readEntry("Print week numbers", true);
1396         mRecurDaily = grp.readEntry("Print daily incidences", true);
1397         mRecurWeekly = grp.readEntry("Print weekly incidences", true);
1398         mIncludeTodos = grp.readEntry("Include todos", false);
1399         mSingleLineLimit = grp.readEntry("Single line limit", false);
1400         mIncludeDescription = grp.readEntry("Include description", false);
1401         mIncludeCategories = grp.readEntry("Include categories", false);
1402     }
1403     setSettingsWidget();
1404 }
1405 
doSaveConfig()1406 void CalPrintMonth::doSaveConfig()
1407 {
1408     readSettingsWidget();
1409     if (mConfig) {
1410         KConfigGroup grp(mConfig, groupName());
1411         grp.writeEntry("Print week numbers", mWeekNumbers);
1412         grp.writeEntry("Print daily incidences", mRecurDaily);
1413         grp.writeEntry("Print weekly incidences", mRecurWeekly);
1414         grp.writeEntry("Include todos", mIncludeTodos);
1415         grp.writeEntry("Single line limit", mSingleLineLimit);
1416         grp.writeEntry("Include description", mIncludeDescription);
1417         grp.writeEntry("Include categories", mIncludeCategories);
1418     }
1419     CalPrintPluginBase::doSaveConfig();
1420 }
1421 
setDateRange(const QDate & from,const QDate & to)1422 void CalPrintMonth::setDateRange(const QDate &from, const QDate &to)
1423 {
1424     CalPrintPluginBase::setDateRange(from, to);
1425     auto cfg = dynamic_cast<CalPrintMonthConfig *>((QWidget *)mConfigWidget);
1426     if (cfg) {
1427         cfg->mFromMonth->clear();
1428         for (int i = 0; i < 12; ++i) {
1429             cfg->mFromMonth->addItem(QLocale().monthName(i + 1, QLocale::LongFormat));
1430         }
1431         cfg->mToMonth->clear();
1432         for (int i = 0; i < 12; ++i) {
1433             cfg->mToMonth->addItem(QLocale().monthName(i + 1, QLocale::LongFormat));
1434         }
1435         cfg->mFromMonth->setCurrentIndex(from.month() - 1);
1436         cfg->mFromYear->setValue(to.year());
1437         cfg->mToMonth->setCurrentIndex(mToDate.month() - 1);
1438         cfg->mToYear->setValue(mToDate.year());
1439     }
1440 }
1441 
print(QPainter & p,int width,int height)1442 void CalPrintMonth::print(QPainter &p, int width, int height)
1443 {
1444     QDate curMonth;
1445     QDate fromMonth;
1446     QDate toMonth;
1447 
1448     fromMonth = mFromDate.addDays(-(mFromDate.day() - 1));
1449     toMonth = mToDate.addDays(mToDate.daysInMonth() - mToDate.day());
1450 
1451     curMonth = fromMonth;
1452 
1453     QRect headerBox(0, 0, width, headerHeight());
1454     QRect footerBox(0, height - footerHeight(), width, footerHeight());
1455     height -= footerHeight();
1456 
1457     QRect monthBox(0, 0, width, height);
1458     monthBox.setTop(headerBox.bottom() + padding());
1459 
1460     do {
1461         QString title(i18nc("monthname year", "%1 %2", QLocale::system().monthName(curMonth.month()), QString::number(curMonth.year())));
1462         QDate tmp(fromMonth);
1463         int weekdayCol = weekdayColumn(tmp.dayOfWeek());
1464         tmp = tmp.addDays(-weekdayCol);
1465 
1466         drawHeader(p, title, curMonth.addMonths(-1), curMonth.addMonths(1), headerBox);
1467         drawMonthTable(p,
1468                        curMonth,
1469                        QTime(),
1470                        QTime(),
1471                        mWeekNumbers,
1472                        mRecurDaily,
1473                        mRecurWeekly,
1474                        mSingleLineLimit,
1475                        mIncludeDescription,
1476                        mIncludeCategories,
1477                        monthBox);
1478 
1479         if (mPrintFooter) {
1480             drawFooter(p, footerBox);
1481         }
1482 
1483         curMonth = curMonth.addDays(curMonth.daysInMonth());
1484         if (curMonth <= toMonth) {
1485             mPrinter->newPage();
1486         }
1487     } while (curMonth <= toMonth);
1488 }
1489 
1490 /**************************************************************
1491  *           Print Todos
1492  **************************************************************/
1493 
CalPrintTodos()1494 CalPrintTodos::CalPrintTodos()
1495     : CalPrintPluginBase()
1496 {
1497     mTodoSortField = TodoFieldUnset;
1498     mTodoSortDirection = TodoDirectionUnset;
1499 }
1500 
~CalPrintTodos()1501 CalPrintTodos::~CalPrintTodos()
1502 {
1503 }
1504 
createConfigWidget(QWidget * w)1505 QWidget *CalPrintTodos::createConfigWidget(QWidget *w)
1506 {
1507     return new CalPrintTodoConfig(w);
1508 }
1509 
readSettingsWidget()1510 void CalPrintTodos::readSettingsWidget()
1511 {
1512     auto cfg = dynamic_cast<CalPrintTodoConfig *>((QWidget *)mConfigWidget);
1513 
1514     if (cfg) {
1515         mPageTitle = cfg->mTitle->text();
1516 
1517         if (cfg->mPrintAll->isChecked()) {
1518             mTodoPrintType = TodosAll;
1519         } else if (cfg->mPrintUnfinished->isChecked()) {
1520             mTodoPrintType = TodosUnfinished;
1521         } else if (cfg->mPrintDueRange->isChecked()) {
1522             mTodoPrintType = TodosDueRange;
1523         } else {
1524             mTodoPrintType = TodosAll;
1525         }
1526 
1527         mFromDate = cfg->mFromDate->date();
1528         mToDate = cfg->mToDate->date();
1529 
1530         mIncludeDescription = cfg->mDescription->isChecked();
1531         mIncludePriority = cfg->mPriority->isChecked();
1532         mIncludeCategories = cfg->mCategories->isChecked();
1533         mIncludeStartDate = cfg->mStartDate->isChecked();
1534         mIncludeDueDate = cfg->mDueDate->isChecked();
1535         mIncludePercentComplete = cfg->mPercentComplete->isChecked();
1536         mConnectSubTodos = cfg->mConnectSubTodos->isChecked();
1537         mStrikeOutCompleted = cfg->mStrikeOutCompleted->isChecked();
1538         mExcludeConfidential = cfg->mExcludeConfidential->isChecked();
1539         mExcludePrivate = cfg->mExcludePrivate->isChecked();
1540 
1541         mTodoSortField = (eTodoSortField)cfg->mSortField->currentIndex();
1542         mTodoSortDirection = (eTodoSortDirection)cfg->mSortDirection->currentIndex();
1543 
1544         mPrintFooter = cfg->mPrintFooter->isChecked();
1545     }
1546 }
1547 
setSettingsWidget()1548 void CalPrintTodos::setSettingsWidget()
1549 {
1550     auto cfg = dynamic_cast<CalPrintTodoConfig *>((QWidget *)mConfigWidget);
1551     if (cfg) {
1552         cfg->mTitle->setText(mPageTitle);
1553 
1554         cfg->mPrintAll->setChecked(mTodoPrintType == TodosAll);
1555         cfg->mPrintUnfinished->setChecked(mTodoPrintType == TodosUnfinished);
1556         cfg->mPrintDueRange->setChecked(mTodoPrintType == TodosDueRange);
1557 
1558         cfg->mFromDate->setDate(mFromDate);
1559         cfg->mToDate->setDate(mToDate);
1560 
1561         cfg->mDescription->setChecked(mIncludeDescription);
1562         cfg->mPriority->setChecked(mIncludePriority);
1563         cfg->mCategories->setChecked(mIncludeCategories);
1564         cfg->mStartDate->setChecked(mIncludeStartDate);
1565         cfg->mDueDate->setChecked(mIncludeDueDate);
1566         cfg->mPercentComplete->setChecked(mIncludePercentComplete);
1567         cfg->mConnectSubTodos->setChecked(mConnectSubTodos);
1568         cfg->mStrikeOutCompleted->setChecked(mStrikeOutCompleted);
1569         cfg->mExcludeConfidential->setChecked(mExcludeConfidential);
1570         cfg->mExcludePrivate->setChecked(mExcludePrivate);
1571 
1572         if (mTodoSortField != TodoFieldUnset) {
1573             // do not insert if already done so.
1574             cfg->mSortField->addItem(i18nc("@option sort by title", "Title"));
1575             cfg->mSortField->addItem(i18nc("@option sort by start date/time", "Start Date"));
1576             cfg->mSortField->addItem(i18nc("@option sort by due date/time", "Due Date"));
1577             cfg->mSortField->addItem(i18nc("@option sort by priority", "Priority"));
1578             cfg->mSortField->addItem(i18nc("@option sort by percent completed", "Percent Complete"));
1579             cfg->mSortField->addItem(i18nc("@option sort by tags", "Tags"));
1580             cfg->mSortField->setCurrentIndex(mTodoSortField);
1581         }
1582 
1583         if (mTodoSortDirection != TodoDirectionUnset) {
1584             // do not insert if already done so.
1585             cfg->mSortDirection->addItem(i18nc("@option sort in increasing order", "Ascending"));
1586             cfg->mSortDirection->addItem(i18nc("@option sort in descreasing order", "Descending"));
1587             cfg->mSortDirection->setCurrentIndex(mTodoSortDirection);
1588         }
1589 
1590         cfg->mPrintFooter->setChecked(mPrintFooter);
1591     }
1592 }
1593 
doLoadConfig()1594 void CalPrintTodos::doLoadConfig()
1595 {
1596     CalPrintPluginBase::doLoadConfig();
1597     if (mConfig) {
1598         KConfigGroup grp(mConfig, groupName());
1599         mPageTitle = grp.readEntry("Page title", i18n("To-do list"));
1600         mTodoPrintType = (eTodoPrintType)grp.readEntry("Print type", static_cast<int>(TodosAll));
1601         mIncludeDescription = grp.readEntry("Include description", true);
1602         mIncludePriority = grp.readEntry("Include priority", true);
1603         mIncludeCategories = grp.readEntry("Include categories", true);
1604         mIncludeStartDate = grp.readEntry("Include start date", true);
1605         mIncludeDueDate = grp.readEntry("Include due date", true);
1606         mIncludePercentComplete = grp.readEntry("Include percentage completed", true);
1607         mConnectSubTodos = grp.readEntry("Connect subtodos", true);
1608         mStrikeOutCompleted = grp.readEntry("Strike out completed summaries", true);
1609         mTodoSortField = (eTodoSortField)grp.readEntry("Sort field", static_cast<int>(TodoFieldSummary));
1610         mTodoSortDirection = (eTodoSortDirection)grp.readEntry("Sort direction", static_cast<int>(TodoDirectionAscending));
1611     }
1612     setSettingsWidget();
1613 }
1614 
doSaveConfig()1615 void CalPrintTodos::doSaveConfig()
1616 {
1617     readSettingsWidget();
1618     if (mConfig) {
1619         KConfigGroup grp(mConfig, groupName());
1620         grp.writeEntry("Page title", mPageTitle);
1621         grp.writeEntry("Print type", int(mTodoPrintType));
1622         grp.writeEntry("Include description", mIncludeDescription);
1623         grp.writeEntry("Include priority", mIncludePriority);
1624         grp.writeEntry("Include categories", mIncludeCategories);
1625         grp.writeEntry("Include start date", mIncludeStartDate);
1626         grp.writeEntry("Include due date", mIncludeDueDate);
1627         grp.writeEntry("Include percentage completed", mIncludePercentComplete);
1628         grp.writeEntry("Connect subtodos", mConnectSubTodos);
1629         grp.writeEntry("Strike out completed summaries", mStrikeOutCompleted);
1630         grp.writeEntry("Sort field", static_cast<int>(mTodoSortField));
1631         grp.writeEntry("Sort direction", static_cast<int>(mTodoSortDirection));
1632     }
1633     CalPrintPluginBase::doSaveConfig();
1634 }
1635 
print(QPainter & p,int width,int height)1636 void CalPrintTodos::print(QPainter &p, int width, int height)
1637 {
1638     int possummary = 100;
1639 
1640     QRect headerBox(0, 0, width, headerHeight());
1641     QRect footerBox(0, height - footerHeight(), width, footerHeight());
1642     height -= footerHeight();
1643 
1644     // Draw the First Page Header
1645     drawHeader(p, mPageTitle, mFromDate, QDate(), headerBox);
1646 
1647     // Estimate widths of some data columns.
1648     QFont oldFont(p.font());
1649     p.setFont(QFont(QStringLiteral("sans-serif"), 10));
1650     const int widDate = p.fontMetrics().boundingRect(QLocale::system().toString(QDate(2222, 12, 22), QLocale::ShortFormat)).width();
1651     const int widPct = p.fontMetrics().boundingRect(i18n("%1%", 100)).width() + 27;
1652 
1653     // Draw the Column Headers
1654     int mCurrentLinePos = headerHeight() + 5;
1655     QString outStr;
1656 
1657     p.setFont(QFont(QStringLiteral("sans-serif"), 9, QFont::Bold));
1658     int lineSpacing = p.fontMetrics().lineSpacing();
1659     mCurrentLinePos += lineSpacing;
1660     int pospriority = -1;
1661     if (mIncludePriority) {
1662         outStr += i18n("Priority");
1663         pospriority = 0;
1664         p.drawText(pospriority, mCurrentLinePos - 2, outStr);
1665     }
1666 
1667     int posSoFar = width;  // Position of leftmost optional header.
1668 
1669     int posdue = -1;
1670     if (mIncludeDueDate) {
1671         outStr.truncate(0);
1672         outStr += i18nc("@label to-do due date", "Due");
1673         const int widDue = std::max(p.fontMetrics().boundingRect(outStr).width(), widDate);
1674         posdue = posSoFar - widDue;
1675         p.drawText(posdue, mCurrentLinePos - 2, outStr);
1676         posSoFar = posdue;
1677     }
1678 
1679     int posStart = -1;
1680     if (mIncludeStartDate) {
1681         outStr.truncate(0);
1682         outStr += i18nc("@label to-do start date", "Start");
1683         const int widStart = std::max(p.fontMetrics().boundingRect(outStr).width(), widDate);
1684         posStart = posSoFar - widStart - 5;
1685         p.drawText(posStart, mCurrentLinePos - 2, outStr);
1686         posSoFar = posStart;
1687     }
1688 
1689     int poscomplete = -1;
1690     if (mIncludePercentComplete) {
1691         outStr.truncate(0);
1692         outStr += i18nc("@label to-do percentage complete", "Complete");
1693         const int widComplete = std::max(p.fontMetrics().boundingRect(outStr).width(), widPct);
1694         poscomplete = posSoFar - widComplete - 5;
1695         p.drawText(poscomplete, mCurrentLinePos - 2, outStr);
1696         posSoFar = poscomplete;
1697     }
1698 
1699     int posCategories = -1;
1700     if (mIncludeCategories) {
1701         outStr.truncate(0);
1702         outStr += i18nc("@label to-do categories", "Tags");
1703         const int widCats = std::max(p.fontMetrics().boundingRect(outStr).width(), 100); // Arbitrary!
1704         posCategories = posSoFar - widCats - 5;
1705         p.drawText(posCategories, mCurrentLinePos - 2, outStr);
1706     }
1707 
1708     p.setFont(QFont(QStringLiteral("sans-serif"), 10));
1709 
1710     KCalendarCore::Todo::List todoList;
1711     KCalendarCore::Todo::List tempList;
1712 
1713     KCalendarCore::SortDirection sortDirection = KCalendarCore::SortDirectionAscending;
1714     switch (mTodoSortDirection) {
1715     case TodoDirectionAscending:
1716         sortDirection = KCalendarCore::SortDirectionAscending;
1717         break;
1718     case TodoDirectionDescending:
1719         sortDirection = KCalendarCore::SortDirectionDescending;
1720         break;
1721     case TodoDirectionUnset:
1722         break;
1723     }
1724 
1725     KCalendarCore::TodoSortField sortField = KCalendarCore::TodoSortSummary;
1726     switch (mTodoSortField) {
1727     case TodoFieldSummary:
1728         sortField = KCalendarCore::TodoSortSummary;
1729         break;
1730     case TodoFieldStartDate:
1731         sortField = KCalendarCore::TodoSortStartDate;
1732         break;
1733     case TodoFieldDueDate:
1734         sortField = KCalendarCore::TodoSortDueDate;
1735         break;
1736     case TodoFieldPriority:
1737         sortField = KCalendarCore::TodoSortPriority;
1738         break;
1739     case TodoFieldPercentComplete:
1740         sortField = KCalendarCore::TodoSortPercentComplete;
1741         break;
1742     case TodoFieldCategories:
1743         sortField = KCalendarCore::TodoSortCategories;
1744         break;
1745     case TodoFieldUnset:
1746         break;
1747     }
1748 
1749     // Create list of to-dos which will be printed
1750     todoList = mCalendar->todos(sortField, sortDirection);
1751     switch (mTodoPrintType) {
1752     case TodosAll:
1753         break;
1754     case TodosUnfinished:
1755         for (const KCalendarCore::Todo::Ptr &todo : std::as_const(todoList)) {
1756             Q_ASSERT(todo);
1757             if (!todo->isCompleted()) {
1758                 tempList.append(todo);
1759             }
1760         }
1761         todoList = tempList;
1762         break;
1763     case TodosDueRange:
1764         for (const KCalendarCore::Todo::Ptr &todo : std::as_const(todoList)) {
1765             Q_ASSERT(todo);
1766             if (todo->hasDueDate()) {
1767                 if (todo->dtDue().date() >= mFromDate && todo->dtDue().date() <= mToDate) {
1768                     tempList.append(todo);
1769                 }
1770             } else {
1771                 tempList.append(todo);
1772             }
1773         }
1774         todoList = tempList;
1775         break;
1776     }
1777 
1778     // Print to-dos
1779     int count = 0;
1780     for (const KCalendarCore::Todo::Ptr &todo : std::as_const(todoList)) {
1781         if ((mExcludeConfidential && todo->secrecy() == KCalendarCore::Incidence::SecrecyConfidential)
1782             || (mExcludePrivate && todo->secrecy() == KCalendarCore::Incidence::SecrecyPrivate)) {
1783             continue;
1784         }
1785         // Skip sub-to-dos. They will be printed recursively in drawTodo()
1786         if (todo->relatedTo().isEmpty()) { // review(AKONADI_PORT)
1787             count++;
1788             drawTodo(count,
1789                      todo,
1790                      p,
1791                      sortField,
1792                      sortDirection,
1793                      mConnectSubTodos,
1794                      mStrikeOutCompleted,
1795                      mIncludeDescription,
1796                      pospriority,
1797                      possummary,
1798                      posCategories,
1799                      posStart,
1800                      posdue,
1801                      poscomplete,
1802                      0,
1803                      0,
1804                      mCurrentLinePos,
1805                      width,
1806                      height,
1807                      todoList,
1808                      nullptr);
1809         }
1810     }
1811 
1812     if (mPrintFooter) {
1813         drawFooter(p, footerBox);
1814     }
1815     p.setFont(oldFont);
1816 }
1817