/*
  Copyright (c) 1998 Preston Brown <pbrown@kde.org>
  Copyright (C) 2003 Reinhold Kainhofer <reinhold@kainhofer.com>
  Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org>
  Copyright (c) 2008 Ron Goodheart <rong.dev@gmail.com>
  Copyright (C) 2010-2020 Laurent Montel <montel@kde.org>
  Copyright (c) 2012-2013 Allen Winter <winter@kde.org>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

  As a special exception, permission is given to link this program
  with any edition of Qt, and distribute the resulting executable,
  without including the source code for Qt in the source distribution.
*/

#include "calprintdefaultplugins.h"
#include "kcalprefs.h"
#include "utils.h"

#include <Item>

#include <KCalendarCore/Visitor>

#include <KCalUtils/IncidenceFormatter>
#include <KCalUtils/Stringify>

#include <KConfigGroup>

#include <QPainter>
#include <QPrinter>
#include <KLocalizedString>

using namespace CalendarSupport;

static QString cleanStr(const QString &instr)
{
    QString ret = instr;
    return ret.replace(QLatin1Char('\n'), QLatin1Char(' '));
}

/**************************************************************
 *           Print Incidence
 **************************************************************/

CalPrintIncidence::CalPrintIncidence() : CalPrintPluginBase()
{
}

CalPrintIncidence::~CalPrintIncidence()
{
}

QWidget *CalPrintIncidence::createConfigWidget(QWidget *w)
{
    return new CalPrintIncidenceConfig(w);
}

void CalPrintIncidence::readSettingsWidget()
{
    CalPrintIncidenceConfig *cfg
        = dynamic_cast<CalPrintIncidenceConfig *>((QWidget *)mConfigWidget);
    if (cfg) {
        mUseColors = cfg->mColors->isChecked();
        mPrintFooter = cfg->mPrintFooter->isChecked();
        mShowOptions = cfg->mShowDetails->isChecked();
        mShowSubitemsNotes = cfg->mShowSubitemsNotes->isChecked();
        mShowAttendees = cfg->mShowAttendees->isChecked();
        mShowAttachments = cfg->mShowAttachments->isChecked();
        mShowNoteLines = cfg->mShowNoteLines->isChecked();
    }
}

void CalPrintIncidence::setSettingsWidget()
{
    CalPrintIncidenceConfig *cfg
        = dynamic_cast<CalPrintIncidenceConfig *>((QWidget *)mConfigWidget);
    if (cfg) {
        cfg->mColors->setChecked(mUseColors);
        cfg->mPrintFooter->setChecked(mPrintFooter);
        cfg->mShowDetails->setChecked(mShowOptions);
        cfg->mShowSubitemsNotes->setChecked(mShowSubitemsNotes);
        cfg->mShowAttendees->setChecked(mShowAttendees);
        cfg->mShowAttachments->setChecked(mShowAttachments);
        cfg->mShowNoteLines->setChecked(mShowNoteLines);
    }
}

void CalPrintIncidence::loadConfig()
{
    if (mConfig) {
        KConfigGroup grp(mConfig, groupName());
        mShowOptions = grp.readEntry("Show Options", false);
        mShowSubitemsNotes = grp.readEntry("Show Subitems and Notes", false);
        mShowAttendees = grp.readEntry("Use Attendees", false);
        mShowAttachments = grp.readEntry("Use Attachments", false);
        mShowNoteLines = grp.readEntry("Note Lines", false);
    }
    setSettingsWidget();
}

void CalPrintIncidence::saveConfig()
{
    readSettingsWidget();
    if (mConfig) {
        KConfigGroup grp(mConfig, groupName());
        grp.writeEntry("Show Options", mShowOptions);
        grp.writeEntry("Show Subitems and Notes", mShowSubitemsNotes);
        grp.writeEntry("Use Attendees", mShowAttendees);
        grp.writeEntry("Use Attachments", mShowAttachments);
        grp.writeEntry("Note Lines", mShowNoteLines);
    }
}

class TimePrintStringsVisitor : public KCalendarCore::Visitor
{
public:
    TimePrintStringsVisitor()
    {
    }

    bool act(KCalendarCore::IncidenceBase::Ptr incidence)
    {
        return incidence->accept(*this, incidence);
    }

    QString mStartCaption, mStartString;
    QString mEndCaption, mEndString;
    QString mDurationCaption, mDurationString;

protected:
    bool visit(const KCalendarCore::Event::Ptr &event) override
    {
        if (event->dtStart().isValid()) {
            mStartCaption = i18n("Start date: ");
            mStartString = KCalUtils::IncidenceFormatter::dateTimeToString(
                event->dtStart(), event->allDay(), false);
        } else {
            mStartCaption = i18n("No start date");
            mStartString.clear();
        }

        if (event->hasEndDate()) {
            mEndCaption = i18n("End date: ");
            mEndString = KCalUtils::IncidenceFormatter::dateTimeToString(
                event->dtEnd(), event->allDay(), false);
        } else if (event->hasDuration()) {
            mEndCaption = i18n("Duration: ");
            int mins = event->duration().asSeconds() / 60;
            if (mins >= 60) {
                mEndString += i18np("1 hour ", "%1 hours ", mins / 60);
            }
            if (mins % 60 > 0) {
                mEndString += i18np("1 minute ", "%1 minutes ", mins % 60);
            }
        } else {
            mEndCaption = i18n("No end date");
            mEndString.clear();
        }
        return true;
    }

    bool visit(const KCalendarCore::Todo::Ptr &todo) override
    {
        if (todo->hasStartDate()) {
            mStartCaption = i18n("Start date: ");
            mStartString = KCalUtils::IncidenceFormatter::dateTimeToString(
                todo->dtStart(), todo->allDay(), false);
        } else {
            mStartCaption = i18n("No start date");
            mStartString.clear();
        }

        if (todo->hasDueDate()) {
            mEndCaption = i18n("Due date: ");
            mEndString = KCalUtils::IncidenceFormatter::dateTimeToString(
                todo->dtDue(), todo->allDay(), false);
        } else {
            mEndCaption = i18n("No due date");
            mEndString.clear();
        }
        return true;
    }

    bool visit(const KCalendarCore::Journal::Ptr &journal) override
    {
        mStartCaption = i18n("Start date: ");
        mStartString = KCalUtils::IncidenceFormatter::dateTimeToString(
            journal->dtStart(), journal->allDay(), false);
        mEndCaption.clear();
        mEndString.clear();
        return true;
    }

    bool visit(const KCalendarCore::FreeBusy::Ptr &fb) override
    {
        Q_UNUSED(fb);
        return true;
    }
};

int CalPrintIncidence::printCaptionAndText(QPainter &p, const QRect &box, const QString &caption, const QString &text, const QFont &captionFont, const QFont &textFont)
{
    QFontMetrics captionFM(captionFont);
    int textWd = captionFM.boundingRect(caption).width();
    QRect textRect(box);

    QFont oldFont(p.font());
    p.setFont(captionFont);
    p.drawText(box, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine, caption);

    if (!text.isEmpty()) {
        textRect.setLeft(textRect.left() + textWd);
        p.setFont(textFont);
        p.drawText(textRect, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine, text);
    }
    p.setFont(oldFont);
    return textRect.bottom();
}

