File indexing completed on 2025-01-05 04:47:41

0001 /*
0002   SPDX-FileCopyrightText: 1998 Preston Brown <pbrown@kde.org>
0003   SPDX-FileCopyrightText: 2003 Reinhold Kainhofer <reinhold@kainhofer.com>
0004   SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org>
0005   SPDX-FileCopyrightText: 2008 Ron Goodheart <rong.dev@gmail.com>
0006   SPDX-FileCopyrightText: 2010-2024 Laurent Montel <montel@kde.org>
0007   SPDX-FileCopyrightText: 2012-2013 Allen Winter <winter@kde.org>
0008 
0009   SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
0010 */
0011 
0012 #include "calprintdefaultplugins.h"
0013 #include "kcalprefs.h"
0014 #include "utils.h"
0015 
0016 #include <cmath>
0017 
0018 #include <Akonadi/CalendarUtils>
0019 #include <Akonadi/Item>
0020 
0021 #include <KCalendarCore/Visitor>
0022 
0023 #include <KCalUtils/IncidenceFormatter>
0024 #include <KCalUtils/Stringify>
0025 
0026 #include <KConfigGroup>
0027 
0028 #include <QPainter>
0029 
0030 using namespace CalendarSupport;
0031 
0032 /**************************************************************
0033  *           Print Incidence
0034  **************************************************************/
0035 
0036 CalPrintIncidence::CalPrintIncidence()
0037     : CalPrintPluginBase()
0038 {
0039 }
0040 
0041 CalPrintIncidence::~CalPrintIncidence() = default;
0042 
0043 QWidget *CalPrintIncidence::createConfigWidget(QWidget *w)
0044 {
0045     return new CalPrintIncidenceConfig(w);
0046 }
0047 
0048 void CalPrintIncidence::readSettingsWidget()
0049 {
0050     auto cfg = dynamic_cast<CalPrintIncidenceConfig *>((QWidget *)mConfigWidget);
0051     if (cfg) {
0052         mUseColors = cfg->mColors->isChecked();
0053         mPrintFooter = cfg->mPrintFooter->isChecked();
0054         mShowOptions = cfg->mShowDetails->isChecked();
0055         mShowSubitemsNotes = cfg->mShowSubitemsNotes->isChecked();
0056         mShowAttendees = cfg->mShowAttendees->isChecked();
0057         mShowAttachments = cfg->mShowAttachments->isChecked();
0058         mShowNoteLines = cfg->mShowNoteLines->isChecked();
0059     }
0060 }
0061 
0062 void CalPrintIncidence::setSettingsWidget()
0063 {
0064     auto cfg = dynamic_cast<CalPrintIncidenceConfig *>((QWidget *)mConfigWidget);
0065     if (cfg) {
0066         cfg->mColors->setChecked(mUseColors);
0067         cfg->mPrintFooter->setChecked(mPrintFooter);
0068         cfg->mShowDetails->setChecked(mShowOptions);
0069         cfg->mShowSubitemsNotes->setChecked(mShowSubitemsNotes);
0070         cfg->mShowAttendees->setChecked(mShowAttendees);
0071         cfg->mShowAttachments->setChecked(mShowAttachments);
0072         cfg->mShowNoteLines->setChecked(mShowNoteLines);
0073     }
0074 }
0075 
0076 void CalPrintIncidence::doLoadConfig()
0077 {
0078     CalPrintPluginBase::doLoadConfig();
0079     if (mConfig) {
0080         KConfigGroup grp(mConfig, groupName());
0081         mShowOptions = grp.readEntry("Show Options", false);
0082         mShowSubitemsNotes = grp.readEntry("Show Subitems and Notes", false);
0083         mShowAttendees = grp.readEntry("Use Attendees", false);
0084         mShowAttachments = grp.readEntry("Use Attachments", false);
0085     }
0086     setSettingsWidget();
0087 }
0088 
0089 void CalPrintIncidence::doSaveConfig()
0090 {
0091     readSettingsWidget();
0092     if (mConfig) {
0093         KConfigGroup grp(mConfig, groupName());
0094         grp.writeEntry("Show Options", mShowOptions);
0095         grp.writeEntry("Show Subitems and Notes", mShowSubitemsNotes);
0096         grp.writeEntry("Use Attendees", mShowAttendees);
0097         grp.writeEntry("Use Attachments", mShowAttachments);
0098     }
0099     CalPrintPluginBase::doSaveConfig();
0100 }
0101 
0102 class TimePrintStringsVisitor : public KCalendarCore::Visitor
0103 {
0104 public:
0105     TimePrintStringsVisitor() = default;
0106 
0107     bool act(KCalendarCore::IncidenceBase::Ptr incidence)
0108     {
0109         return incidence->accept(*this, incidence);
0110     }
0111 
0112     QString mStartCaption, mStartString;
0113     QString mEndCaption, mEndString;
0114     QString mDurationCaption, mDurationString;
0115 
0116 protected:
0117     bool visit(const KCalendarCore::Event::Ptr &event) override
0118     {
0119         if (event->dtStart().isValid()) {
0120             mStartCaption = i18n("Start date: ");
0121             mStartString = KCalUtils::IncidenceFormatter::dateTimeToString(event->dtStart(), event->allDay(), false);
0122         } else {
0123             mStartCaption = i18n("No start date");
0124             mStartString.clear();
0125         }
0126 
0127         if (event->hasEndDate()) {
0128             mEndCaption = i18n("End date: ");
0129             mEndString = KCalUtils::IncidenceFormatter::dateTimeToString(event->dtEnd(), event->allDay(), false);
0130         } else if (event->hasDuration()) {
0131             mEndCaption = i18n("Duration: ");
0132             int mins = event->duration().asSeconds() / 60;
0133             if (mins >= 60) {
0134                 mEndString += i18np("1 hour ", "%1 hours ", mins / 60);
0135             }
0136             if (mins % 60 > 0) {
0137                 mEndString += i18np("1 minute ", "%1 minutes ", mins % 60);
0138             }
0139         } else {
0140             mEndCaption = i18n("No end date");
0141             mEndString.clear();
0142         }
0143         return true;
0144     }
0145 
0146     bool visit(const KCalendarCore::Todo::Ptr &todo) override
0147     {
0148         if (todo->hasStartDate()) {
0149             mStartCaption = i18n("Start date: ");
0150             mStartString = KCalUtils::IncidenceFormatter::dateTimeToString(todo->dtStart(), todo->allDay(), false);
0151         } else {
0152             mStartCaption = i18n("No start date");
0153             mStartString.clear();
0154         }
0155 
0156         if (todo->hasDueDate()) {
0157             mEndCaption = i18n("Due date: ");
0158             mEndString = KCalUtils::IncidenceFormatter::dateTimeToString(todo->dtDue(), todo->allDay(), false);
0159         } else {
0160             mEndCaption = i18n("No due date");
0161             mEndString.clear();
0162         }
0163         return true;
0164     }
0165 
0166     bool visit(const KCalendarCore::Journal::Ptr &journal) override
0167     {
0168         mStartCaption = i18n("Start date: ");
0169         mStartString = KCalUtils::IncidenceFormatter::dateTimeToString(journal->dtStart(), journal->allDay(), false);
0170         mEndCaption.clear();
0171         mEndString.clear();
0172         return true;
0173     }
0174 
0175     bool visit(const KCalendarCore::FreeBusy::Ptr &fb) override
0176     {
0177         Q_UNUSED(fb)
0178         return true;
0179     }
0180 };
0181 
0182 int CalPrintIncidence::printCaptionAndText(QPainter &p, QRect box, const QString &caption, const QString &text, const QFont &captionFont, const QFont &textFont)
0183 {
0184     QFontMetrics captionFM(captionFont);
0185     int textWd = captionFM.horizontalAdvance(caption);
0186     QRect textRect(box);
0187 
0188     QFont oldFont(p.font());
0189     p.setFont(captionFont);
0190     p.drawText(box, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine, caption);
0191 
0192     if (!text.isEmpty()) {
0193         textRect.setLeft(textRect.left() + textWd);
0194         p.setFont(textFont);
0195         p.drawText(textRect, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine, text);
0196     }
0197     p.setFont(oldFont);
0198     return textRect.bottom();
0199 }
0200 
0201 void CalPrintIncidence::print(QPainter &p, int width, int height)
0202 {
0203     QFont oldFont(p.font());
0204     QFont textFont(QStringLiteral("sans-serif"), 11, QFont::Normal);
0205     QFont captionFont(QStringLiteral("sans-serif"), 11, QFont::Bold);
0206     p.setFont(textFont);
0207     int lineHeight = p.fontMetrics().lineSpacing();
0208     QString cap;
0209     QString txt;
0210 
0211     KCalendarCore::Incidence::List::ConstIterator it;
0212     for (it = mSelectedIncidences.constBegin(); it != mSelectedIncidences.constEnd(); ++it) {
0213         // don't do anything on a 0-pointer!
0214         if (!(*it)) {
0215             continue;
0216         }
0217         if (it != mSelectedIncidences.constBegin()) {
0218             mPrinter->newPage();
0219         }
0220 
0221         const bool isJournal = ((*it)->type() == KCalendarCore::Incidence::TypeJournal);
0222 
0223         //  PAGE Layout (same for landscape and portrait! astonishingly, it looks good with both!):
0224         //  +-----------------------------------+
0225         //  | Header:  Summary                  |
0226         //  +===================================+
0227         //  | start: ______   end: _________    |
0228         //  | repeats: ___________________      |
0229         //  | reminder: __________________      |
0230         //  +-----------------------------------+
0231         //  | Location: ______________________  |
0232         //  +------------------------+----------+
0233         //  | Description:           | Notes or |
0234         //  |                        | Subitems |
0235         //  |                        |          |
0236         //  |                        |          |
0237         //  |                        |          |
0238         //  |                        |          |
0239         //  |                        |          |
0240         //  |                        |          |
0241         //  |                        |          |
0242         //  |                        |          |
0243         //  +------------------------+----------+
0244         //  | Attachments:           | Settings |
0245         //  |                        |          |
0246         //  +------------------------+----------+
0247         //  | Attendees:                        |
0248         //  |                                   |
0249         //  +-----------------------------------+
0250         //  | Categories: _____________________ |
0251         //  +-----------------------------------+
0252 
0253         QRect box(0, 0, width, height);
0254         QRect titleBox(box);
0255         titleBox.setHeight(headerHeight());
0256         QColor headerColor = mUseColors ? categoryBgColor(*it) : QColor();
0257         // Draw summary as header, no small calendars in title bar, expand height if needed
0258         int titleBottom = drawHeader(p, (*it)->summary(), QDate(), QDate(), titleBox, true, headerColor);
0259         titleBox.setBottom(titleBottom);
0260 
0261         QRect timesBox(titleBox);
0262         timesBox.setTop(titleBox.bottom() + padding());
0263         timesBox.setHeight(height / 8);
0264 
0265         TimePrintStringsVisitor stringVis;
0266         int h = timesBox.top();
0267         if (stringVis.act(*it)) {
0268             QRect textRect(timesBox.left() + padding(), timesBox.top() + padding(), 0, lineHeight);
0269             textRect.setRight(timesBox.center().x());
0270             h = printCaptionAndText(p, textRect, stringVis.mStartCaption, stringVis.mStartString, captionFont, textFont);
0271 
0272             textRect.setLeft(textRect.right());
0273             textRect.setRight(timesBox.right() - padding());
0274             h = qMax(printCaptionAndText(p, textRect, stringVis.mEndCaption, stringVis.mEndString, captionFont, textFont), h);
0275         }
0276 
0277         // Recurrence Printing
0278         if ((*it)->recurs()) {
0279             QRect recurBox(timesBox.left() + padding(), h + padding(), timesBox.right() - padding(), lineHeight);
0280             KCalendarCore::Recurrence *recurs = (*it)->recurrence();
0281             QString displayString = KCalUtils::IncidenceFormatter::recurrenceString((*it));
0282             // exception dates
0283             QString exceptString;
0284             if (!recurs->exDates().isEmpty()) {
0285                 exceptString = i18nc("except for listed dates", " except");
0286                 for (int i = 0; i < recurs->exDates().size(); ++i) {
0287                     exceptString.append(QLatin1Char(' '));
0288                     exceptString.append(QLocale::system().toString(recurs->exDates().at(i), QLocale::ShortFormat));
0289                 }
0290             }
0291             displayString.append(exceptString);
0292             h = qMax(printCaptionAndText(p, recurBox, i18n("Repeats: "), displayString, captionFont, textFont), h);
0293         }
0294 
0295         if (!isJournal) {
0296             // Alarms Printing
0297             QRect alarmBox(timesBox.left() + padding(), h + padding(), timesBox.right() - padding(), lineHeight);
0298             KCalendarCore::Alarm::List alarms = (*it)->alarms();
0299             if (alarms.isEmpty()) {
0300                 cap = i18n("No reminders");
0301                 txt.clear();
0302             } else {
0303                 cap = i18np("Reminder: ", "%1 reminders: ", alarms.count());
0304 
0305                 QStringList alarmStrings;
0306                 KCalendarCore::Alarm::List::ConstIterator it;
0307                 alarmStrings.reserve(alarms.count());
0308                 for (it = alarms.constBegin(); it != alarms.constEnd(); ++it) {
0309                     KCalendarCore::Alarm::Ptr alarm = *it;
0310 
0311                     // Alarm offset, copied from koeditoralarms.cpp:
0312                     KLocalizedString offsetstr;
0313                     int offset = 0;
0314                     if (alarm->hasStartOffset()) {
0315                         offset = alarm->startOffset().asSeconds();
0316                         if (offset < 0) {
0317                             offsetstr = ki18nc("N days/hours/minutes before/after the start/end", "%1 before the start");
0318                             offset = -offset;
0319                         } else {
0320                             offsetstr = ki18nc("N days/hours/minutes before/after the start/end", "%1 after the start");
0321                         }
0322                     } else if (alarm->hasEndOffset()) {
0323                         offset = alarm->endOffset().asSeconds();
0324                         if (offset < 0) {
0325                             offsetstr = ki18nc("N days/hours/minutes before/after the start/end", "%1 before the end");
0326                             offset = -offset;
0327                         } else {
0328                             offsetstr = ki18nc("N days/hours/minutes before/after the start/end", "%1 after the end");
0329                         }
0330                     }
0331 
0332                     offset = offset / 60; // make minutes
0333                     int useoffset = 0;
0334 
0335                     if (offset % (24 * 60) == 0 && offset > 0) { // divides evenly into days?
0336                         useoffset = offset / (24 * 60);
0337                         offsetstr = offsetstr.subs(i18np("1 day", "%1 days", useoffset));
0338                     } else if (offset % 60 == 0 && offset > 0) { // divides evenly into hours?
0339                         useoffset = offset / 60;
0340                         offsetstr = offsetstr.subs(i18np("1 hour", "%1 hours", useoffset));
0341                     } else {
0342                         useoffset = offset;
0343                         offsetstr = offsetstr.subs(i18np("1 minute", "%1 minutes", useoffset));
0344                     }
0345                     alarmStrings << offsetstr.toString();
0346                 }
0347                 txt = alarmStrings.join(i18nc("Spacer for the joined list of categories/tags", ", "));
0348             }
0349             h = qMax(printCaptionAndText(p, alarmBox, cap, txt, captionFont, textFont), h);
0350         }
0351         QRect organizerBox(timesBox.left() + padding(), h + padding(), timesBox.right() - padding(), lineHeight);
0352         h = qMax(printCaptionAndText(p, organizerBox, i18n("Organizer: "), (*it)->organizer().fullName(), captionFont, textFont), h);
0353 
0354         // Finally, draw the frame around the time information...
0355         timesBox.setBottom(qMax(timesBox.bottom(), h + padding()));
0356         drawBox(p, BOX_BORDER_WIDTH, timesBox);
0357 
0358         QRect locationBox(timesBox);
0359         locationBox.setTop(timesBox.bottom() + padding());
0360         locationBox.setHeight(0);
0361         int locationBottom = 0;
0362         if (!isJournal) {
0363             locationBottom = drawBoxWithCaption(p,
0364                                                 locationBox,
0365                                                 i18n("Location: "),
0366                                                 (*it)->location(),
0367                                                 /*sameLine=*/true,
0368                                                 /*expand=*/true,
0369                                                 captionFont,
0370                                                 textFont);
0371         }
0372         locationBox.setBottom(locationBottom);
0373 
0374         // Now start constructing the boxes from the bottom:
0375         QRect footerBox(locationBox);
0376         footerBox.setBottom(box.bottom());
0377         footerBox.setTop(footerBox.bottom() - lineHeight - 2 * padding());
0378 
0379         QRect categoriesBox(footerBox);
0380         categoriesBox.setBottom(footerBox.top());
0381         categoriesBox.setTop(categoriesBox.bottom() - lineHeight - 2 * padding());
0382         QRect attendeesBox(box.left(), categoriesBox.top() - padding() - box.height() / 9, box.width(), box.height() / 9);
0383         QRect attachmentsBox(box.left(), attendeesBox.top() - padding() - box.height() / 9, box.width() * 3 / 4 - padding(), box.height() / 9);
0384         QRect optionsBox(isJournal ? box.left() : attachmentsBox.right() + padding(), attachmentsBox.top(), 0, 0);
0385         optionsBox.setRight(box.right());
0386         optionsBox.setBottom(attachmentsBox.bottom());
0387         QRect notesBox(optionsBox.left(), isJournal ? (timesBox.bottom() + padding()) : (locationBox.bottom() + padding()), optionsBox.width(), 0);
0388         notesBox.setBottom(optionsBox.top() - padding());
0389         QRect descriptionBox(notesBox);
0390         descriptionBox.setLeft(box.left());
0391         descriptionBox.setRight(attachmentsBox.right());
0392 
0393         // Adjust boxes depending on the show options...
0394         if (!mShowSubitemsNotes || isJournal) {
0395             descriptionBox.setRight(box.right());
0396         }
0397         if (!mShowAttachments || !mShowAttendees) {
0398             descriptionBox.setBottom(attachmentsBox.bottom());
0399             optionsBox.setTop(attendeesBox.top());
0400             optionsBox.setBottom(attendeesBox.bottom());
0401             notesBox.setBottom(attachmentsBox.bottom());
0402             if (mShowOptions) {
0403                 attendeesBox.setRight(attachmentsBox.right());
0404             }
0405             if (!mShowAttachments && !mShowAttendees) {
0406                 if (mShowSubitemsNotes) {
0407                     descriptionBox.setBottom(attendeesBox.bottom());
0408                 }
0409                 if (!mShowOptions) {
0410                     descriptionBox.setBottom(attendeesBox.bottom());
0411                     notesBox.setBottom(attendeesBox.bottom());
0412                 }
0413             }
0414         }
0415         if (mShowAttachments && !isJournal) {
0416             if (!mShowOptions) {
0417                 attachmentsBox.setRight(box.right());
0418                 attachmentsBox.setRight(box.right());
0419             }
0420             if (!mShowAttendees) {
0421                 attachmentsBox.setTop(attendeesBox.top());
0422                 attachmentsBox.setBottom(attendeesBox.bottom());
0423             }
0424         }
0425         int newBottom = drawBoxWithCaption(p,
0426                                            descriptionBox,
0427                                            i18n("Description:"),
0428                                            (*it)->description(),
0429                                            /*sameLine=*/false,
0430                                            /*expand=*/false,
0431                                            captionFont,
0432                                            textFont,
0433                                            (*it)->descriptionIsRich());
0434         if (mShowNoteLines) {
0435             drawNoteLines(p, descriptionBox, newBottom);
0436         }
0437 
0438         if (mShowSubitemsNotes && !isJournal) {
0439             KCalendarCore::Todo::List relations;
0440             for (const auto &incidence : mCalendar->incidences()) {
0441                 auto todo = incidence.dynamicCast<KCalendarCore::Todo>();
0442                 if (!todo) {
0443                     continue;
0444                 }
0445                 if (todo->relatedTo() != (*it)->uid()) {
0446                     continue;
0447                 }
0448                 relations.push_back(todo);
0449             }
0450 
0451             if (relations.isEmpty() || (*it)->type() != KCalendarCore::Incidence::TypeTodo) {
0452                 int notesPosition = drawBoxWithCaption(p,
0453                                                        notesBox,
0454                                                        i18n("Notes:"),
0455                                                        QString(),
0456                                                        /*sameLine=*/false,
0457                                                        /*expand=*/false,
0458                                                        captionFont,
0459                                                        textFont);
0460                 if (mShowNoteLines) {
0461                     drawNoteLines(p, notesBox, notesPosition);
0462                 }
0463             } else {
0464                 QString subitemCaption;
0465                 if (relations.isEmpty()) {
0466                     subitemCaption = i18n("No Subitems");
0467                     txt.clear();
0468                 } else {
0469                     subitemCaption = i18np("1 Subitem:", "%1 Subitems:", relations.count());
0470                 }
0471 
0472                 QString subitemString;
0473                 QString statusString;
0474                 QString datesString;
0475                 int count = 0;
0476                 for (const auto &todo : std::as_const(relations)) {
0477                     ++count;
0478                     if (!todo) { // defensive, skip any zero pointers
0479                         continue;
0480                     }
0481                     // format the status
0482                     statusString = KCalUtils::Stringify::incidenceStatus(todo->status());
0483                     if (statusString.isEmpty()) {
0484                         if (todo->status() == KCalendarCore::Incidence::StatusNone) {
0485                             statusString = i18nc("no status", "none");
0486                         } else {
0487                             statusString = i18nc("unknown status", "unknown");
0488                         }
0489                     }
0490                     // format the dates if provided
0491                     datesString.clear();
0492                     if (todo->dtStart().isValid()) {
0493                         datesString +=
0494                             i18nc("subitem start date", "Start Date: %1\n", QLocale().toString(todo->dtStart().toLocalTime().date(), QLocale::ShortFormat));
0495                         if (!todo->allDay()) {
0496                             datesString +=
0497                                 i18nc("subitem start time", "Start Time: %1\n", QLocale().toString(todo->dtStart().toLocalTime().time(), QLocale::ShortFormat));
0498                         }
0499                     }
0500                     if (todo->dateTime(KCalendarCore::Incidence::RoleEnd).isValid()) {
0501                         subitemString +=
0502                             i18nc("subitem due date",
0503                                   "Due Date: %1\n",
0504                                   QLocale().toString(todo->dateTime(KCalendarCore::Incidence ::RoleEnd).toLocalTime().date(), QLocale::ShortFormat));
0505 
0506                         if (!todo->allDay()) {
0507                             subitemString +=
0508                                 i18nc("subitem due time",
0509                                       "Due Time: %1\n",
0510                                       QLocale().toString(todo->dateTime(KCalendarCore::Incidence::RoleEnd).toLocalTime().time(), QLocale::ShortFormat));
0511                         }
0512                     }
0513                     subitemString += i18nc("subitem counter", "%1: ", count);
0514                     subitemString += todo->summary();
0515                     subitemString += QLatin1Char('\n');
0516                     if (!datesString.isEmpty()) {
0517                         subitemString += datesString;
0518                         subitemString += QLatin1Char('\n');
0519                     }
0520                     subitemString += i18nc("subitem Status: statusString", "Status: %1\n", statusString);
0521                     subitemString += KCalUtils::IncidenceFormatter::recurrenceString(todo) + QLatin1Char('\n');
0522                     subitemString += i18nc("subitem Priority: N", "Priority: %1\n", QString::number(todo->priority()));
0523                     subitemString += i18nc("subitem Secrecy: secrecyString", "Secrecy: %1\n", KCalUtils::Stringify::incidenceSecrecy(todo->secrecy()));
0524                     subitemString += QLatin1Char('\n');
0525                 }
0526                 drawBoxWithCaption(p,
0527                                    notesBox,
0528                                    subitemCaption,
0529                                    subitemString,
0530                                    /*sameLine=*/false,
0531                                    /*expand=*/false,
0532                                    captionFont,
0533                                    textFont);
0534             }
0535         }
0536 
0537         if (mShowAttachments && !isJournal) {
0538             const KCalendarCore::Attachment::List attachments = (*it)->attachments();
0539             QString attachmentCaption;
0540             if (attachments.isEmpty()) {
0541                 attachmentCaption = i18n("No Attachments");
0542                 txt.clear();
0543             } else {
0544                 attachmentCaption = i18np("1 Attachment:", "%1 Attachments:", attachments.count());
0545             }
0546             QString attachmentString;
0547             KCalendarCore::Attachment::List::ConstIterator ait = attachments.constBegin();
0548             for (; ait != attachments.constEnd(); ++ait) {
0549                 if (!attachmentString.isEmpty()) {
0550                     attachmentString += i18nc("Spacer for list of attachments", "  ");
0551                 }
0552                 attachmentString.append((*ait).label());
0553             }
0554             drawBoxWithCaption(p,
0555                                attachmentsBox,
0556                                attachmentCaption,
0557                                attachmentString,
0558                                /*sameLine=*/false,
0559                                /*expand=*/false,
0560                                captionFont,
0561                                textFont);
0562         }
0563         if (mShowAttendees) {
0564             const KCalendarCore::Attendee::List attendees = (*it)->attendees();
0565             QString attendeeCaption;
0566             if (attendees.isEmpty()) {
0567                 attendeeCaption = i18n("No Attendees");
0568             } else {
0569                 attendeeCaption = i18np("1 Attendee:", "%1 Attendees:", attendees.count());
0570             }
0571             QString attendeeString;
0572             KCalendarCore::Attendee::List::ConstIterator ait = attendees.constBegin();
0573             for (; ait != attendees.constEnd(); ++ait) {
0574                 if (!attendeeString.isEmpty()) {
0575                     attendeeString += QLatin1Char('\n');
0576                 }
0577                 attendeeString += i18nc(
0578                     "Formatting of an attendee: "
0579                     "'Name (Role): Status', e.g. 'Reinhold Kainhofer "
0580                     "<reinhold@kainhofer.com> (Participant): Awaiting Response'",
0581                     "%1 (%2): %3",
0582                     (*ait).fullName(),
0583                     KCalUtils::Stringify::attendeeRole((*ait).role()),
0584                     KCalUtils::Stringify::attendeeStatus((*ait).status()));
0585             }
0586             drawBoxWithCaption(p,
0587                                attendeesBox,
0588                                attendeeCaption,
0589                                attendeeString,
0590                                /*sameLine=*/false,
0591                                /*expand=*/false,
0592                                captionFont,
0593                                textFont);
0594         }
0595 
0596         if (mShowOptions) {
0597             QString optionsString;
0598             if (!KCalUtils::Stringify::incidenceStatus((*it)->status()).isEmpty()) {
0599                 optionsString += i18n("Status: %1", KCalUtils::Stringify::incidenceStatus((*it)->status()));
0600                 optionsString += QLatin1Char('\n');
0601             }
0602             if (!KCalUtils::Stringify::incidenceSecrecy((*it)->secrecy()).isEmpty()) {
0603                 optionsString += i18n("Secrecy: %1", KCalUtils::Stringify::incidenceSecrecy((*it)->secrecy()));
0604                 optionsString += QLatin1Char('\n');
0605             }
0606             if ((*it)->type() == KCalendarCore::Incidence::TypeEvent) {
0607                 KCalendarCore::Event::Ptr e = (*it).staticCast<KCalendarCore::Event>();
0608                 if (e->transparency() == KCalendarCore::Event::Opaque) {
0609                     optionsString += i18n("Show as: Busy");
0610                 } else {
0611                     optionsString += i18n("Show as: Free");
0612                 }
0613                 optionsString += QLatin1Char('\n');
0614             } else if ((*it)->type() == KCalendarCore::Incidence::TypeTodo) {
0615                 KCalendarCore::Todo::Ptr t = (*it).staticCast<KCalendarCore::Todo>();
0616                 if (t->isOverdue()) {
0617                     optionsString += i18n("This task is overdue!");
0618                     optionsString += QLatin1Char('\n');
0619                 }
0620             } else if ((*it)->type() == KCalendarCore::Incidence::TypeJournal) {
0621                 // TODO: Anything Journal-specific?
0622             }
0623             drawBoxWithCaption(p, optionsBox, i18n("Settings: "), optionsString, /*sameLine=*/false, /*expand=*/false, captionFont, textFont);
0624         }
0625 
0626         drawBoxWithCaption(p,
0627                            categoriesBox,
0628                            i18n("Tags: "),
0629                            (*it)->categories().join(i18nc("Spacer for the joined list of categories/tags", ", ")),
0630                            /*sameLine=*/true,
0631                            /*expand=*/false,
0632                            captionFont,
0633                            textFont);
0634 
0635         if (mPrintFooter) {
0636             drawFooter(p, footerBox);
0637         }
0638     }
0639     p.setFont(oldFont);
0640 }
0641 
0642 /**************************************************************
0643  *           Print Timetables
0644  **************************************************************/
0645 
0646 CalPrintTimetable::CalPrintTimetable()
0647     : CalPrintPluginBase()
0648 {
0649 }
0650 
0651 CalPrintTimetable::~CalPrintTimetable() = default;
0652 
0653 void CalPrintTimetable::doLoadConfig()
0654 {
0655     CalPrintPluginBase::doLoadConfig();
0656     if (mConfig) {
0657         KConfigGroup grp(mConfig, groupName());
0658         QDate dt = QDate::currentDate(); // any valid QDate will do
0659         QTime tm1(dayStart());
0660         QDateTime startTm(dt, tm1);
0661         QDateTime endTm(dt, tm1.addSecs(12 * 60 * 60));
0662         mStartTime = grp.readEntry("Start time", startTm).time();
0663         mEndTime = grp.readEntry("End time", endTm).time();
0664         mIncludeDescription = grp.readEntry("Include description", false);
0665         mIncludeCategories = grp.readEntry("Include categories", false);
0666         mIncludeTodos = grp.readEntry("Include todos", false);
0667         mIncludeAllEvents = grp.readEntry("Include all events", false);
0668         mSingleLineLimit = grp.readEntry("Single line limit", false);
0669         mExcludeTime = grp.readEntry("Exclude time", false);
0670     }
0671 }
0672 
0673 void CalPrintTimetable::doSaveConfig()
0674 {
0675     if (mConfig) {
0676         KConfigGroup grp(mConfig, groupName());
0677         QDateTime dt = QDateTime::currentDateTime(); // any valid QDateTime will do
0678         dt.setTime(mStartTime);
0679         grp.writeEntry("Start time", dt);
0680         dt.setTime(mEndTime);
0681         grp.writeEntry("End time", dt);
0682         grp.writeEntry("Include description", mIncludeDescription);
0683         grp.writeEntry("Include categories", mIncludeCategories);
0684         grp.writeEntry("Include todos", mIncludeTodos);
0685         grp.writeEntry("Include all events", mIncludeAllEvents);
0686         grp.writeEntry("Single line limit", mSingleLineLimit);
0687         grp.writeEntry("Exclude time", mExcludeTime);
0688     }
0689     CalPrintPluginBase::doSaveConfig();
0690 }
0691 
0692 static QString cleanString(const QString &instr)
0693 {
0694     QString ret = instr;
0695     return ret.replace(QLatin1Char('\n'), QLatin1Char(' '));
0696 }
0697 
0698 void CalPrintTimetable::drawAllDayBox(QPainter &p, const KCalendarCore::Event::List &eventList, QDate qd, QRect box, const QList<QDate> &workDays)
0699 {
0700     int lineSpacing = p.fontMetrics().lineSpacing();
0701 
0702     if (!workDays.contains(qd)) {
0703         drawShadedBox(p, BOX_BORDER_WIDTH, sHolidayBackground, box);
0704     } else {
0705         drawBox(p, BOX_BORDER_WIDTH, box);
0706     }
0707 
0708     QRect eventBox(box);
0709     eventBox.setTop(box.top() + padding());
0710     eventBox.setBottom(eventBox.top() + lineSpacing);
0711 
0712     for (const KCalendarCore::Event::Ptr &currEvent : std::as_const(eventList)) {
0713         if (!currEvent || !currEvent->allDay() || (mExcludeConfidential && currEvent->secrecy() == KCalendarCore::Incidence::SecrecyConfidential)
0714             || (mExcludePrivate && currEvent->secrecy() == KCalendarCore::Incidence::SecrecyPrivate)) {
0715             continue;
0716         }
0717         QString str;
0718         if (currEvent->location().isEmpty()) {
0719             str = cleanString(currEvent->summary());
0720         } else {
0721             str = i18nc("summary, location", "%1, %2", cleanString(currEvent->summary()), cleanString(currEvent->location()));
0722         }
0723         if (mIncludeCategories && !currEvent->categoriesStr().isEmpty()) {
0724             str = i18nc("summary, categories", "%1, %2", str, currEvent->categoriesStr());
0725         }
0726         printEventString(p, eventBox, str);
0727         eventBox.setTop(eventBox.bottom());
0728         eventBox.setBottom(eventBox.top() + lineSpacing);
0729     }
0730 }
0731 
0732 void CalPrintTimetable::drawTimeTable(QPainter &p, QDate fromDate, QDate toDate, QRect box)
0733 {
0734     QTime myFromTime = mStartTime;
0735     QTime myToTime = mEndTime;
0736     int maxAllDayEvents = 0;
0737     QDate curDate(fromDate);
0738     while (curDate <= toDate) {
0739         KCalendarCore::Event::List eventList = mCalendar->events(curDate, QTimeZone::systemTimeZone());
0740         int allDayEvents = holiday(curDate).isEmpty() ? 0 : 1;
0741         for (const KCalendarCore::Event::Ptr &event : std::as_const(eventList)) {
0742             Q_ASSERT(event);
0743             if (!event || (mExcludeConfidential && event->secrecy() == KCalendarCore::Incidence::SecrecyConfidential)
0744                 || (mExcludePrivate && event->secrecy() == KCalendarCore::Incidence::SecrecyPrivate)) {
0745                 continue;
0746             }
0747             if (event->allDay()) {
0748                 allDayEvents += 1;
0749             } else if (mIncludeAllEvents) {
0750                 if (event->dtStart().time() < myFromTime) {
0751                     myFromTime = event->dtStart().time();
0752                 }
0753                 if (event->dtEnd().time() > myToTime) {
0754                     myToTime = event->dtEnd().time();
0755                 }
0756             }
0757         }
0758         if (allDayEvents > maxAllDayEvents) {
0759             maxAllDayEvents = allDayEvents;
0760         }
0761         curDate = curDate.addDays(1);
0762     }
0763 
0764     p.setFont(QFont(QStringLiteral("sans-serif"), 11, QFont::Normal));
0765     const int lineSpacing = p.fontMetrics().lineSpacing();
0766 
0767     int timelineWidth = TIMELINE_WIDTH + padding();
0768 
0769     QRect dowBox(box);
0770     dowBox.setLeft(box.left() + timelineWidth);
0771     dowBox.setHeight(mSubHeaderHeight);
0772     drawDaysOfWeek(p, fromDate, toDate, dowBox);
0773 
0774     int tlTop = dowBox.bottom();
0775 
0776     int alldayHeight = 0;
0777     if (maxAllDayEvents > 0) {
0778         // Draw the side bar for all-day events.
0779         const auto alldayLabel = i18nc("label for timetable all-day boxes", "All day");
0780         const QFont oldFont(p.font());
0781         p.setFont(QFont(QStringLiteral("sans-serif"), 9, QFont::Normal));
0782         const auto labelHeight = p.fontMetrics().horizontalAdvance(alldayLabel) + 2 * padding();
0783         alldayHeight = std::max(maxAllDayEvents * lineSpacing + 2 * padding(), labelHeight);
0784         drawVerticalBox(p,
0785                         BOX_BORDER_WIDTH,
0786                         QRect(0, tlTop, TIMELINE_WIDTH, alldayHeight),
0787                         alldayLabel,
0788                         Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextWordWrap);
0789         p.setFont(oldFont);
0790         tlTop += alldayHeight + padding();
0791     }
0792 
0793     QRect tlBox(box);
0794     tlBox.setWidth(TIMELINE_WIDTH);
0795     tlBox.setTop(tlTop);
0796     drawTimeLine(p, myFromTime, myToTime, tlBox);
0797 
0798     // draw each day
0799     curDate = fromDate;
0800     int i = 0;
0801     double cellWidth = double(dowBox.width() - 1) / double(fromDate.daysTo(toDate) + 1);
0802     QRect allDayBox(dowBox.left(), dowBox.bottom(), cellWidth, alldayHeight);
0803     const QList<QDate> workDays = CalendarSupport::workDays(fromDate, toDate);
0804     while (curDate <= toDate) {
0805         KCalendarCore::Event::List eventList =
0806             mCalendar->events(curDate, QTimeZone::systemTimeZone(), KCalendarCore::EventSortStartDate, KCalendarCore::SortDirectionAscending);
0807 
0808         allDayBox.setLeft(dowBox.left() + int(i * cellWidth));
0809         allDayBox.setRight(dowBox.left() + int((i + 1) * cellWidth));
0810         if (maxAllDayEvents > 0) {
0811             if (const auto h = holidayEvent(curDate)) {
0812                 eventList.prepend(h);
0813             }
0814             drawAllDayBox(p, eventList, curDate, allDayBox, workDays);
0815         }
0816 
0817         QRect dayBox(allDayBox);
0818         dayBox.setTop(tlTop);
0819         dayBox.setBottom(box.bottom());
0820         drawAgendaDayBox(p, eventList, curDate, false, myFromTime, myToTime, dayBox, mIncludeDescription, mIncludeCategories, mExcludeTime, workDays);
0821 
0822         ++i;
0823         curDate = curDate.addDays(1);
0824     }
0825 }
0826 
0827 /**************************************************************
0828  *           Print Day
0829  **************************************************************/
0830 
0831 CalPrintDay::CalPrintDay()
0832     : CalPrintTimetable()
0833 {
0834 }
0835 
0836 CalPrintDay::~CalPrintDay() = default;
0837 
0838 QWidget *CalPrintDay::createConfigWidget(QWidget *w)
0839 {
0840     return new CalPrintDayConfig(w);
0841 }
0842 
0843 void CalPrintDay::readSettingsWidget()
0844 {
0845     auto cfg = dynamic_cast<CalPrintDayConfig *>((QWidget *)mConfigWidget);
0846     if (cfg) {
0847         mFromDate = cfg->mFromDate->date();
0848         mToDate = cfg->mToDate->date();
0849 
0850         if (cfg->mPrintTypeFilofax->isChecked()) {
0851             mDayPrintType = Filofax;
0852         } else if (cfg->mPrintTypeTimetable->isChecked()) {
0853             mDayPrintType = Timetable;
0854         } else {
0855             mDayPrintType = SingleTimetable;
0856         }
0857 
0858         mStartTime = cfg->mFromTime->time();
0859         mEndTime = cfg->mToTime->time();
0860         mIncludeAllEvents = cfg->mIncludeAllEvents->isChecked();
0861 
0862         mIncludeDescription = cfg->mIncludeDescription->isChecked();
0863         mIncludeCategories = cfg->mIncludeCategories->isChecked();
0864         mSingleLineLimit = cfg->mSingleLineLimit->isChecked();
0865         mIncludeTodos = cfg->mIncludeTodos->isChecked();
0866         mUseColors = cfg->mColors->isChecked();
0867         mPrintFooter = cfg->mPrintFooter->isChecked();
0868         mShowNoteLines = cfg->mShowNoteLines->isChecked();
0869         mExcludeTime = cfg->mExcludeTime->isChecked();
0870         mExcludeConfidential = cfg->mExcludeConfidential->isChecked();
0871         mExcludePrivate = cfg->mExcludePrivate->isChecked();
0872     }
0873 }
0874 
0875 void CalPrintDay::setSettingsWidget()
0876 {
0877     auto cfg = dynamic_cast<CalPrintDayConfig *>((QWidget *)mConfigWidget);
0878     if (cfg) {
0879         cfg->mFromDate->setDate(mFromDate);
0880         cfg->mToDate->setDate(mToDate);
0881 
0882         cfg->mPrintTypeFilofax->setChecked(mDayPrintType == Filofax);
0883         cfg->mPrintTypeTimetable->setChecked(mDayPrintType == Timetable);
0884         cfg->mPrintTypeSingleTimetable->setChecked(mDayPrintType == SingleTimetable);
0885 
0886         cfg->mFromTime->setTime(mStartTime);
0887         cfg->mToTime->setTime(mEndTime);
0888         cfg->mIncludeAllEvents->setChecked(mIncludeAllEvents);
0889 
0890         cfg->mIncludeDescription->setChecked(mIncludeDescription);
0891         cfg->mIncludeCategories->setChecked(mIncludeCategories);
0892         cfg->mSingleLineLimit->setChecked(mSingleLineLimit);
0893         cfg->mIncludeTodos->setChecked(mIncludeTodos);
0894         cfg->mColors->setChecked(mUseColors);
0895         cfg->mPrintFooter->setChecked(mPrintFooter);
0896         cfg->mShowNoteLines->setChecked(mShowNoteLines);
0897         cfg->mExcludeTime->setChecked(mExcludeTime);
0898         cfg->mExcludeConfidential->setChecked(mExcludeConfidential);
0899         cfg->mExcludePrivate->setChecked(mExcludePrivate);
0900     }
0901 }
0902 
0903 void CalPrintDay::doLoadConfig()
0904 {
0905     CalPrintTimetable::doLoadConfig();
0906     if (mConfig) {
0907         KConfigGroup grp(mConfig, groupName());
0908         mDayPrintType = static_cast<eDayPrintType>(grp.readEntry("Print type", static_cast<int>(Timetable)));
0909     }
0910     setSettingsWidget();
0911 }
0912 
0913 void CalPrintDay::doSaveConfig()
0914 {
0915     readSettingsWidget();
0916     if (mConfig) {
0917         KConfigGroup grp(mConfig, groupName());
0918         grp.writeEntry("Print type", int(mDayPrintType));
0919     }
0920     CalPrintTimetable::doSaveConfig();
0921 }
0922 
0923 void CalPrintDay::setDateRange(const QDate &from, const QDate &to)
0924 {
0925     CalPrintPluginBase::setDateRange(from, to);
0926     auto cfg = dynamic_cast<CalPrintDayConfig *>((QWidget *)mConfigWidget);
0927     if (cfg) {
0928         cfg->mFromDate->setDate(from);
0929         cfg->mToDate->setDate(to);
0930     }
0931 }
0932 
0933 void CalPrintDay::drawDays(QPainter &p, QRect box)
0934 {
0935     const int numberOfDays = mFromDate.daysTo(mToDate) + 1;
0936     int vcells;
0937     const bool portrait = (box.height() > box.width());
0938     int cellWidth;
0939     if (portrait) {
0940         // 2 columns
0941         vcells = std::ceil(static_cast<double>(numberOfDays) / 2.0);
0942         if (numberOfDays > 1) {
0943             cellWidth = box.width() / 2;
0944         } else {
0945             cellWidth = box.width();
0946         }
0947     } else {
0948         // landscape: N columns
0949         vcells = 1;
0950         cellWidth = box.width() / numberOfDays;
0951     }
0952     const int cellHeight = box.height() / vcells;
0953     QDate weekDate = mFromDate;
0954     for (int i = 0; i < numberOfDays; ++i, weekDate = weekDate.addDays(1)) {
0955         const int hpos = i / vcells;
0956         const int vpos = i % vcells;
0957         const QRect dayBox(box.left() + cellWidth * hpos, box.top() + cellHeight * vpos, cellWidth, cellHeight);
0958         drawDayBox(p, weekDate, mStartTime, mEndTime, dayBox, true, true, true, mSingleLineLimit, mIncludeDescription, mIncludeCategories);
0959     } // for i through all selected days
0960 }
0961 
0962 void CalPrintDay::print(QPainter &p, int width, int height)
0963 {
0964     QDate curDay(mFromDate);
0965 
0966     QRect headerBox(0, 0, width, headerHeight());
0967     QRect footerBox(0, height - footerHeight(), width, footerHeight());
0968     height -= footerHeight();
0969     QRect daysBox(headerBox);
0970     daysBox.setTop(headerBox.bottom() + padding());
0971     daysBox.setBottom(height);
0972 
0973     auto local = QLocale::system();
0974 
0975     switch (mDayPrintType) {
0976     case Filofax:
0977     case SingleTimetable: {
0978         QString line1 = local.toString(mFromDate, QLocale::ShortFormat);
0979         QString line2 = local.toString(mToDate, QLocale::ShortFormat);
0980         QString title;
0981         if (mFromDate == mToDate) {
0982             title = line1;
0983         } else {
0984             title = i18nc("date from-to", "%1\u2013%2", line1, line2);
0985         }
0986         drawHeader(p, title, mFromDate, QDate(), headerBox);
0987         if (mDayPrintType == Filofax) {
0988             drawDays(p, daysBox);
0989         } else if (mDayPrintType == SingleTimetable) {
0990             drawTimeTable(p, mFromDate, mToDate, daysBox);
0991         }
0992         if (mPrintFooter) {
0993             drawFooter(p, footerBox);
0994         }
0995         break;
0996     }
0997 
0998     case Timetable:
0999     default:
1000         do {
1001             QTime curStartTime(mStartTime);
1002             QTime curEndTime(mEndTime);
1003 
1004             // For an invalid time range, simply show one hour, starting at the hour
1005             // before the given start time
1006             if (curEndTime <= curStartTime) {
1007                 curStartTime = QTime(curStartTime.hour(), 0, 0);
1008                 curEndTime = curStartTime.addSecs(3600);
1009             }
1010 
1011             drawHeader(p, local.toString(curDay, QLocale::ShortFormat), curDay, QDate(), headerBox);
1012             drawTimeTable(p, curDay, curDay, daysBox);
1013             if (mPrintFooter) {
1014                 drawFooter(p, footerBox);
1015             }
1016 
1017             curDay = curDay.addDays(1);
1018             if (curDay <= mToDate) {
1019                 mPrinter->newPage();
1020             }
1021         } while (curDay <= mToDate);
1022     } // switch
1023 }
1024 
1025 /**************************************************************
1026  *           Print Week
1027  **************************************************************/
1028 
1029 CalPrintWeek::CalPrintWeek()
1030     : CalPrintTimetable()
1031 {
1032 }
1033 
1034 CalPrintWeek::~CalPrintWeek() = default;
1035 
1036 QWidget *CalPrintWeek::createConfigWidget(QWidget *w)
1037 {
1038     return new CalPrintWeekConfig(w);
1039 }
1040 
1041 void CalPrintWeek::readSettingsWidget()
1042 {
1043     auto cfg = dynamic_cast<CalPrintWeekConfig *>((QWidget *)mConfigWidget);
1044     if (cfg) {
1045         mFromDate = cfg->mFromDate->date();
1046         mToDate = cfg->mToDate->date();
1047 
1048         if (cfg->mPrintTypeFilofax->isChecked()) {
1049             mWeekPrintType = Filofax;
1050         } else if (cfg->mPrintTypeTimetable->isChecked()) {
1051             mWeekPrintType = Timetable;
1052         } else if (cfg->mPrintTypeSplitWeek->isChecked()) {
1053             mWeekPrintType = SplitWeek;
1054         } else {
1055             mWeekPrintType = Timetable;
1056         }
1057 
1058         mStartTime = cfg->mFromTime->time();
1059         mEndTime = cfg->mToTime->time();
1060         mIncludeAllEvents = cfg->mIncludeAllEvents->isChecked();
1061 
1062         mShowNoteLines = cfg->mShowNoteLines->isChecked();
1063         mSingleLineLimit = cfg->mSingleLineLimit->isChecked();
1064         mIncludeTodos = cfg->mIncludeTodos->isChecked();
1065         mUseColors = cfg->mColors->isChecked();
1066         mPrintFooter = cfg->mPrintFooter->isChecked();
1067         mIncludeDescription = cfg->mIncludeDescription->isChecked();
1068         mIncludeCategories = cfg->mIncludeCategories->isChecked();
1069         mExcludeTime = cfg->mExcludeTime->isChecked();
1070         mExcludeConfidential = cfg->mExcludeConfidential->isChecked();
1071         mExcludePrivate = cfg->mExcludePrivate->isChecked();
1072     }
1073 }
1074 
1075 void CalPrintWeek::setSettingsWidget()
1076 {
1077     auto cfg = dynamic_cast<CalPrintWeekConfig *>((QWidget *)mConfigWidget);
1078     if (cfg) {
1079         cfg->mFromDate->setDate(mFromDate);
1080         cfg->mToDate->setDate(mToDate);
1081 
1082         cfg->mPrintTypeFilofax->setChecked(mWeekPrintType == Filofax);
1083         cfg->mPrintTypeTimetable->setChecked(mWeekPrintType == Timetable);
1084         cfg->mPrintTypeSplitWeek->setChecked(mWeekPrintType == SplitWeek);
1085 
1086         cfg->mFromTime->setTime(mStartTime);
1087         cfg->mToTime->setTime(mEndTime);
1088         cfg->mIncludeAllEvents->setChecked(mIncludeAllEvents);
1089 
1090         cfg->mShowNoteLines->setChecked(mShowNoteLines);
1091         cfg->mSingleLineLimit->setChecked(mSingleLineLimit);
1092         cfg->mIncludeTodos->setChecked(mIncludeTodos);
1093         cfg->mColors->setChecked(mUseColors);
1094         cfg->mPrintFooter->setChecked(mPrintFooter);
1095         cfg->mIncludeDescription->setChecked(mIncludeDescription);
1096         cfg->mIncludeCategories->setChecked(mIncludeCategories);
1097         cfg->mExcludeTime->setChecked(mExcludeTime);
1098         cfg->mExcludeConfidential->setChecked(mExcludeConfidential);
1099         cfg->mExcludePrivate->setChecked(mExcludePrivate);
1100     }
1101     CalPrintTimetable::setSettingsWidget();
1102 }
1103 
1104 void CalPrintWeek::doLoadConfig()
1105 {
1106     CalPrintTimetable::doLoadConfig();
1107     if (mConfig) {
1108         KConfigGroup grp(mConfig, groupName());
1109         mWeekPrintType = (eWeekPrintType)(grp.readEntry("Print type", (int)Filofax));
1110     }
1111     setSettingsWidget();
1112 }
1113 
1114 void CalPrintWeek::doSaveConfig()
1115 {
1116     readSettingsWidget();
1117     if (mConfig) {
1118         KConfigGroup grp(mConfig, groupName());
1119         grp.writeEntry("Print type", int(mWeekPrintType));
1120     }
1121     CalPrintTimetable::doSaveConfig();
1122 }
1123 
1124 QPageLayout::Orientation CalPrintWeek::defaultOrientation() const
1125 {
1126     if (mWeekPrintType == Filofax) {
1127         return QPageLayout::Portrait;
1128     } else if (mWeekPrintType == SplitWeek) {
1129         return QPageLayout::Portrait;
1130     } else {
1131         return QPageLayout::Landscape;
1132     }
1133 }
1134 
1135 void CalPrintWeek::setDateRange(const QDate &from, const QDate &to)
1136 {
1137     CalPrintPluginBase::setDateRange(from, to);
1138     auto cfg = dynamic_cast<CalPrintWeekConfig *>((QWidget *)mConfigWidget);
1139     if (cfg) {
1140         cfg->mFromDate->setDate(from);
1141         cfg->mToDate->setDate(to);
1142     }
1143 }
1144 
1145 void CalPrintWeek::drawWeek(QPainter &p, QDate qd, QRect box)
1146 {
1147     QDate weekDate = qd;
1148     const bool portrait = (box.height() > box.width());
1149     int cellWidth;
1150     int vcells;
1151     if (portrait) {
1152         cellWidth = box.width() / 2;
1153         vcells = 3;
1154     } else {
1155         cellWidth = box.width() / 6;
1156         vcells = 1;
1157     }
1158     const int cellHeight = box.height() / vcells;
1159 
1160     // correct begin of week
1161     int weekdayCol = weekdayColumn(qd.dayOfWeek());
1162     weekDate = qd.addDays(-weekdayCol);
1163 
1164     for (int i = 0; i < 7; ++i, weekDate = weekDate.addDays(1)) {
1165         // Saturday and sunday share a cell, so we have to special-case sunday
1166         int hpos = ((i < 6) ? i : (i - 1)) / vcells;
1167         int vpos = ((i < 6) ? i : (i - 1)) % vcells;
1168         QRect dayBox(box.left() + cellWidth * hpos,
1169                      box.top() + cellHeight * vpos + ((i == 6) ? (cellHeight / 2) : 0),
1170                      cellWidth,
1171                      (i < 5) ? (cellHeight) : (cellHeight / 2));
1172         drawDayBox(p, weekDate, mStartTime, mEndTime, dayBox, true, true, true, mSingleLineLimit, mIncludeDescription, mIncludeCategories);
1173     } // for i through all weekdays
1174 }
1175 
1176 void CalPrintWeek::print(QPainter &p, int width, int height)
1177 {
1178     QDate curWeek;
1179     QDate fromWeek;
1180     QDate toWeek;
1181 
1182     // correct begin and end to first and last day of week
1183     int weekdayCol = weekdayColumn(mFromDate.dayOfWeek());
1184     fromWeek = mFromDate.addDays(-weekdayCol);
1185     weekdayCol = weekdayColumn(mToDate.dayOfWeek());
1186     toWeek = mToDate.addDays(6 - weekdayCol);
1187 
1188     curWeek = fromWeek.addDays(6);
1189     auto local = QLocale::system();
1190 
1191     QString line1;
1192     QString line2;
1193     QString title;
1194     QRect headerBox(0, 0, width, headerHeight());
1195     QRect footerBox(0, height - footerHeight(), width, footerHeight());
1196     height -= footerHeight();
1197 
1198     QRect weekBox(headerBox);
1199     weekBox.setTop(headerBox.bottom() + padding());
1200     weekBox.setBottom(height);
1201 
1202     switch (mWeekPrintType) {
1203     case Filofax:
1204         do {
1205             line1 = local.toString(curWeek.addDays(-6), QLocale::ShortFormat);
1206             line2 = local.toString(curWeek, QLocale::ShortFormat);
1207             title = i18nc("date from-to", "%1\u2013%2", line1, line2);
1208             drawHeader(p, title, curWeek.addDays(-6), QDate(), headerBox);
1209 
1210             drawWeek(p, curWeek, weekBox);
1211 
1212             if (mPrintFooter) {
1213                 drawFooter(p, footerBox);
1214             }
1215 
1216             curWeek = curWeek.addDays(7);
1217             if (curWeek <= toWeek) {
1218                 mPrinter->newPage();
1219             }
1220         } while (curWeek <= toWeek);
1221         break;
1222 
1223     case Timetable:
1224     default:
1225         do {
1226             line1 = local.toString(curWeek.addDays(-6), QLocale::ShortFormat);
1227             line2 = local.toString(curWeek, QLocale::ShortFormat);
1228             if (orientation() == QPageLayout::Landscape) {
1229                 title = i18nc("date from - to (week number)", "%1\u2013%2 (Week %3)", line1, line2, curWeek.weekNumber());
1230             } else {
1231                 title = i18nc("date from - to\\n(week number)", "%1\u2013%2\n(Week %3)", line1, line2, curWeek.weekNumber());
1232             }
1233             drawHeader(p, title, curWeek, QDate(), headerBox);
1234 
1235             drawTimeTable(p, fromWeek, curWeek, weekBox);
1236 
1237             if (mPrintFooter) {
1238                 drawFooter(p, footerBox);
1239             }
1240 
1241             fromWeek = fromWeek.addDays(7);
1242             curWeek = fromWeek.addDays(6);
1243             if (curWeek <= toWeek) {
1244                 mPrinter->newPage();
1245             }
1246         } while (curWeek <= toWeek);
1247         break;
1248 
1249     case SplitWeek: {
1250         QRect weekBox1(weekBox);
1251         // On the left side there are four days (mo-th) plus the timeline,
1252         // on the right there are only three days (fr-su) plus the timeline. Don't
1253         // use the whole width, but rather give them the same width as on the left.
1254         weekBox1.setRight(int((width - TIMELINE_WIDTH) * 3. / 4. + TIMELINE_WIDTH));
1255         do {
1256             QDate endLeft(fromWeek.addDays(3));
1257             int hh = headerHeight();
1258 
1259             drawSplitHeaderRight(p, fromWeek, curWeek, QDate(), width, hh);
1260             drawTimeTable(p, fromWeek, endLeft, weekBox);
1261             if (mPrintFooter) {
1262                 drawFooter(p, footerBox);
1263             }
1264             mPrinter->newPage();
1265             drawSplitHeaderRight(p, fromWeek, curWeek, QDate(), width, hh);
1266             drawTimeTable(p, endLeft.addDays(1), curWeek, weekBox1);
1267 
1268             if (mPrintFooter) {
1269                 drawFooter(p, footerBox);
1270             }
1271 
1272             fromWeek = fromWeek.addDays(7);
1273             curWeek = fromWeek.addDays(6);
1274             if (curWeek <= toWeek) {
1275                 mPrinter->newPage();
1276             }
1277         } while (curWeek <= toWeek);
1278         break;
1279     }
1280     }
1281 }
1282 
1283 /**************************************************************
1284  *           Print Month
1285  **************************************************************/
1286 
1287 CalPrintMonth::CalPrintMonth()
1288     : CalPrintPluginBase()
1289 {
1290 }
1291 
1292 CalPrintMonth::~CalPrintMonth() = default;
1293 
1294 QWidget *CalPrintMonth::createConfigWidget(QWidget *w)
1295 {
1296     return new CalPrintMonthConfig(w);
1297 }
1298 
1299 void CalPrintMonth::readSettingsWidget()
1300 {
1301     auto cfg = dynamic_cast<CalPrintMonthConfig *>((QWidget *)mConfigWidget);
1302 
1303     if (cfg) {
1304         mFromDate = QDate(cfg->mFromYear->value(), cfg->mFromMonth->currentIndex() + 1, 1);
1305         mToDate = QDate(cfg->mToYear->value(), cfg->mToMonth->currentIndex() + 1, 1);
1306 
1307         mWeekNumbers = cfg->mWeekNumbers->isChecked();
1308         mRecurDaily = cfg->mRecurDaily->isChecked();
1309         mRecurWeekly = cfg->mRecurWeekly->isChecked();
1310         mIncludeTodos = cfg->mIncludeTodos->isChecked();
1311         mShowNoteLines = cfg->mShowNoteLines->isChecked();
1312         mSingleLineLimit = cfg->mSingleLineLimit->isChecked();
1313         mUseColors = cfg->mColors->isChecked();
1314         mPrintFooter = cfg->mPrintFooter->isChecked();
1315         mIncludeDescription = cfg->mIncludeDescription->isChecked();
1316         mIncludeCategories = cfg->mIncludeCategories->isChecked();
1317         mExcludeConfidential = cfg->mExcludeConfidential->isChecked();
1318         mExcludePrivate = cfg->mExcludePrivate->isChecked();
1319     }
1320 }
1321 
1322 void CalPrintMonth::setSettingsWidget()
1323 {
1324     auto cfg = dynamic_cast<CalPrintMonthConfig *>((QWidget *)mConfigWidget);
1325 
1326     if (cfg) {
1327         setDateRange(mFromDate, mToDate);
1328 
1329         cfg->mWeekNumbers->setChecked(mWeekNumbers);
1330         cfg->mRecurDaily->setChecked(mRecurDaily);
1331         cfg->mRecurWeekly->setChecked(mRecurWeekly);
1332         cfg->mIncludeTodos->setChecked(mIncludeTodos);
1333         cfg->mShowNoteLines->setChecked(mShowNoteLines);
1334         cfg->mSingleLineLimit->setChecked(mSingleLineLimit);
1335         cfg->mColors->setChecked(mUseColors);
1336         cfg->mPrintFooter->setChecked(mPrintFooter);
1337         cfg->mIncludeDescription->setChecked(mIncludeDescription);
1338         cfg->mIncludeCategories->setChecked(mIncludeCategories);
1339         cfg->mExcludeConfidential->setChecked(mExcludeConfidential);
1340         cfg->mExcludePrivate->setChecked(mExcludePrivate);
1341     }
1342 }
1343 
1344 void CalPrintMonth::doLoadConfig()
1345 {
1346     CalPrintPluginBase::doLoadConfig();
1347     if (mConfig) {
1348         KConfigGroup grp(mConfig, groupName());
1349         mWeekNumbers = grp.readEntry("Print week numbers", true);
1350         mRecurDaily = grp.readEntry("Print daily incidences", true);
1351         mRecurWeekly = grp.readEntry("Print weekly incidences", true);
1352         mIncludeTodos = grp.readEntry("Include todos", false);
1353         mSingleLineLimit = grp.readEntry("Single line limit", false);
1354         mIncludeDescription = grp.readEntry("Include description", false);
1355         mIncludeCategories = grp.readEntry("Include categories", false);
1356     }
1357     setSettingsWidget();
1358 }
1359 
1360 void CalPrintMonth::doSaveConfig()
1361 {
1362     readSettingsWidget();
1363     if (mConfig) {
1364         KConfigGroup grp(mConfig, groupName());
1365         grp.writeEntry("Print week numbers", mWeekNumbers);
1366         grp.writeEntry("Print daily incidences", mRecurDaily);
1367         grp.writeEntry("Print weekly incidences", mRecurWeekly);
1368         grp.writeEntry("Include todos", mIncludeTodos);
1369         grp.writeEntry("Single line limit", mSingleLineLimit);
1370         grp.writeEntry("Include description", mIncludeDescription);
1371         grp.writeEntry("Include categories", mIncludeCategories);
1372     }
1373     CalPrintPluginBase::doSaveConfig();
1374 }
1375 
1376 void CalPrintMonth::setDateRange(const QDate &from, const QDate &to)
1377 {
1378     CalPrintPluginBase::setDateRange(from, to);
1379     auto cfg = dynamic_cast<CalPrintMonthConfig *>((QWidget *)mConfigWidget);
1380     if (cfg) {
1381         const QLocale locale;
1382         cfg->mFromMonth->clear();
1383         cfg->mToMonth->clear();
1384         for (int i = 1; i < 13; ++i) {
1385             const auto monthName = locale.standaloneMonthName(i, QLocale::LongFormat);
1386             cfg->mFromMonth->addItem(monthName);
1387             cfg->mToMonth->addItem(monthName);
1388         }
1389         cfg->mFromMonth->setCurrentIndex(from.month() - 1);
1390         cfg->mFromYear->setValue(to.year());
1391         cfg->mToMonth->setCurrentIndex(mToDate.month() - 1);
1392         cfg->mToYear->setValue(mToDate.year());
1393     }
1394 }
1395 
1396 void CalPrintMonth::print(QPainter &p, int width, int height)
1397 {
1398     QDate curMonth;
1399     QDate fromMonth;
1400     QDate toMonth;
1401 
1402     fromMonth = mFromDate.addDays(-(mFromDate.day() - 1));
1403     toMonth = mToDate.addDays(mToDate.daysInMonth() - mToDate.day());
1404 
1405     curMonth = fromMonth;
1406 
1407     QRect headerBox(0, 0, width, headerHeight());
1408     QRect footerBox(0, height - footerHeight(), width, footerHeight());
1409     height -= footerHeight();
1410 
1411     QRect monthBox(0, 0, width, height);
1412     monthBox.setTop(headerBox.bottom() + padding());
1413 
1414     do {
1415         QString title(
1416             i18nc("monthname year", "%1 %2", QLocale::system().standaloneMonthName(curMonth.month(), QLocale::LongFormat), QString::number(curMonth.year())));
1417         QDate tmp(fromMonth);
1418         int weekdayCol = weekdayColumn(tmp.dayOfWeek());
1419         tmp = tmp.addDays(-weekdayCol);
1420 
1421         drawHeader(p, title, curMonth.addMonths(-1), curMonth.addMonths(1), headerBox);
1422         drawMonthTable(p,
1423                        curMonth,
1424                        QTime(),
1425                        QTime(),
1426                        mWeekNumbers,
1427                        mRecurDaily,
1428                        mRecurWeekly,
1429                        mSingleLineLimit,
1430                        mIncludeDescription,
1431                        mIncludeCategories,
1432                        monthBox);
1433 
1434         if (mPrintFooter) {
1435             drawFooter(p, footerBox);
1436         }
1437 
1438         curMonth = curMonth.addDays(curMonth.daysInMonth());
1439         if (curMonth <= toMonth) {
1440             mPrinter->newPage();
1441         }
1442     } while (curMonth <= toMonth);
1443 }
1444 
1445 /**************************************************************
1446  *           Print Todos
1447  **************************************************************/
1448 
1449 CalPrintTodos::CalPrintTodos()
1450     : CalPrintPluginBase()
1451 {
1452     mTodoSortField = TodoFieldUnset;
1453     mTodoSortDirection = TodoDirectionUnset;
1454 }
1455 
1456 CalPrintTodos::~CalPrintTodos() = default;
1457 
1458 QWidget *CalPrintTodos::createConfigWidget(QWidget *w)
1459 {
1460     return new CalPrintTodoConfig(w);
1461 }
1462 
1463 void CalPrintTodos::readSettingsWidget()
1464 {
1465     auto cfg = dynamic_cast<CalPrintTodoConfig *>((QWidget *)mConfigWidget);
1466 
1467     if (cfg) {
1468         mPageTitle = cfg->mTitle->text();
1469 
1470         if (cfg->mPrintAll->isChecked()) {
1471             mTodoPrintType = TodosAll;
1472         } else if (cfg->mPrintUnfinished->isChecked()) {
1473             mTodoPrintType = TodosUnfinished;
1474         } else if (cfg->mPrintDueRange->isChecked()) {
1475             mTodoPrintType = TodosDueRange;
1476         } else {
1477             mTodoPrintType = TodosAll;
1478         }
1479 
1480         mFromDate = cfg->mFromDate->date();
1481         mToDate = cfg->mToDate->date();
1482 
1483         mIncludeDescription = cfg->mDescription->isChecked();
1484         mIncludePriority = cfg->mPriority->isChecked();
1485         mIncludeCategories = cfg->mCategories->isChecked();
1486         mIncludeStartDate = cfg->mStartDate->isChecked();
1487         mIncludeDueDate = cfg->mDueDate->isChecked();
1488         mIncludePercentComplete = cfg->mPercentComplete->isChecked();
1489         mConnectSubTodos = cfg->mConnectSubTodos->isChecked();
1490         mStrikeOutCompleted = cfg->mStrikeOutCompleted->isChecked();
1491         mExcludeConfidential = cfg->mExcludeConfidential->isChecked();
1492         mExcludePrivate = cfg->mExcludePrivate->isChecked();
1493 
1494         mTodoSortField = (eTodoSortField)cfg->mSortField->currentIndex();
1495         mTodoSortDirection = (eTodoSortDirection)cfg->mSortDirection->currentIndex();
1496 
1497         mPrintFooter = cfg->mPrintFooter->isChecked();
1498     }
1499 }
1500 
1501 void CalPrintTodos::setSettingsWidget()
1502 {
1503     auto cfg = dynamic_cast<CalPrintTodoConfig *>((QWidget *)mConfigWidget);
1504     if (cfg) {
1505         cfg->mTitle->setText(mPageTitle);
1506 
1507         cfg->mPrintAll->setChecked(mTodoPrintType == TodosAll);
1508         cfg->mPrintUnfinished->setChecked(mTodoPrintType == TodosUnfinished);
1509         cfg->mPrintDueRange->setChecked(mTodoPrintType == TodosDueRange);
1510 
1511         cfg->mFromDate->setDate(mFromDate);
1512         cfg->mToDate->setDate(mToDate);
1513 
1514         cfg->mDescription->setChecked(mIncludeDescription);
1515         cfg->mPriority->setChecked(mIncludePriority);
1516         cfg->mCategories->setChecked(mIncludeCategories);
1517         cfg->mStartDate->setChecked(mIncludeStartDate);
1518         cfg->mDueDate->setChecked(mIncludeDueDate);
1519         cfg->mPercentComplete->setChecked(mIncludePercentComplete);
1520         cfg->mConnectSubTodos->setChecked(mConnectSubTodos);
1521         cfg->mStrikeOutCompleted->setChecked(mStrikeOutCompleted);
1522         cfg->mExcludeConfidential->setChecked(mExcludeConfidential);
1523         cfg->mExcludePrivate->setChecked(mExcludePrivate);
1524 
1525         if (mTodoSortField != TodoFieldUnset) {
1526             // do not insert if already done so.
1527             cfg->mSortField->addItem(i18nc("@option sort by summary", "Summary"));
1528             cfg->mSortField->addItem(i18nc("@option sort by start date/time", "Start Date"));
1529             cfg->mSortField->addItem(i18nc("@option sort by due date/time", "Due Date"));
1530             cfg->mSortField->addItem(i18nc("@option sort by priority", "Priority"));
1531             cfg->mSortField->addItem(i18nc("@option sort by percent completed", "Percent Complete"));
1532             cfg->mSortField->addItem(i18nc("@option sort by tags", "Tags"));
1533             cfg->mSortField->setCurrentIndex(mTodoSortField);
1534         }
1535 
1536         if (mTodoSortDirection != TodoDirectionUnset) {
1537             // do not insert if already done so.
1538             cfg->mSortDirection->addItem(i18nc("@option sort in increasing order", "Ascending"));
1539             cfg->mSortDirection->addItem(i18nc("@option sort in descreasing order", "Descending"));
1540             cfg->mSortDirection->setCurrentIndex(mTodoSortDirection);
1541         }
1542 
1543         cfg->mPrintFooter->setChecked(mPrintFooter);
1544     }
1545 }
1546 
1547 void CalPrintTodos::doLoadConfig()
1548 {
1549     CalPrintPluginBase::doLoadConfig();
1550     if (mConfig) {
1551         KConfigGroup grp(mConfig, groupName());
1552         mPageTitle = grp.readEntry("Page title", i18n("To-do list"));
1553         mTodoPrintType = (eTodoPrintType)grp.readEntry("Print type", static_cast<int>(TodosAll));
1554         mIncludeDescription = grp.readEntry("Include description", true);
1555         mIncludePriority = grp.readEntry("Include priority", true);
1556         mIncludeCategories = grp.readEntry("Include categories", true);
1557         mIncludeStartDate = grp.readEntry("Include start date", true);
1558         mIncludeDueDate = grp.readEntry("Include due date", true);
1559         mIncludePercentComplete = grp.readEntry("Include percentage completed", true);
1560         mConnectSubTodos = grp.readEntry("Connect subtodos", true);
1561         mStrikeOutCompleted = grp.readEntry("Strike out completed summaries", true);
1562         mTodoSortField = (eTodoSortField)grp.readEntry("Sort field", static_cast<int>(TodoFieldSummary));
1563         mTodoSortDirection = (eTodoSortDirection)grp.readEntry("Sort direction", static_cast<int>(TodoDirectionAscending));
1564     }
1565     setSettingsWidget();
1566 }
1567 
1568 void CalPrintTodos::doSaveConfig()
1569 {
1570     readSettingsWidget();
1571     if (mConfig) {
1572         KConfigGroup grp(mConfig, groupName());
1573         grp.writeEntry("Page title", mPageTitle);
1574         grp.writeEntry("Print type", int(mTodoPrintType));
1575         grp.writeEntry("Include description", mIncludeDescription);
1576         grp.writeEntry("Include priority", mIncludePriority);
1577         grp.writeEntry("Include categories", mIncludeCategories);
1578         grp.writeEntry("Include start date", mIncludeStartDate);
1579         grp.writeEntry("Include due date", mIncludeDueDate);
1580         grp.writeEntry("Include percentage completed", mIncludePercentComplete);
1581         grp.writeEntry("Connect subtodos", mConnectSubTodos);
1582         grp.writeEntry("Strike out completed summaries", mStrikeOutCompleted);
1583         grp.writeEntry("Sort field", static_cast<int>(mTodoSortField));
1584         grp.writeEntry("Sort direction", static_cast<int>(mTodoSortDirection));
1585     }
1586     CalPrintPluginBase::doSaveConfig();
1587 }
1588 
1589 void CalPrintTodos::print(QPainter &p, int width, int height)
1590 {
1591     int possummary = 100;
1592 
1593     QRect headerBox(0, 0, width, headerHeight());
1594     QRect footerBox(0, height - footerHeight(), width, footerHeight());
1595     height -= footerHeight();
1596 
1597     // Draw the First Page Header
1598     drawHeader(p, mPageTitle, mFromDate, QDate(), headerBox);
1599 
1600     // Estimate widths of some data columns.
1601     QFont oldFont(p.font());
1602     p.setFont(QFont(QStringLiteral("sans-serif"), 10));
1603     const int widDate = p.fontMetrics().boundingRect(QLocale::system().toString(QDate(2222, 12, 22), QLocale::ShortFormat)).width();
1604     const int widPct = p.fontMetrics().boundingRect(i18n("%1%", 100)).width() + 27;
1605 
1606     // Draw the Column Headers
1607     int mCurrentLinePos = headerHeight() + 5;
1608     QString outStr;
1609 
1610     p.setFont(QFont(QStringLiteral("sans-serif"), 9, QFont::Bold));
1611     int lineSpacing = p.fontMetrics().lineSpacing();
1612     mCurrentLinePos += lineSpacing;
1613     int pospriority = -1;
1614     if (mIncludePriority) {
1615         outStr += i18n("Priority");
1616         pospriority = 0;
1617         p.drawText(pospriority, mCurrentLinePos - 2, outStr);
1618     }
1619 
1620     int posSoFar = width; // Position of leftmost optional header.
1621 
1622     int posdue = -1;
1623     if (mIncludeDueDate) {
1624         outStr.truncate(0);
1625         outStr += i18nc("@label to-do due date", "Due");
1626         const int widDue = std::max(p.fontMetrics().boundingRect(outStr).width(), widDate);
1627         posdue = posSoFar - widDue;
1628         p.drawText(posdue, mCurrentLinePos - 2, outStr);
1629         posSoFar = posdue;
1630     }
1631 
1632     int posStart = -1;
1633     if (mIncludeStartDate) {
1634         outStr.truncate(0);
1635         outStr += i18nc("@label to-do start date", "Start");
1636         const int widStart = std::max(p.fontMetrics().boundingRect(outStr).width(), widDate);
1637         posStart = posSoFar - widStart - 5;
1638         p.drawText(posStart, mCurrentLinePos - 2, outStr);
1639         posSoFar = posStart;
1640     }
1641 
1642     int poscomplete = -1;
1643     if (mIncludePercentComplete) {
1644         outStr.truncate(0);
1645         outStr += i18nc("@label to-do percentage complete", "Complete");
1646         const int widComplete = std::max(p.fontMetrics().boundingRect(outStr).width(), widPct);
1647         poscomplete = posSoFar - widComplete - 5;
1648         p.drawText(poscomplete, mCurrentLinePos - 2, outStr);
1649         posSoFar = poscomplete;
1650     }
1651 
1652     int posCategories = -1;
1653     if (mIncludeCategories) {
1654         outStr.truncate(0);
1655         outStr += i18nc("@label to-do categories", "Tags");
1656         const int widCats = std::max(p.fontMetrics().boundingRect(outStr).width(), 100); // Arbitrary!
1657         posCategories = posSoFar - widCats - 5;
1658         p.drawText(posCategories, mCurrentLinePos - 2, outStr);
1659     }
1660 
1661     p.setFont(QFont(QStringLiteral("sans-serif"), 10));
1662 
1663     KCalendarCore::Todo::List todoList;
1664     KCalendarCore::Todo::List tempList;
1665 
1666     KCalendarCore::SortDirection sortDirection = KCalendarCore::SortDirectionAscending;
1667     switch (mTodoSortDirection) {
1668     case TodoDirectionAscending:
1669         sortDirection = KCalendarCore::SortDirectionAscending;
1670         break;
1671     case TodoDirectionDescending:
1672         sortDirection = KCalendarCore::SortDirectionDescending;
1673         break;
1674     case TodoDirectionUnset:
1675         break;
1676     }
1677 
1678     KCalendarCore::TodoSortField sortField = KCalendarCore::TodoSortSummary;
1679     switch (mTodoSortField) {
1680     case TodoFieldSummary:
1681         sortField = KCalendarCore::TodoSortSummary;
1682         break;
1683     case TodoFieldStartDate:
1684         sortField = KCalendarCore::TodoSortStartDate;
1685         break;
1686     case TodoFieldDueDate:
1687         sortField = KCalendarCore::TodoSortDueDate;
1688         break;
1689     case TodoFieldPriority:
1690         sortField = KCalendarCore::TodoSortPriority;
1691         break;
1692     case TodoFieldPercentComplete:
1693         sortField = KCalendarCore::TodoSortPercentComplete;
1694         break;
1695     case TodoFieldCategories:
1696         sortField = KCalendarCore::TodoSortCategories;
1697         break;
1698     case TodoFieldUnset:
1699         break;
1700     }
1701 
1702     // Create list of to-dos which will be printed
1703     todoList = mCalendar->todos(sortField, sortDirection);
1704     switch (mTodoPrintType) {
1705     case TodosAll:
1706         break;
1707     case TodosUnfinished:
1708         for (const KCalendarCore::Todo::Ptr &todo : std::as_const(todoList)) {
1709             Q_ASSERT(todo);
1710             if (!todo->isCompleted()) {
1711                 tempList.append(todo);
1712             }
1713         }
1714         todoList = tempList;
1715         break;
1716     case TodosDueRange:
1717         for (const KCalendarCore::Todo::Ptr &todo : std::as_const(todoList)) {
1718             Q_ASSERT(todo);
1719             if (todo->hasDueDate()) {
1720                 if (todo->dtDue().date() >= mFromDate && todo->dtDue().date() <= mToDate) {
1721                     tempList.append(todo);
1722                 }
1723             } else {
1724                 tempList.append(todo);
1725             }
1726         }
1727         todoList = tempList;
1728         break;
1729     }
1730 
1731     // Print to-dos
1732     int count = 0;
1733     for (const KCalendarCore::Todo::Ptr &todo : std::as_const(todoList)) {
1734         // Skip sub-to-dos. They will be printed recursively in drawTodo()
1735         if (todo->relatedTo().isEmpty()) { // review(AKONADI_PORT)
1736             count++;
1737             drawTodo(count,
1738                      todo,
1739                      p,
1740                      sortField,
1741                      sortDirection,
1742                      mConnectSubTodos,
1743                      mStrikeOutCompleted,
1744                      mIncludeDescription,
1745                      pospriority,
1746                      possummary,
1747                      posCategories,
1748                      posStart,
1749                      posdue,
1750                      poscomplete,
1751                      0,
1752                      0,
1753                      mCurrentLinePos,
1754                      width,
1755                      height,
1756                      todoList,
1757                      nullptr);
1758         }
1759     }
1760 
1761     if (mPrintFooter) {
1762         drawFooter(p, footerBox);
1763     }
1764     p.setFont(oldFont);
1765 }
1766 
1767 #include "moc_calprintdefaultplugins.cpp"