void CalPrintIncidence::print(QPainter &p, int width, int height)
{
    QFont oldFont(p.font());
    QFont textFont(QStringLiteral("sans-serif"), 11, QFont::Normal);
    QFont captionFont(QStringLiteral("sans-serif"), 11, QFont::Bold);
    p.setFont(textFont);
    int lineHeight = p.fontMetrics().lineSpacing();
    QString cap, txt;

    KCalendarCore::Incidence::List::ConstIterator it;
    for (it = mSelectedIncidences.constBegin(); it != mSelectedIncidences.constEnd(); ++it) {
        // don't do anything on a 0-pointer!
        if (!(*it)) {
            continue;
        }
        if (it != mSelectedIncidences.constBegin()) {
            mPrinter->newPage();
        }

        const bool isJournal = ((*it)->type() == KCalendarCore::Incidence::TypeJournal);

        //  PAGE Layout (same for landscape and portrait! astonishingly, it looks good with both!):
        //  +-----------------------------------+
        //  | Header:  Summary                  |
        //  +===================================+
        //  | start: ______   end: _________    |
        //  | repeats: ___________________      |
        //  | reminder: __________________      |
        //  +-----------------------------------+
        //  | Location: ______________________  |
        //  +------------------------+----------+
        //  | Description:           | Notes or |
        //  |                        | Subitems |
        //  |                        |          |
        //  |                        |          |
        //  |                        |          |
        //  |                        |          |
        //  |                        |          |
        //  |                        |          |
        //  |                        |          |
        //  |                        |          |
        //  +------------------------+----------+
        //  | Attachments:           | Settings |
        //  |                        |          |
        //  +------------------------+----------+
        //  | Attendees:                        |
        //  |                                   |
        //  +-----------------------------------+
        //  | Categories: _____________________ |
        //  +-----------------------------------+

        QRect box(0, 0, width, height);
        QRect titleBox(box);
        titleBox.setHeight(headerHeight());
        QColor headerColor = categoryBgColor(*it);
        // Draw summary as header, no small calendars in title bar, expand height if needed
        int titleBottom = drawHeader(p, (*it)->summary(), QDate(), QDate(),
                                     titleBox, true, headerColor);
        titleBox.setBottom(titleBottom);

        QRect timesBox(titleBox);
        timesBox.setTop(titleBox.bottom() + padding());
        timesBox.setHeight(height / 8);

        TimePrintStringsVisitor stringVis;
        int h = timesBox.top();
        if (stringVis.act(*it)) {
            QRect textRect(timesBox.left() + padding(),
                           timesBox.top() + padding(), 0, lineHeight);
            textRect.setRight(timesBox.center().x());
            h = printCaptionAndText(p, textRect, stringVis.mStartCaption,
                                    stringVis.mStartString, captionFont, textFont);

            textRect.setLeft(textRect.right());
            textRect.setRight(timesBox.right() - padding());
            h = qMax(printCaptionAndText(p, textRect, stringVis.mEndCaption,
                                         stringVis.mEndString, captionFont, textFont), h);
        }

        // Recurrence Printing
        if ((*it)->recurs()) {
            QRect recurBox(timesBox.left() + padding(), h + padding(),
                           timesBox.right() - padding(), lineHeight);
            KCalendarCore::Recurrence *recurs = (*it)->recurrence();
            QString displayString = KCalUtils::IncidenceFormatter::recurrenceString((*it));
            // exception dates
            QString exceptString;
            if (!recurs->exDates().isEmpty()) {
                exceptString = i18nc("except for listed dates", " except");
                for (int i = 0; i < recurs->exDates().size(); ++i) {
                    exceptString.append(QLatin1Char(' '));
                    exceptString.append(QLocale::system().toString(recurs->exDates().at(i),
                                                                   QLocale::ShortFormat));
                }
            }
            displayString.append(exceptString);
            h = qMax(printCaptionAndText(p, recurBox, i18n("Repeats: "),
                                         displayString, captionFont, textFont), h);
        }

        if (!isJournal) {
            // Alarms Printing
            QRect alarmBox(timesBox.left() + padding(), h + padding(),
                           timesBox.right() - padding(), lineHeight);
            KCalendarCore::Alarm::List alarms = (*it)->alarms();
            if (alarms.isEmpty()) {
                cap = i18n("No reminders");
                txt.clear();
            } else {
                cap = i18np("Reminder: ", "%1 reminders: ", alarms.count());

                QStringList alarmStrings;
                KCalendarCore::Alarm::List::ConstIterator it;
                alarmStrings.reserve(alarms.count());
                for (it = alarms.constBegin(); it != alarms.constEnd(); ++it) {
                    KCalendarCore::Alarm::Ptr alarm = *it;

                    // Alarm offset, copied from koeditoralarms.cpp:
                    KLocalizedString offsetstr;
                    int offset = 0;
                    if (alarm->hasStartOffset()) {
                        offset = alarm->startOffset().asSeconds();
                        if (offset < 0) {
                            offsetstr = ki18nc("N days/hours/minutes before/after the start/end",
                                               "%1 before the start");
                            offset = -offset;
                        } else {
                            offsetstr = ki18nc("N days/hours/minutes before/after the start/end",
                                               "%1 after the start");
                        }
                    } else if (alarm->hasEndOffset()) {
                        offset = alarm->endOffset().asSeconds();
                        if (offset < 0) {
                            offsetstr = ki18nc("N days/hours/minutes before/after the start/end",
                                               "%1 before the end");
                            offset = -offset;
                        } else {
                            offsetstr = ki18nc("N days/hours/minutes before/after the start/end",
                                               "%1 after the end");
                        }
                    }

                    offset = offset / 60; // make minutes
                    int useoffset = 0;

                    if (offset % (24 * 60) == 0 && offset > 0) {     // divides evenly into days?
                        useoffset = offset / (24 * 60);
                        offsetstr = offsetstr.subs(i18np("1 day", "%1 days", useoffset));
                    } else if (offset % 60 == 0 && offset > 0) {   // divides evenly into hours?
                        useoffset = offset / 60;
                        offsetstr = offsetstr.subs(i18np("1 hour", "%1 hours", useoffset));
                    } else {
                        useoffset = offset;
                        offsetstr = offsetstr.subs(i18np("1 minute", "%1 minutes", useoffset));
                    }
                    alarmStrings << offsetstr.toString();
                }
                txt = alarmStrings.join(i18nc("Spacer for the joined list of categories", ", "));
            }
            h = qMax(printCaptionAndText(p, alarmBox, cap, txt, captionFont, textFont), h);
        }
        QRect organizerBox(timesBox.left() + padding(), h + padding(),
                           timesBox.right() - padding(), lineHeight);
        h = qMax(printCaptionAndText(p, organizerBox, i18n("Organizer: "),
                                     (*it)->organizer().fullName(), captionFont, textFont), h);

        // Finally, draw the frame around the time information...
        timesBox.setBottom(qMax(timesBox.bottom(), h + padding()));
        drawBox(p, BOX_BORDER_WIDTH, timesBox);

        QRect locationBox(timesBox);
        locationBox.setTop(timesBox.bottom() + padding());
        locationBox.setHeight(0);
        int locationBottom = 0;
        if (!isJournal) {
            locationBottom = drawBoxWithCaption(p, locationBox, i18n("Location: "),
                                                (*it)->location(), /*sameLine=*/ true,
                                                /*expand=*/ true, captionFont, textFont);
        }
        locationBox.setBottom(locationBottom);

        // Now start constructing the boxes from the bottom:
        QRect footerBox(locationBox);
        footerBox.setBottom(box.bottom());
        footerBox.setTop(footerBox.bottom() - lineHeight - 2 * padding());

        QRect categoriesBox(footerBox);
        categoriesBox.setBottom(footerBox.top());
        categoriesBox.setTop(categoriesBox.bottom() - lineHeight - 2 * padding());
        QRect attendeesBox(box.left(), categoriesBox.top() - padding() - box.height() / 9,
                           box.width(), box.height() / 9);
        QRect attachmentsBox(box.left(), attendeesBox.top() - padding() - box.height() / 9,
                             box.width() * 3 / 4 - padding(), box.height() / 9);
        QRect optionsBox(isJournal ? box.left() : attachmentsBox.right() + padding(),
                         attachmentsBox.top(), 0, 0);
        optionsBox.setRight(box.right());
        optionsBox.setBottom(attachmentsBox.bottom());
        QRect notesBox(optionsBox.left(),
                       isJournal ? (timesBox.bottom() + padding())
                       : (locationBox.bottom() + padding()),
                       optionsBox.width(), 0);
        notesBox.setBottom(optionsBox.top() - padding());
        QRect descriptionBox(notesBox);
        descriptionBox.setLeft(box.left());
        descriptionBox.setRight(attachmentsBox.right());

        // Adjust boxes depending on the show options...
        if (!mShowSubitemsNotes || isJournal) {
            descriptionBox.setRight(box.right());
        }
        if (!mShowAttachments || !mShowAttendees) {
            descriptionBox.setBottom(attachmentsBox.bottom());
            optionsBox.setTop(attendeesBox.top());
            optionsBox.setBottom(attendeesBox.bottom());
            notesBox.setBottom(attachmentsBox.bottom());
            if (mShowOptions) {
                attendeesBox.setRight(attachmentsBox.right());
            }
            if (!mShowAttachments && !mShowAttendees) {
                if (mShowSubitemsNotes) {
                    descriptionBox.setBottom(attendeesBox.bottom());
                }
                if (!mShowOptions) {
                    descriptionBox.setBottom(attendeesBox.bottom());
                    notesBox.setBottom(attendeesBox.bottom());
                }
            }
        }
        if (mShowAttachments && !isJournal) {
            if (!mShowOptions) {
                attachmentsBox.setRight(box.right());
                attachmentsBox.setRight(box.right());
            }
            if (!mShowAttendees) {
                attachmentsBox.setTop(attendeesBox.top());
                attachmentsBox.setBottom(attendeesBox.bottom());
            }
        }
        int newBottom = drawBoxWithCaption(p, descriptionBox, i18n("Description:"),
                                           (*it)->description(), /*sameLine=*/ false,
                                           /*expand=*/ false, captionFont, textFont,
                                           (*it)->descriptionIsRich());
        if (mShowNoteLines) {
            drawNoteLines(p, descriptionBox, newBottom);
        }

        Akonadi::Item item = mCalendar->item((*it)->uid());
        Akonadi::Item::List relations = mCalendar->childItems(item.id());

        if (mShowSubitemsNotes && !isJournal) {
            if (relations.isEmpty() || (*it)->type() != KCalendarCore::Incidence::TypeTodo) {
                int notesPosition = drawBoxWithCaption(p, notesBox, i18n("Notes:"),
                                                       QString(), /*sameLine=*/ false,
                                                       /*expand=*/ false, captionFont, textFont);
                drawNoteLines(p, notesBox, notesPosition);
            } else {
                QString subitemCaption;
                if (relations.isEmpty()) {
                    subitemCaption = i18n("No Subitems");
                    txt.clear();
                } else {
                    subitemCaption = i18np("1 Subitem:",
                                           "%1 Subitems:",
                                           relations.count());
                }

                QString subitemString;
                QString statusString;
                QString datesString;
                int count = 0;
                for (const Akonadi::Item &item : qAsConst(relations)) {
                    KCalendarCore::Todo::Ptr todo = CalendarSupport::todo(item);
                    ++count;
                    if (!todo) {   // defensive, skip any zero pointers
                        continue;
                    }
                    // format the status
                    statusString = KCalUtils::Stringify::incidenceStatus(todo->status());
                    if (statusString.isEmpty()) {
                        if (todo->status() == KCalendarCore::Incidence::StatusNone) {
                            statusString = i18nc("no status", "none");
                        } else {
                            statusString = i18nc("unknown status", "unknown");
                        }
                    }
                    // format the dates if provided
                    datesString.clear();
                    if (todo->dtStart().isValid()) {
                        datesString += i18nc("subitem start date", "Start Date: %1\n",
                                             QLocale().toString(todo->dtStart().toLocalTime().date(),
                                                                QLocale::ShortFormat));
                        if (!todo->allDay()) {
                            datesString += i18nc("subitem start time", "Start Time: %1\n",
                                                 QLocale().toString(todo->dtStart().toLocalTime().
                                                                    time(), QLocale::ShortFormat));
                        }
                    }
                    if (todo->dateTime(KCalendarCore::Incidence::RoleEnd).isValid()) {
                        subitemString += i18nc("subitem due date", "Due Date: %1\n",
                                               QLocale().toString(todo->dateTime(KCalendarCore::Incidence
                                                                                 ::RoleEnd).
                                                                  toLocalTime().date(),
                                                                  QLocale::ShortFormat));

                        if (!todo->allDay()) {
                            subitemString += i18nc("subitem due time", "Due Time: %1\n",
                                                   QLocale().toString(todo->dateTime(KCalendarCore::
                                                                                     Incidence::
                                                                                     RoleEnd).
                                                                      toLocalTime().time(),
                                                                      QLocale::ShortFormat));
                        }
                    }
                    subitemString += i18nc("subitem counter", "%1: ", count);
                    subitemString += todo->summary();
                    subitemString += QLatin1Char('\n');
                    if (!datesString.isEmpty()) {
                        subitemString += datesString;
                        subitemString += QLatin1Char('\n');
                    }
                    subitemString += i18nc("subitem Status: statusString",
                                           "Status: %1\n",
                                           statusString);
                    subitemString += KCalUtils::IncidenceFormatter::recurrenceString(todo)
                                     + QLatin1Char('\n');
                    subitemString += i18nc("subitem Priority: N",
                                           "Priority: %1\n",
                                           QString::number(todo->priority()));
                    subitemString += i18nc("subitem Secrecy: secrecyString",
                                           "Secrecy: %1\n",
                                           KCalUtils::Stringify::incidenceSecrecy(todo->secrecy()));
                    subitemString += QLatin1Char('\n');
                }
                drawBoxWithCaption(p, notesBox, subitemCaption,
                                   subitemString, /*sameLine=*/ false,
                                   /*expand=*/ false, captionFont, textFont);
            }
        }

        if (mShowAttachments && !isJournal) {
            const KCalendarCore::Attachment::List attachments = (*it)->attachments();
            QString attachmentCaption;
            if (attachments.isEmpty()) {
                attachmentCaption = i18n("No Attachments");
                txt.clear();
            } else {
                attachmentCaption = i18np("1 Attachment:",
                                          "%1 Attachments:",
                                          attachments.count());
            }
            QString attachmentString;
            KCalendarCore::Attachment::List::ConstIterator ait = attachments.constBegin();
            for (; ait != attachments.constEnd(); ++ait) {
                if (!attachmentString.isEmpty()) {
                    attachmentString += i18nc("Spacer for list of attachments", "  ");
                }
                attachmentString.append((*ait).label());
            }
            drawBoxWithCaption(p, attachmentsBox,
                               attachmentCaption, attachmentString,
                               /*sameLine=*/ false, /*expand=*/ false,
                               captionFont, textFont);
        }
        if (mShowAttendees) {
            const KCalendarCore::Attendee::List attendees = (*it)->attendees();
            QString attendeeCaption;
            if (attendees.isEmpty()) {
                attendeeCaption = i18n("No Attendees");
            } else {
                attendeeCaption = i18np("1 Attendee:",
                                        "%1 Attendees:",
                                        attendees.count());
            }
            QString attendeeString;
            KCalendarCore::Attendee::List::ConstIterator ait = attendees.constBegin();
            for (; ait != attendees.constEnd(); ++ait) {
                if (!attendeeString.isEmpty()) {
                    attendeeString += QLatin1Char('\n');
                }
                attendeeString += i18nc(
                    "Formatting of an attendee: "
                    "'Name (Role): Status', e.g. 'Reinhold Kainhofer "
                    "<reinhold@kainhofer.com> (Participant): Awaiting Response'",
                    "%1 (%2): %3",
                    (*ait).fullName(),
                    KCalUtils::Stringify::attendeeRole((*ait).role()),
                    KCalUtils::Stringify::attendeeStatus((*ait).status()));
            }
            drawBoxWithCaption(p, attendeesBox, attendeeCaption, attendeeString,
                               /*sameLine=*/ false, /*expand=*/ false,
                               captionFont, textFont);
        }

        if (mShowOptions) {
            QString optionsString;
            if (!KCalUtils::Stringify::incidenceStatus((*it)->status()).isEmpty()) {
                optionsString
                    += i18n("Status: %1", KCalUtils::Stringify::incidenceStatus((*it)->status()));
                optionsString += QLatin1Char('\n');
            }
            if (!KCalUtils::Stringify::incidenceSecrecy((*it)->secrecy()).isEmpty()) {
                optionsString
                    += i18n("Secrecy: %1",
                            KCalUtils::Stringify::incidenceSecrecy((*it)->secrecy()));
                optionsString += QLatin1Char('\n');
            }
            if ((*it)->type() == KCalendarCore::Incidence::TypeEvent) {
                KCalendarCore::Event::Ptr e = (*it).staticCast<KCalendarCore::Event>();
                if (e->transparency() == KCalendarCore::Event::Opaque) {
                    optionsString += i18n("Show as: Busy");
                } else {
                    optionsString += i18n("Show as: Free");
                }
                optionsString += QLatin1Char('\n');
            } else if ((*it)->type() == KCalendarCore::Incidence::TypeTodo) {
                KCalendarCore::Todo::Ptr t = (*it).staticCast<KCalendarCore::Todo>();
                if (t->isOverdue()) {
                    optionsString += i18n("This task is overdue!");
                    optionsString += QLatin1Char('\n');
                }
            } else if ((*it)->type() == KCalendarCore::Incidence::TypeJournal) {
                //TODO: Anything Journal-specific?
            }
            drawBoxWithCaption(p, optionsBox, i18n("Settings: "),
                               optionsString, /*sameLine=*/ false, /*expand=*/ false,
                               captionFont, textFont);
        }

        drawBoxWithCaption(p, categoriesBox, i18n("Categories: "),
                           (*it)->categories().join(i18nc("Spacer for the joined list of categories",
                                                          ", ")),
                           /*sameLine=*/ true, /*expand=*/ false, captionFont, textFont);

        if (mPrintFooter) {
            drawFooter(p, footerBox);
        }
    }
    p.setFont(oldFont);
}

/**************************************************************
 *           Print Day
 **************************************************************/

CalPrintDay::CalPrintDay() : CalPrintPluginBase()
{
}

CalPrintDay::~CalPrintDay()
{
}

QWidget *CalPrintDay::createConfigWidget(QWidget *w)
{
    return new CalPrintDayConfig(w);
}

void CalPrintDay::readSettingsWidget()
{
    CalPrintDayConfig *cfg
        = dynamic_cast<CalPrintDayConfig *>((QWidget *)mConfigWidget);
    if (cfg) {
        mFromDate = cfg->mFromDate->date();
        mToDate = cfg->mToDate->date();

        if (cfg->mPrintTypeFilofax->isChecked()) {
            mDayPrintType = Filofax;
        } else if (cfg->mPrintTypeTimetable->isChecked()) {
            mDayPrintType = Timetable;
        } else {
            mDayPrintType = SingleTimetable;
        }

        mStartTime = cfg->mFromTime->time();
        mEndTime = cfg->mToTime->time();
        mIncludeAllEvents = cfg->mIncludeAllEvents->isChecked();

        mIncludeDescription = cfg->mIncludeDescription->isChecked();
        mSingleLineLimit = cfg->mSingleLineLimit->isChecked();
        mIncludeTodos = cfg->mIncludeTodos->isChecked();
        mUseColors = cfg->mColors->isChecked();
        mPrintFooter = cfg->mPrintFooter->isChecked();
        mShowNoteLines = cfg->mShowNoteLines->isChecked();
        mExcludeTime = cfg->mExcludeTime->isChecked();
        mExcludeConfidential = cfg->mExcludeConfidential->isChecked();
        mExcludePrivate = cfg->mExcludePrivate->isChecked();
    }
}

void CalPrintDay::setSettingsWidget()
{
    CalPrintDayConfig *cfg = dynamic_cast<CalPrintDayConfig *>((QWidget *)mConfigWidget);
    if (cfg) {
        cfg->mFromDate->setDate(mFromDate);
        cfg->mToDate->setDate(mToDate);

        cfg->mPrintTypeFilofax->setChecked(mDayPrintType == Filofax);
        cfg->mPrintTypeTimetable->setChecked(mDayPrintType == Timetable);
        cfg->mPrintTypeSingleTimetable->setChecked(mDayPrintType == SingleTimetable);

        cfg->mFromTime->setTime(mStartTime);
        cfg->mToTime->setTime(mEndTime);
        cfg->mIncludeAllEvents->setChecked(mIncludeAllEvents);

        cfg->mIncludeDescription->setChecked(mIncludeDescription);
        cfg->mSingleLineLimit->setChecked(mSingleLineLimit);
        cfg->mIncludeTodos->setChecked(mIncludeTodos);
        cfg->mColors->setChecked(mUseColors);
        cfg->mPrintFooter->setChecked(mPrintFooter);
        cfg->mShowNoteLines->setChecked(mShowNoteLines);
        cfg->mExcludeTime->setChecked(mExcludeTime);
        cfg->mExcludeConfidential->setChecked(mExcludeConfidential);
        cfg->mExcludePrivate->setChecked(mExcludePrivate);
    }
}

void CalPrintDay::loadConfig()
{
    if (mConfig) {
        KConfigGroup grp(mConfig, groupName());
        QDate dt = QDate::currentDate(); // any valid QDate will do
        QTime tm1(dayStart());
        QDateTime startTm(dt, tm1);
        QDateTime endTm(dt, tm1.addSecs(12 * 60 * 60));
        mStartTime = grp.readEntry("Start time", startTm).time();
        mEndTime = grp.readEntry("End time", endTm).time();
        mIncludeDescription = grp.readEntry("Include description", false);
        mIncludeTodos = grp.readEntry("Include todos", false);
        mIncludeAllEvents = grp.readEntry("Include all events", false);
        mDayPrintType = static_cast<eDayPrintType>(grp.readEntry("Print type", static_cast<int>(Timetable)));
        mSingleLineLimit = grp.readEntry("Single line limit", false);
        mShowNoteLines = grp.readEntry("Note Lines", false);
        mExcludeTime = grp.readEntry("Exclude time", false);
        mExcludeConfidential = grp.readEntry("Exclude confidential", true);
        mExcludePrivate = grp.readEntry("Exclude private", true);
    }
    setSettingsWidget();
}

void CalPrintDay::saveConfig()
{
    readSettingsWidget();
    if (mConfig) {
        KConfigGroup grp(mConfig, groupName());
        QDateTime dt = QDateTime::currentDateTime(); // any valid QDateTime will do
        dt.setTime(mStartTime);
        grp.writeEntry("Start time", dt);
        dt.setTime(mEndTime);
        grp.writeEntry("End time", dt);
        grp.writeEntry("Include description", mIncludeDescription);
        grp.writeEntry("Include todos", mIncludeTodos);
        grp.writeEntry("Include all events", mIncludeAllEvents);
        grp.writeEntry("Print type", int(mDayPrintType));
        grp.writeEntry("Single line limit", mSingleLineLimit);
        grp.writeEntry("Note Lines", mShowNoteLines);
        grp.writeEntry("Exclude time", mExcludeTime);
        grp.writeEntry("Exclude confidential", mExcludeConfidential);
        grp.writeEntry("Exclude private", mExcludePrivate);
    }
}

void CalPrintDay::setDateRange(const QDate &from, const QDate &to)
{
    CalPrintPluginBase::setDateRange(from, to);
    CalPrintDayConfig *cfg
        = dynamic_cast<CalPrintDayConfig *>((QWidget *)mConfigWidget);
    if (cfg) {
        cfg->mFromDate->setDate(from);
        cfg->mToDate->setDate(to);
    }
}

void CalPrintDay::print(QPainter &p, int width, int height)
{
    QDate curDay(mFromDate);

    QRect headerBox(0, 0, width, headerHeight());
    QRect footerBox(0, height - footerHeight(), width, footerHeight());
    height -= footerHeight();

    auto local = QLocale::system();

    switch (mDayPrintType) {
    case Filofax:
    case SingleTimetable:
    {
        QRect daysBox(headerBox);
        daysBox.setTop(headerBox.bottom() + padding());
        daysBox.setBottom(height);
        QString line1 = local.toString(mFromDate, QLocale::ShortFormat);
        QString line2 = local.toString(mToDate, QLocale::ShortFormat);
        QString title;
        if (orientation() == QPrinter::Landscape) {
            title = i18nc("date from-to", "%1 - %2", line1, line2);
        } else {
            title = i18nc("date from-\nto", "%1 -\n%2", line1, line2);
        }
        drawHeader(p, title, mFromDate, QDate(), headerBox);
        if (mDayPrintType == Filofax) {
            drawDays(p, mFromDate, mToDate, mStartTime, mEndTime, daysBox,
                     mSingleLineLimit, mShowNoteLines,
                     mIncludeDescription, mExcludeConfidential, mExcludePrivate);
        } else if (mDayPrintType == SingleTimetable) {
            drawTimeTable(p, mFromDate, mToDate,
                          mIncludeAllEvents, mStartTime, mEndTime, daysBox,
                          mIncludeDescription, mExcludeTime, mExcludeConfidential,
                          mExcludePrivate);
        }
        if (mPrintFooter) {
            drawFooter(p, footerBox);
        }
        break;
    }

    case Timetable:
    default:
        do {
            QTime curStartTime(mStartTime);
            QTime curEndTime(mEndTime);

            // For an invalid time range, simply show one hour, starting at the hour
            // before the given start time
            if (curEndTime <= curStartTime) {
                curStartTime = QTime(curStartTime.hour(), 0, 0);
                curEndTime = curStartTime.addSecs(3600);
            }

            drawHeader(p, local.toString(curDay, QLocale::ShortFormat), curDay, QDate(), headerBox);
            KCalendarCore::Event::List eventList = mCalendar->events(curDay, QTimeZone::systemTimeZone(),
                                                                     KCalendarCore::EventSortStartDate,
                                                                     KCalendarCore::SortDirectionAscending);

            // split out the all day events as they will be printed in a separate box
            KCalendarCore::Event::List alldayEvents, timedEvents;
            for (const KCalendarCore::Event::Ptr &event : qAsConst(eventList)) {
                if (event->allDay()) {
                    alldayEvents.append(event);
                } else {
                    timedEvents.append(event);
                }
            }

            int fontSize = 11;
            QFont textFont(QStringLiteral("sans-serif"), fontSize, QFont::Normal);
            p.setFont(textFont);
            int lineSpacing = p.fontMetrics().lineSpacing();

            int maxAllDayEvents = 8; // the max we allow to be printed, sorry.
            int allDayHeight = qMin(alldayEvents.count(), maxAllDayEvents) * lineSpacing;
            allDayHeight = qMax(allDayHeight, (5 * lineSpacing)) + (2 * padding());
            QRect allDayBox(TIMELINE_WIDTH + padding(), headerBox.bottom() + padding(),
                            width - TIMELINE_WIDTH - padding(), allDayHeight);
            if (!alldayEvents.isEmpty()) {
                // draw the side bar for all-day events
                QFont oldFont(p.font());
                p.setFont(QFont(QStringLiteral("sans-serif"), 9, QFont::Normal));
                drawVerticalBox(p,
                                BOX_BORDER_WIDTH,
                                QRect(0, headerBox.bottom() + padding(), TIMELINE_WIDTH,
                                      allDayHeight),
                                i18n("Today's Events"),
                                Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextWordWrap);
                p.setFont(oldFont);

                // now draw at most maxAllDayEvents in the all-day box
                drawBox(p, BOX_BORDER_WIDTH, allDayBox);

                QRect eventBox(allDayBox);
                eventBox.setLeft(TIMELINE_WIDTH + (2 * padding()));
                eventBox.setTop(eventBox.top() + padding());
                eventBox.setBottom(eventBox.top() + lineSpacing);
                int count = 0;
                for (const KCalendarCore::Event::Ptr &event : qAsConst(alldayEvents)) {
                    if (count == maxAllDayEvents) {
                        break;
                    }
                    count++;
                    QString str;
                    if (event->location().isEmpty()) {
                        str = cleanStr(event->summary());
                    } else {
                        str = i18nc("summary, location", "%1, %2",
                                    cleanStr(event->summary()), cleanStr(event->location()));
                    }
                    printEventString(p, eventBox, str);
                    eventBox.setTop(eventBox.bottom());
                    eventBox.setBottom(eventBox.top() + lineSpacing);
                }
            } else {
                allDayBox.setBottom(headerBox.bottom());
            }

            QRect dayBox(allDayBox);
            dayBox.setTop(allDayBox.bottom() + padding());
            dayBox.setBottom(height);
            QList<QDate> workDays = CalendarSupport::workDays(curDay, curDay);
            drawAgendaDayBox(p, timedEvents, curDay, mIncludeAllEvents,
                             curStartTime, curEndTime, dayBox,
                             mIncludeDescription, mExcludeTime,
                             mExcludeConfidential, mExcludePrivate,
                             workDays);

            QRect tlBox(dayBox);
            tlBox.setLeft(0);
            tlBox.setWidth(TIMELINE_WIDTH);
            drawTimeLine(p, curStartTime, curEndTime, tlBox);

            if (mPrintFooter) {
                drawFooter(p, footerBox);
            }

            curDay = curDay.addDays(1);
            if (curDay <= mToDate) {
                mPrinter->newPage();
            }
        } while (curDay <= mToDate);
    } //switch
}

/**************************************************************
 *           Print Week
 **************************************************************/

CalPrintWeek::CalPrintWeek() : CalPrintPluginBase()
{
}

CalPrintWeek::~CalPrintWeek()
{
}

QWidget *CalPrintWeek::createConfigWidget(QWidget *w)
{
    return new CalPrintWeekConfig(w);
}

void CalPrintWeek::readSettingsWidget()
{
    CalPrintWeekConfig *cfg = dynamic_cast<CalPrintWeekConfig *>((QWidget *)mConfigWidget);
    if (cfg) {
        mFromDate = cfg->mFromDate->date();
        mToDate = cfg->mToDate->date();

        if (cfg->mPrintTypeFilofax->isChecked()) {
            mWeekPrintType = Filofax;
        } else if (cfg->mPrintTypeTimetable->isChecked()) {
            mWeekPrintType = Timetable;
        } else if (cfg->mPrintTypeSplitWeek->isChecked()) {
            mWeekPrintType = SplitWeek;
        } else {
            mWeekPrintType = Timetable;
        }

        mStartTime = cfg->mFromTime->time();
        mEndTime = cfg->mToTime->time();

        mShowNoteLines = cfg->mShowNoteLines->isChecked();
        mSingleLineLimit = cfg->mSingleLineLimit->isChecked();
        mIncludeTodos = cfg->mIncludeTodos->isChecked();
        mUseColors = cfg->mColors->isChecked();
        mPrintFooter = cfg->mPrintFooter->isChecked();
        mIncludeDescription = cfg->mIncludeDescription->isChecked();
        mExcludeTime = cfg->mExcludeTime->isChecked();
        mExcludeConfidential = cfg->mExcludeConfidential->isChecked();
        mExcludePrivate = cfg->mExcludePrivate->isChecked();
    }
}

void CalPrintWeek::setSettingsWidget()
{
    CalPrintWeekConfig *cfg = dynamic_cast<CalPrintWeekConfig *>((QWidget *)mConfigWidget);
    if (cfg) {
        cfg->mFromDate->setDate(mFromDate);
        cfg->mToDate->setDate(mToDate);

        cfg->mPrintTypeFilofax->setChecked(mWeekPrintType == Filofax);
        cfg->mPrintTypeTimetable->setChecked(mWeekPrintType == Timetable);
        cfg->mPrintTypeSplitWeek->setChecked(mWeekPrintType == SplitWeek);

        cfg->mFromTime->setTime(mStartTime);
        cfg->mToTime->setTime(mEndTime);

        cfg->mShowNoteLines->setChecked(mShowNoteLines);
        cfg->mSingleLineLimit->setChecked(mSingleLineLimit);
        cfg->mIncludeTodos->setChecked(mIncludeTodos);
        cfg->mColors->setChecked(mUseColors);
        cfg->mPrintFooter->setChecked(mPrintFooter);
        cfg->mIncludeDescription->setChecked(mIncludeDescription);
        cfg->mExcludeTime->setChecked(mExcludeTime);
        cfg->mExcludeConfidential->setChecked(mExcludeConfidential);
        cfg->mExcludePrivate->setChecked(mExcludePrivate);
    }
}

void CalPrintWeek::loadConfig()
{
    if (mConfig) {
        KConfigGroup grp(mConfig, groupName());
        QDate dt = QDate::currentDate(); // any valid QDate will do
        QTime tm1(dayStart());
        QDateTime startTm(dt, tm1);
        QDateTime endTm(dt, tm1.addSecs(43200));
        mStartTime = grp.readEntry("Start time", startTm).time();
        mEndTime = grp.readEntry("End time", endTm).time();
        mShowNoteLines = grp.readEntry("Note Lines", false);
        mSingleLineLimit = grp.readEntry("Single line limit", false);
        mIncludeTodos = grp.readEntry("Include todos", false);
        mWeekPrintType = (eWeekPrintType)(grp.readEntry("Print type", (int)Filofax));
        mIncludeDescription = grp.readEntry("Include Description", false);
        mExcludeTime = grp.readEntry("Exclude Time", false);
        mExcludeConfidential = grp.readEntry("Exclude confidential", true);
        mExcludePrivate = grp.readEntry("Exclude private", true);
    }
    setSettingsWidget();
}

void CalPrintWeek::saveConfig()
{
    readSettingsWidget();
    if (mConfig) {
        KConfigGroup grp(mConfig, groupName());
        QDateTime dt = QDateTime::currentDateTime(); // any valid QDateTime will do
        dt.setTime(mStartTime);
        grp.writeEntry("Start time", dt);
        dt.setTime(mEndTime);
        grp.writeEntry("End time", dt);
        grp.writeEntry("Note Lines", mShowNoteLines);
        grp.writeEntry("Single line limit", mSingleLineLimit);
        grp.writeEntry("Include todos", mIncludeTodos);
        grp.writeEntry("Print type", int(mWeekPrintType));
        grp.writeEntry("Include Description", mIncludeDescription);
        grp.writeEntry("Exclude Time", mExcludeTime);
        grp.writeEntry("Exclude confidential", mExcludeConfidential);
        grp.writeEntry("Exclude private", mExcludePrivate);
    }
}

QPrinter::Orientation CalPrintWeek::defaultOrientation() const
{
    if (mWeekPrintType == Filofax) {
        return QPrinter::Portrait;
    } else if (mWeekPrintType == SplitWeek) {
        return QPrinter::Portrait;
    } else {
        return QPrinter::Landscape;
    }
}

void CalPrintWeek::setDateRange(const QDate &from, const QDate &to)
{
    CalPrintPluginBase::setDateRange(from, to);
    CalPrintWeekConfig *cfg
        = dynamic_cast<CalPrintWeekConfig *>((QWidget *)mConfigWidget);
    if (cfg) {
        cfg->mFromDate->setDate(from);
        cfg->mToDate->setDate(to);
    }
}

void CalPrintWeek::print(QPainter &p, int width, int height)
{
    QDate curWeek, fromWeek, toWeek;

    // correct begin and end to first and last day of week
    int weekdayCol = weekdayColumn(mFromDate.dayOfWeek());
    fromWeek = mFromDate.addDays(-weekdayCol);
    weekdayCol = weekdayColumn(mToDate.dayOfWeek());
    toWeek = mToDate.addDays(6 - weekdayCol);

    curWeek = fromWeek.addDays(6);
    auto local = QLocale::system();

    QString line1, line2, title;
    QRect headerBox(0, 0, width, headerHeight());
    QRect footerBox(0, height - footerHeight(), width, footerHeight());
    height -= footerHeight();

    QRect weekBox(headerBox);
    weekBox.setTop(headerBox.bottom() + padding());
    weekBox.setBottom(height);

    switch (mWeekPrintType) {
    case Filofax:
        do {
            line1 = local.toString(curWeek.addDays(-6), QLocale::ShortFormat);
            line2 = local.toString(curWeek, QLocale::ShortFormat);
            if (orientation() == QPrinter::Landscape) {
                title = i18nc("date from-to", "%1 - %2", line1, line2);
            } else {
                title = i18nc("date from-\nto", "%1 -\n%2", line1, line2);
            }
            drawHeader(p, title, curWeek.addDays(-6), QDate(), headerBox);

            drawWeek(p, curWeek, mStartTime, mEndTime, weekBox, mSingleLineLimit,
                     mShowNoteLines, mIncludeDescription,
                     mExcludeConfidential, mExcludePrivate);

            if (mPrintFooter) {
                drawFooter(p, footerBox);
            }

            curWeek = curWeek.addDays(7);
            if (curWeek <= toWeek) {
                mPrinter->newPage();
            }
        } while (curWeek <= toWeek);
        break;

    case Timetable:
    default:
        do {
            line1 = local.toString(curWeek.addDays(-6), QLocale::ShortFormat);
            line2 = local.toString(curWeek, QLocale::ShortFormat);
            if (orientation() == QPrinter::Landscape) {
                title = i18nc("date from - to (week number)", "%1 - %2 (Week %3)",
                              line1, line2, curWeek.weekNumber());
            } else {
                title = i18nc("date from -\nto (week number)", "%1 -\n%2 (Week %3)",
                              line1, line2, curWeek.weekNumber());
            }
            drawHeader(p, title, curWeek, QDate(), headerBox);

            drawTimeTable(p, fromWeek, curWeek,
                          false, mStartTime, mEndTime, weekBox,
                          mIncludeDescription, mExcludeTime, mExcludeConfidential,
                          mExcludePrivate);

            if (mPrintFooter) {
                drawFooter(p, footerBox);
            }

            fromWeek = fromWeek.addDays(7);
            curWeek = fromWeek.addDays(6);
            if (curWeek <= toWeek) {
                mPrinter->newPage();
            }
        } while (curWeek <= toWeek);
        break;

    case SplitWeek:
    {
        QRect weekBox1(weekBox);
        // On the left side there are four days (mo-th) plus the timeline,
        // on the right there are only three days (fr-su) plus the timeline. Don't
        // use the whole width, but rather give them the same width as on the left.
        weekBox1.setRight(int((width - TIMELINE_WIDTH) * 3. / 4. + TIMELINE_WIDTH));
        do {
            QDate endLeft(fromWeek.addDays(3));
            int hh = headerHeight();

            drawSplitHeaderRight(p, fromWeek, curWeek, QDate(), width, hh);
            drawTimeTable(p, fromWeek, endLeft,
                          false, mStartTime, mEndTime, weekBox,
                          mIncludeDescription, mExcludeTime,
                          mExcludeConfidential, mExcludePrivate);
            if (mPrintFooter) {
                drawFooter(p, weekBox1);
            }
            mPrinter->newPage();
            drawSplitHeaderRight(p, fromWeek, curWeek, QDate(), width, hh);
            drawTimeTable(p, endLeft.addDays(1), curWeek,
                          false, mStartTime, mEndTime, weekBox1,
                          mIncludeDescription, mExcludeTime,
                          mExcludeConfidential, mExcludePrivate);

            if (mPrintFooter) {
                drawFooter(p, footerBox);
            }

            fromWeek = fromWeek.addDays(7);
            curWeek = fromWeek.addDays(6);
            if (curWeek <= toWeek) {
                mPrinter->newPage();
            }
        } while (curWeek <= toWeek);
        break;
    }
    }
}

/**************************************************************
 *           Print Month
 **************************************************************/

CalPrintMonth::CalPrintMonth() : CalPrintPluginBase()
{
}

CalPrintMonth::~CalPrintMonth()
{
}

QWidget *CalPrintMonth::createConfigWidget(QWidget *w)
{
    return new CalPrintMonthConfig(w);
}

void CalPrintMonth::readSettingsWidget()
{
    CalPrintMonthConfig *cfg = dynamic_cast<CalPrintMonthConfig *>((QWidget *)mConfigWidget);

    if (cfg) {
        mFromDate = QDate(cfg->mFromYear->value(), cfg->mFromMonth->currentIndex() + 1, 1);
        mToDate = QDate(cfg->mToYear->value(), cfg->mToMonth->currentIndex() + 1, 1);

        mWeekNumbers = cfg->mWeekNumbers->isChecked();
        mRecurDaily = cfg->mRecurDaily->isChecked();
        mRecurWeekly = cfg->mRecurWeekly->isChecked();
        mIncludeTodos = cfg->mIncludeTodos->isChecked();
        mShowNoteLines = cfg->mShowNoteLines->isChecked();
        mSingleLineLimit = cfg->mSingleLineLimit->isChecked();
        mUseColors = cfg->mColors->isChecked();
        mPrintFooter = cfg->mPrintFooter->isChecked();
        mIncludeDescription = cfg->mIncludeDescription->isChecked();
        mExcludeConfidential = cfg->mExcludeConfidential->isChecked();
        mExcludePrivate = cfg->mExcludePrivate->isChecked();
    }
}

void CalPrintMonth::setSettingsWidget()
{
    CalPrintMonthConfig *cfg = dynamic_cast<CalPrintMonthConfig *>((QWidget *)mConfigWidget);

    if (cfg) {
        setDateRange(mFromDate, mToDate);

        cfg->mWeekNumbers->setChecked(mWeekNumbers);
        cfg->mRecurDaily->setChecked(mRecurDaily);
        cfg->mRecurWeekly->setChecked(mRecurWeekly);
        cfg->mIncludeTodos->setChecked(mIncludeTodos);
        cfg->mShowNoteLines->setChecked(mShowNoteLines);
        cfg->mSingleLineLimit->setChecked(mSingleLineLimit);
        cfg->mColors->setChecked(mUseColors);
        cfg->mPrintFooter->setChecked(mPrintFooter);
        cfg->mIncludeDescription->setChecked(mIncludeDescription);
        cfg->mExcludeConfidential->setChecked(mExcludeConfidential);
        cfg->mExcludePrivate->setChecked(mExcludePrivate);
    }
}

void CalPrintMonth::loadConfig()
{
    if (mConfig) {
        KConfigGroup grp(mConfig, groupName());
        mWeekNumbers = grp.readEntry("Print week numbers", true);
        mRecurDaily = grp.readEntry("Print daily incidences", true);
        mRecurWeekly = grp.readEntry("Print weekly incidences", true);
        mIncludeTodos = grp.readEntry("Include todos", false);
        mSingleLineLimit = grp.readEntry("Single line limit", false);
        mShowNoteLines = grp.readEntry("Note Lines", false);
        mIncludeDescription = grp.readEntry("Include description", false);
        mExcludeConfidential = grp.readEntry("Exclude confidential", true);
        mExcludePrivate = grp.readEntry("Exclude private", true);
    }
    setSettingsWidget();
}

void CalPrintMonth::saveConfig()
{
    readSettingsWidget();
    if (mConfig) {
        KConfigGroup grp(mConfig, groupName());
        grp.writeEntry("Print week numbers", mWeekNumbers);
        grp.writeEntry("Print daily incidences", mRecurDaily);
        grp.writeEntry("Print weekly incidences", mRecurWeekly);
        grp.writeEntry("Include todos", mIncludeTodos);
        grp.writeEntry("Single line limit", mSingleLineLimit);
        grp.writeEntry("Note Lines", mShowNoteLines);
        grp.writeEntry("Include description", mIncludeDescription);
        grp.writeEntry("Exclude confidential", mExcludeConfidential);
        grp.writeEntry("Exclude private", mExcludePrivate);
    }
}

void CalPrintMonth::setDateRange(const QDate &from, const QDate &to)
{
    CalPrintPluginBase::setDateRange(from, to);
    CalPrintMonthConfig *cfg
        = dynamic_cast<CalPrintMonthConfig *>((QWidget *)mConfigWidget);
    if (cfg) {
        cfg->mFromMonth->clear();
        for (int i = 0; i < 12; ++i) {
            cfg->mFromMonth->addItem(QLocale().monthName(i + 1, QLocale::LongFormat));
        }
        cfg->mToMonth->clear();
        for (int i = 0; i < 12; ++i) {
            cfg->mToMonth->addItem(QLocale().monthName(i + 1, QLocale::LongFormat));
        }
        cfg->mFromMonth->setCurrentIndex(from.month() - 1);
        cfg->mFromYear->setValue(to.year());
        cfg->mToMonth->setCurrentIndex(mToDate.month() - 1);
        cfg->mToYear->setValue(mToDate.year());
    }
}

void CalPrintMonth::print(QPainter &p, int width, int height)
{
    QDate curMonth, fromMonth, toMonth;

    fromMonth = mFromDate.addDays(-(mFromDate.day() - 1));
    toMonth = mToDate.addDays(mToDate.daysInMonth() - mToDate.day());

    curMonth = fromMonth;

    QRect headerBox(0, 0, width, headerHeight());
    QRect footerBox(0, height - footerHeight(), width, footerHeight());
    height -= footerHeight();

    QRect monthBox(0, 0, width, height);
    monthBox.setTop(headerBox.bottom() + padding());

    do {
        QString title(i18nc("monthname year", "%1 %2",
                            QLocale::system().monthName(curMonth.month()),
                            QString::number(curMonth.year())));
        QDate tmp(fromMonth);
        int weekdayCol = weekdayColumn(tmp.dayOfWeek());
        tmp = tmp.addDays(-weekdayCol);

        drawHeader(p, title, curMonth.addMonths(-1), curMonth.addMonths(1),
                   headerBox);
        drawMonthTable(p,
                       curMonth, QTime(), QTime(),
                       mWeekNumbers,
                       mRecurDaily, mRecurWeekly,
                       mSingleLineLimit, mShowNoteLines, mIncludeDescription,
                       mExcludeConfidential, mExcludePrivate, monthBox);

        if (mPrintFooter) {
            drawFooter(p, footerBox);
        }

        curMonth = curMonth.addDays(curMonth.daysInMonth());
        if (curMonth <= toMonth) {
            mPrinter->newPage();
        }
    } while (curMonth <= toMonth);
}

/**************************************************************
 *           Print Todos
 **************************************************************/

CalPrintTodos::CalPrintTodos() : CalPrintPluginBase()
{
    mTodoSortField = TodoFieldUnset;
    mTodoSortDirection = TodoDirectionUnset;
}

CalPrintTodos::~CalPrintTodos()
{
}

QWidget *CalPrintTodos::createConfigWidget(QWidget *w)
{
    return new CalPrintTodoConfig(w);
}

void CalPrintTodos::readSettingsWidget()
{
    CalPrintTodoConfig *cfg = dynamic_cast<CalPrintTodoConfig *>((QWidget *)mConfigWidget);

    if (cfg) {
        mPageTitle = cfg->mTitle->text();

        if (cfg->mPrintAll->isChecked()) {
            mTodoPrintType = TodosAll;
        } else if (cfg->mPrintUnfinished->isChecked()) {
            mTodoPrintType = TodosUnfinished;
        } else if (cfg->mPrintDueRange->isChecked()) {
            mTodoPrintType = TodosDueRange;
        } else {
            mTodoPrintType = TodosAll;
        }

        mFromDate = cfg->mFromDate->date();
        mToDate = cfg->mToDate->date();

        mIncludeDescription = cfg->mDescription->isChecked();
        mIncludePriority = cfg->mPriority->isChecked();
        mIncludeDueDate = cfg->mDueDate->isChecked();
        mIncludePercentComplete = cfg->mPercentComplete->isChecked();
        mConnectSubTodos = cfg->mConnectSubTodos->isChecked();
        mStrikeOutCompleted = cfg->mStrikeOutCompleted->isChecked();
        mExcludeConfidential = cfg->mExcludeConfidential->isChecked();
        mExcludePrivate = cfg->mExcludePrivate->isChecked();

        mTodoSortField = (eTodoSortField)cfg->mSortField->currentIndex();
        mTodoSortDirection = (eTodoSortDirection)cfg->mSortDirection->currentIndex();

        mPrintFooter = cfg->mPrintFooter->isChecked();
    }
}

void CalPrintTodos::setSettingsWidget()
{
    CalPrintTodoConfig *cfg
        = dynamic_cast<CalPrintTodoConfig *>((QWidget *)mConfigWidget);
    if (cfg) {
        cfg->mTitle->setText(mPageTitle);

        cfg->mPrintAll->setChecked(mTodoPrintType == TodosAll);
        cfg->mPrintUnfinished->setChecked(mTodoPrintType == TodosUnfinished);
        cfg->mPrintDueRange->setChecked(mTodoPrintType == TodosDueRange);

        cfg->mFromDate->setDate(mFromDate);
        cfg->mToDate->setDate(mToDate);

        cfg->mDescription->setChecked(mIncludeDescription);
        cfg->mPriority->setChecked(mIncludePriority);
        cfg->mDueDate->setChecked(mIncludeDueDate);
        cfg->mPercentComplete->setChecked(mIncludePercentComplete);
        cfg->mConnectSubTodos->setChecked(mConnectSubTodos);
        cfg->mStrikeOutCompleted->setChecked(mStrikeOutCompleted);
        cfg->mExcludeConfidential->setChecked(mExcludeConfidential);
        cfg->mExcludePrivate->setChecked(mExcludePrivate);

        if (mTodoSortField != TodoFieldUnset) {
            // do not insert if already done so.
            cfg->mSortField->addItem(i18nc("@option sort by title", "Title"));
            cfg->mSortField->addItem(i18nc("@option sort by start date/time", "Start Date"));
            cfg->mSortField->addItem(i18nc("@option sort by due date/time", "Due Date"));
            cfg->mSortField->addItem(i18nc("@option sort by priority", "Priority"));
            cfg->mSortField->addItem(i18nc("@option sort by percent completed",
                                           "Percent Complete"));
            cfg->mSortField->setCurrentIndex(mTodoSortField);
        }

        if (mTodoSortDirection != TodoDirectionUnset) {
            // do not insert if already done so.
            cfg->mSortDirection->addItem(i18nc("@option sort in increasing order", "Ascending"));
            cfg->mSortDirection->addItem(i18nc("@option sort in descreasing order", "Descending"));
            cfg->mSortDirection->setCurrentIndex(mTodoSortDirection);
        }

        cfg->mPrintFooter->setChecked(mPrintFooter);
    }
}

void CalPrintTodos::loadConfig()
{
    if (mConfig) {
        KConfigGroup grp(mConfig, groupName());
        mPageTitle = grp.readEntry("Page title", i18n("To-do list"));
        mTodoPrintType = (eTodoPrintType)grp.readEntry("Print type", static_cast<int>(TodosAll));
        mIncludeDescription = grp.readEntry("Include description", true);
        mIncludePriority = grp.readEntry("Include priority", true);
        mIncludeDueDate = grp.readEntry("Include due date", true);
        mIncludePercentComplete = grp.readEntry("Include percentage completed", true);
        mConnectSubTodos = grp.readEntry("Connect subtodos", true);
        mStrikeOutCompleted = grp.readEntry("Strike out completed summaries", true);
        mTodoSortField
            = (eTodoSortField)grp.readEntry("Sort field", static_cast<int>(TodoFieldSummary));
        mTodoSortDirection
            = (eTodoSortDirection)grp.readEntry("Sort direction", static_cast<int>(TodoDirectionAscending));
        mExcludeConfidential = grp.readEntry("Exclude confidential", true);
        mExcludePrivate = grp.readEntry("Exclude private", true);
    }
    setSettingsWidget();
}

void CalPrintTodos::saveConfig()
{
    readSettingsWidget();
    if (mConfig) {
        KConfigGroup grp(mConfig, groupName());
        grp.writeEntry("Page title", mPageTitle);
        grp.writeEntry("Print type", int(mTodoPrintType));
        grp.writeEntry("Include description", mIncludeDescription);
        grp.writeEntry("Include priority", mIncludePriority);
        grp.writeEntry("Include due date", mIncludeDueDate);
        grp.writeEntry("Include percentage completed", mIncludePercentComplete);
        grp.writeEntry("Connect subtodos", mConnectSubTodos);
        grp.writeEntry("Strike out completed summaries", mStrikeOutCompleted);
        grp.writeEntry("Sort field", static_cast<int>(mTodoSortField));
        grp.writeEntry("Sort direction", static_cast<int>(mTodoSortDirection));
        grp.writeEntry("Exclude confidential", mExcludeConfidential);
        grp.writeEntry("Exclude private", mExcludePrivate);
    }
}

void CalPrintTodos::print(QPainter &p, int width, int height)
{
    // TODO: Find a good way to guarantee a nicely designed output
    int pospriority = 0;
    int possummary = 100;
    int posdue = width - 65;
    int poscomplete = posdue - 70; //Complete column is to right of the Due column

    QRect headerBox(0, 0, width, headerHeight());
    QRect footerBox(0, height - footerHeight(), width, footerHeight());
    height -= footerHeight();

    // Draw the First Page Header
    drawHeader(p, mPageTitle, mFromDate, QDate(), headerBox);

    // Draw the Column Headers
    int mCurrentLinePos = headerHeight() + 5;
    QString outStr;
    QFont oldFont(p.font());

    p.setFont(QFont(QStringLiteral("sans-serif"), 9, QFont::Bold));
    int lineSpacing = p.fontMetrics().lineSpacing();
    mCurrentLinePos += lineSpacing;
    if (mIncludePriority) {
        outStr += i18n("Priority");
        p.drawText(pospriority, mCurrentLinePos - 2, outStr);
    } else {
        pospriority = -1;
    }

    outStr.truncate(0);
    outStr += i18nc("@label to-do summary", "Title");
    p.drawText(possummary, mCurrentLinePos - 2, outStr);

    if (mIncludePercentComplete) {
        if (!mIncludeDueDate) {   //move Complete column to the right
            poscomplete = posdue; //if not print the Due Date column
        }
        outStr.truncate(0);
        outStr += i18nc("@label to-do percentage complete", "Complete");
        p.drawText(poscomplete, mCurrentLinePos - 2, outStr);
    } else {
        poscomplete = -1;
    }

    if (mIncludeDueDate) {
        outStr.truncate(0);
        outStr += i18nc("@label to-do due date", "Due");
        p.drawText(posdue, mCurrentLinePos - 2, outStr);
    } else {
        posdue = -1;
    }

    p.setFont(QFont(QStringLiteral("sans-serif"), 10));
    //fontHeight = p.fontMetrics().height();

    KCalendarCore::Todo::List todoList;
    KCalendarCore::Todo::List tempList;

    KCalendarCore::SortDirection sortDirection = KCalendarCore::SortDirectionAscending;
    switch (mTodoSortDirection) {
    case TodoDirectionAscending:
        sortDirection = KCalendarCore::SortDirectionAscending;
        break;
    case TodoDirectionDescending:
        sortDirection = KCalendarCore::SortDirectionDescending;
        break;
    case TodoDirectionUnset:
        break;
    }

    KCalendarCore::TodoSortField sortField = KCalendarCore::TodoSortSummary;
    switch (mTodoSortField) {
    case TodoFieldSummary:
        sortField = KCalendarCore::TodoSortSummary;
        break;
    case TodoFieldStartDate:
        sortField = KCalendarCore::TodoSortStartDate;
        break;
    case TodoFieldDueDate:
        sortField = KCalendarCore::TodoSortDueDate;
        break;
    case TodoFieldPriority:
        sortField = KCalendarCore::TodoSortPriority;
        break;
    case TodoFieldPercentComplete:
        sortField = KCalendarCore::TodoSortPercentComplete;
        break;
    case TodoFieldUnset:
        break;
    }

    // Create list of to-dos which will be printed
    todoList = mCalendar->todos(sortField, sortDirection);
    switch (mTodoPrintType) {
    case TodosAll:
        break;
    case TodosUnfinished:
        for (const KCalendarCore::Todo::Ptr &todo : qAsConst(todoList)) {
            Q_ASSERT(todo);
            if (!todo->isCompleted()) {
                tempList.append(todo);
            }
        }
        todoList = tempList;
        break;
    case TodosDueRange:
        for (const KCalendarCore::Todo::Ptr &todo : qAsConst(todoList)) {
            Q_ASSERT(todo);
            if (todo->hasDueDate()) {
                if (todo->dtDue().date() >= mFromDate && todo->dtDue().date() <= mToDate) {
                    tempList.append(todo);
                }
            } else {
                tempList.append(todo);
            }
        }
        todoList = tempList;
        break;
    }

    // Print to-dos
    int count = 0;
    for (const KCalendarCore::Todo::Ptr &todo : qAsConst(todoList)) {
        if ((mExcludeConfidential && todo->secrecy() == KCalendarCore::Incidence::SecrecyConfidential)
            || (mExcludePrivate && todo->secrecy() == KCalendarCore::Incidence::SecrecyPrivate)) {
            continue;
        }
        // Skip sub-to-dos. They will be printed recursively in drawTodo()
        if (todo->relatedTo().isEmpty()) {   //review(AKONADI_PORT)
            count++;
            drawTodo(count, todo, p,
                     sortField, sortDirection,
                     mConnectSubTodos,
                     mStrikeOutCompleted, mIncludeDescription,
                     pospriority, possummary, posdue, poscomplete,
                     0, 0, mCurrentLinePos, width, height, todoList, nullptr,
                     mExcludeConfidential, mExcludePrivate);
        }
    }

    if (mPrintFooter) {
        drawFooter(p, footerBox);
    }
    p.setFont(oldFont);
}
