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

0001 /*
0002   SPDX-FileCopyrightText: 1998 Preston Brown <pbrown@kde.org>
0003   SPDX-FileCopyrightText: 2003 Reinhold Kainhofer <reinhold@kainhofer.com>
0004   SPDX-FileCopyrightText: 2008 Ron Goodheart <rong.dev@gmail.com>
0005   SPDX-FileCopyrightText: 2012-2013 Allen Winter <winter@kde.org>
0006 
0007   SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
0008 */
0009 
0010 #include "calprintpluginbase.h"
0011 #include "cellitem.h"
0012 #include "kcalprefs.h"
0013 #include "utils.h"
0014 
0015 #include <Akonadi/Item>
0016 #include <Akonadi/TagCache>
0017 
0018 #include "calendarsupport_debug.h"
0019 #include <KConfig>
0020 #include <KConfigGroup>
0021 #include <KWordWrap>
0022 
0023 #include <KLocalizedString>
0024 #include <QAbstractTextDocumentLayout>
0025 #include <QFrame>
0026 #include <QLabel>
0027 #include <QLocale>
0028 #include <QTextCursor>
0029 #include <QTextDocument>
0030 #include <QTextDocumentFragment>
0031 #include <QTimeZone>
0032 #include <QVBoxLayout>
0033 #include <qmath.h> // qCeil krazy:exclude=camelcase since no QMath
0034 
0035 using namespace CalendarSupport;
0036 
0037 static QString cleanStr(const QString &instr)
0038 {
0039     QString ret = instr;
0040     return ret.replace(QLatin1Char('\n'), QLatin1Char(' '));
0041 }
0042 
0043 const QColor CalPrintPluginBase::sHolidayBackground = QColor(244, 244, 244);
0044 
0045 /******************************************************************
0046  **              The Todo positioning structure                  **
0047  ******************************************************************/
0048 class CalPrintPluginBase::TodoParentStart
0049 {
0050 public:
0051     TodoParentStart(QRect pt = QRect(), bool hasLine = false, bool page = true)
0052         : mRect(pt)
0053         , mHasLine(hasLine)
0054         , mSamePage(page)
0055     {
0056     }
0057 
0058     QRect mRect;
0059     bool mHasLine;
0060     bool mSamePage;
0061 };
0062 
0063 /******************************************************************
0064  **                     The Print item                           **
0065  ******************************************************************/
0066 
0067 class PrintCellItem : public CellItem
0068 {
0069 public:
0070     PrintCellItem(const KCalendarCore::Event::Ptr &event, const QDateTime &start, const QDateTime &end)
0071         : mEvent(event)
0072         , mStart(start)
0073         , mEnd(end)
0074     {
0075     }
0076 
0077     [[nodiscard]] KCalendarCore::Event::Ptr event() const
0078     {
0079         return mEvent;
0080     }
0081 
0082     [[nodiscard]] QString label() const override
0083     {
0084         return mEvent->summary();
0085     }
0086 
0087     [[nodiscard]] QDateTime start() const
0088     {
0089         return mStart;
0090     }
0091 
0092     [[nodiscard]] QDateTime end() const
0093     {
0094         return mEnd;
0095     }
0096 
0097     /** Calculate the start and end date/time of the recurrence that
0098         happens on the given day */
0099     bool overlaps(CellItem *o) const override
0100     {
0101         auto other = static_cast<PrintCellItem *>(o);
0102         return !(other->start() >= end() || other->end() <= start());
0103     }
0104 
0105 private:
0106     KCalendarCore::Event::Ptr mEvent;
0107     QDateTime mStart, mEnd;
0108 };
0109 
0110 /******************************************************************
0111  **                    The Print plugin                          **
0112  ******************************************************************/
0113 
0114 CalPrintPluginBase::CalPrintPluginBase()
0115     : PrintPlugin()
0116     , mUseColors(true)
0117     , mPrintFooter(true)
0118     , mHeaderHeight(-1)
0119     , mSubHeaderHeight(SUBHEADER_HEIGHT)
0120     , mFooterHeight(-1)
0121     , mMargin(MARGIN_SIZE)
0122     , mPadding(PADDING_SIZE)
0123 {
0124 }
0125 
0126 CalPrintPluginBase::~CalPrintPluginBase() = default;
0127 
0128 QWidget *CalPrintPluginBase::createConfigWidget(QWidget *w)
0129 {
0130     auto wdg = new QFrame(w);
0131     auto layout = new QVBoxLayout(wdg);
0132 
0133     auto title = new QLabel(description(), wdg);
0134     QFont titleFont(title->font());
0135     titleFont.setPointSize(20);
0136     titleFont.setBold(true);
0137     title->setFont(titleFont);
0138 
0139     layout->addWidget(title);
0140     layout->addWidget(new QLabel(info(), wdg));
0141     layout->addSpacing(20);
0142     layout->addWidget(new QLabel(i18n("This printing style does not have any configuration options."), wdg));
0143     layout->addStretch();
0144     return wdg;
0145 }
0146 
0147 void CalPrintPluginBase::doPrint(QPrinter *printer)
0148 {
0149     if (!printer) {
0150         return;
0151     }
0152     mPrinter = printer;
0153     QPainter p;
0154 
0155     mPrinter->setColorMode(mUseColors ? QPrinter::Color : QPrinter::GrayScale);
0156 
0157     p.begin(mPrinter);
0158     // TODO: Fix the margins!!!
0159     // the painter initially begins at 72 dpi per the Qt docs.
0160     // we want half-inch margins.
0161     int margins = margin();
0162     p.setViewport(margins, margins, p.viewport().width() - 2 * margins, p.viewport().height() - 2 * margins);
0163     //   QRect vp( p.viewport() );
0164     // vp.setRight( vp.right()*2 );
0165     // vp.setBottom( vp.bottom()*2 );
0166     //   p.setWindow( vp );
0167     int pageWidth = p.window().width();
0168     int pageHeight = p.window().height();
0169     //   int pageWidth = p.viewport().width();
0170     //   int pageHeight = p.viewport().height();
0171 
0172     print(p, pageWidth, pageHeight);
0173 
0174     p.end();
0175     mPrinter = nullptr;
0176 }
0177 
0178 void CalPrintPluginBase::doLoadConfig()
0179 {
0180     if (mConfig) {
0181         KConfigGroup group(mConfig, groupName());
0182         mConfig->sync();
0183         QDateTime dt = QDateTime::currentDateTime();
0184         mFromDate = group.readEntry("FromDate", dt).date();
0185         mToDate = group.readEntry("ToDate", dt).date();
0186         mUseColors = group.readEntry("UseColors", true);
0187         mPrintFooter = group.readEntry("PrintFooter", true);
0188         mShowNoteLines = group.readEntry("Note Lines", false);
0189         mExcludeConfidential = group.readEntry("Exclude confidential", true);
0190         mExcludePrivate = group.readEntry("Exclude private", true);
0191     } else {
0192         qCDebug(CALENDARSUPPORT_LOG) << "No config available in loadConfig!!!!";
0193     }
0194 }
0195 
0196 void CalPrintPluginBase::doSaveConfig()
0197 {
0198     if (mConfig) {
0199         KConfigGroup group(mConfig, groupName());
0200         QDateTime dt = QDateTime::currentDateTime(); // any valid QDateTime will do
0201         dt.setDate(mFromDate);
0202         group.writeEntry("FromDate", dt);
0203         dt.setDate(mToDate);
0204         group.writeEntry("ToDate", dt);
0205         group.writeEntry("UseColors", mUseColors);
0206         group.writeEntry("PrintFooter", mPrintFooter);
0207         group.writeEntry("Note Lines", mShowNoteLines);
0208         group.writeEntry("Exclude confidential", mExcludeConfidential);
0209         group.writeEntry("Exclude private", mExcludePrivate);
0210         mConfig->sync();
0211     } else {
0212         qCDebug(CALENDARSUPPORT_LOG) << "No config available in saveConfig!!!!";
0213     }
0214 }
0215 
0216 bool CalPrintPluginBase::useColors() const
0217 {
0218     return mUseColors;
0219 }
0220 
0221 void CalPrintPluginBase::setUseColors(bool useColors)
0222 {
0223     mUseColors = useColors;
0224 }
0225 
0226 bool CalPrintPluginBase::printFooter() const
0227 {
0228     return mPrintFooter;
0229 }
0230 
0231 void CalPrintPluginBase::setPrintFooter(bool printFooter)
0232 {
0233     mPrintFooter = printFooter;
0234 }
0235 
0236 QPageLayout::Orientation CalPrintPluginBase::orientation() const
0237 {
0238     return mPrinter ? mPrinter->pageLayout().orientation() : QPageLayout::Portrait;
0239 }
0240 
0241 QColor CalPrintPluginBase::getTextColor(const QColor &c) const
0242 {
0243     double luminance = (c.red() * 0.299) + (c.green() * 0.587) + (c.blue() * 0.114);
0244     return (luminance > 128.0) ? QColor(0, 0, 0) : QColor(255, 255, 255);
0245 }
0246 
0247 QTime CalPrintPluginBase::dayStart() const
0248 {
0249     QTime start(8, 0, 0);
0250     QDateTime dayBegins = KCalPrefs::instance()->dayBegins();
0251     if (dayBegins.isValid()) {
0252         start = dayBegins.time();
0253     }
0254     return start;
0255 }
0256 
0257 void CalPrintPluginBase::setColorsByIncidenceCategory(QPainter &p, const KCalendarCore::Incidence::Ptr &incidence) const
0258 {
0259     QColor bgColor = categoryBgColor(incidence);
0260     if (bgColor.isValid()) {
0261         p.setBrush(bgColor);
0262     }
0263     QColor tColor(getTextColor(bgColor));
0264     if (tColor.isValid()) {
0265         p.setPen(tColor);
0266     }
0267 }
0268 
0269 QColor CalPrintPluginBase::categoryColor(const QStringList &categories) const
0270 {
0271     // FIXME: Correctly treat events with multiple categories
0272     QColor bgColor;
0273     if (!categories.isEmpty()) {
0274         bgColor = Akonadi::TagCache::instance()->tagColor(categories.at(0));
0275     }
0276 
0277     return bgColor.isValid() ? bgColor : KCalPrefs::instance()->unsetCategoryColor();
0278 }
0279 
0280 QColor CalPrintPluginBase::categoryBgColor(const KCalendarCore::Incidence::Ptr &incidence) const
0281 {
0282     if (incidence) {
0283         QColor backColor = categoryColor(incidence->categories());
0284         if (incidence->type() == KCalendarCore::Incidence::TypeTodo) {
0285             if ((incidence.staticCast<KCalendarCore::Todo>())->isOverdue()) {
0286                 backColor = QColor(255, 100, 100); // was KOPrefs::instance()->todoOverdueColor();
0287             }
0288         }
0289         return backColor;
0290     } else {
0291         return {};
0292     }
0293 }
0294 
0295 QString CalPrintPluginBase::holidayString(QDate date) const
0296 {
0297     const QStringList lst = holiday(date);
0298     return lst.join(i18nc("@item:intext delimiter for joining holiday names", ","));
0299 }
0300 
0301 KCalendarCore::Event::Ptr CalPrintPluginBase::holidayEvent(QDate date) const
0302 {
0303     QString hstring(holidayString(date));
0304     if (hstring.isEmpty()) {
0305         return {};
0306     }
0307 
0308     KCalendarCore::Event::Ptr holiday(new KCalendarCore::Event);
0309     holiday->setSummary(hstring);
0310     holiday->setCategories(i18n("Holiday"));
0311 
0312     QDateTime kdt(date, QTime(0, 0), QTimeZone::LocalTime);
0313     holiday->setDtStart(kdt);
0314     holiday->setDtEnd(kdt);
0315     holiday->setAllDay(true);
0316 
0317     return holiday;
0318 }
0319 
0320 int CalPrintPluginBase::headerHeight() const
0321 {
0322     if (mHeaderHeight >= 0) {
0323         return mHeaderHeight;
0324     } else if (orientation() == QPageLayout::Portrait) {
0325         return PORTRAIT_HEADER_HEIGHT;
0326     } else {
0327         return LANDSCAPE_HEADER_HEIGHT;
0328     }
0329 }
0330 
0331 void CalPrintPluginBase::setHeaderHeight(const int height)
0332 {
0333     mHeaderHeight = height;
0334 }
0335 
0336 int CalPrintPluginBase::subHeaderHeight() const
0337 {
0338     return mSubHeaderHeight;
0339 }
0340 
0341 void CalPrintPluginBase::setSubHeaderHeight(const int height)
0342 {
0343     mSubHeaderHeight = height;
0344 }
0345 
0346 int CalPrintPluginBase::footerHeight() const
0347 {
0348     if (!mPrintFooter) {
0349         return 0;
0350     }
0351 
0352     if (mFooterHeight >= 0) {
0353         return mFooterHeight;
0354     } else if (orientation() == QPageLayout::Portrait) {
0355         return PORTRAIT_FOOTER_HEIGHT;
0356     } else {
0357         return LANDSCAPE_FOOTER_HEIGHT;
0358     }
0359 }
0360 
0361 void CalPrintPluginBase::setFooterHeight(const int height)
0362 {
0363     mFooterHeight = height;
0364 }
0365 
0366 int CalPrintPluginBase::margin() const
0367 {
0368     return mMargin;
0369 }
0370 
0371 void CalPrintPluginBase::setMargin(const int margin)
0372 {
0373     mMargin = margin;
0374 }
0375 
0376 int CalPrintPluginBase::padding() const
0377 {
0378     return mPadding;
0379 }
0380 
0381 void CalPrintPluginBase::setPadding(const int padding)
0382 {
0383     mPadding = padding;
0384 }
0385 
0386 int CalPrintPluginBase::borderWidth() const
0387 {
0388     return mBorder;
0389 }
0390 
0391 void CalPrintPluginBase::setBorderWidth(const int borderwidth)
0392 {
0393     mBorder = borderwidth;
0394 }
0395 
0396 void CalPrintPluginBase::drawBox(QPainter &p, int linewidth, QRect rect)
0397 {
0398     QPen pen(p.pen());
0399     QPen oldpen(pen);
0400     // no border
0401     if (linewidth >= 0) {
0402         pen.setWidth(linewidth);
0403         p.setPen(pen);
0404     } else {
0405         p.setPen(Qt::NoPen);
0406     }
0407     p.drawRect(rect);
0408     p.setPen(oldpen);
0409 }
0410 
0411 void CalPrintPluginBase::drawShadedBox(QPainter &p, int linewidth, const QBrush &brush, QRect rect)
0412 {
0413     QBrush oldbrush(p.brush());
0414     p.setBrush(brush);
0415     drawBox(p, linewidth, rect);
0416     p.setBrush(oldbrush);
0417 }
0418 
0419 void CalPrintPluginBase::printEventString(QPainter &p, QRect box, const QString &str, int flags)
0420 {
0421     QRect newbox(box);
0422     newbox.adjust(3, 1, -1, -1);
0423     p.drawText(newbox, (flags == -1) ? (Qt::AlignTop | Qt::AlignLeft | Qt::TextWordWrap) : flags, str);
0424 }
0425 
0426 void CalPrintPluginBase::showEventBox(QPainter &p, int linewidth, QRect box, const KCalendarCore::Incidence::Ptr &incidence, const QString &str, int flags)
0427 {
0428     QPen oldpen(p.pen());
0429     QBrush oldbrush(p.brush());
0430     QColor bgColor(categoryBgColor(incidence));
0431     if (mUseColors && bgColor.isValid()) {
0432         p.setBrush(bgColor);
0433     } else {
0434         p.setBrush(QColor(232, 232, 232));
0435     }
0436     drawBox(p, (linewidth > 0) ? linewidth : EVENT_BORDER_WIDTH, box);
0437     if (mUseColors && bgColor.isValid()) {
0438         p.setPen(getTextColor(bgColor));
0439     }
0440     printEventString(p, box, str, flags);
0441     p.setPen(oldpen);
0442     p.setBrush(oldbrush);
0443 }
0444 
0445 void CalPrintPluginBase::drawSubHeaderBox(QPainter &p, const QString &str, QRect box)
0446 {
0447     drawShadedBox(p, BOX_BORDER_WIDTH, QColor(232, 232, 232), box);
0448     QFont oldfont(p.font());
0449     p.setFont(QFont(QStringLiteral("sans-serif"), 10, QFont::Bold));
0450     p.drawText(box, Qt::AlignHCenter | Qt::AlignTop, str);
0451     p.setFont(oldfont);
0452 }
0453 
0454 void CalPrintPluginBase::drawVerticalBox(QPainter &p, int linewidth, QRect box, const QString &str, int flags)
0455 {
0456     p.save();
0457     p.rotate(-90);
0458     QRect rotatedBox(-box.top() - box.height(), box.left(), box.height(), box.width());
0459     showEventBox(p, linewidth, rotatedBox, KCalendarCore::Incidence::Ptr(), str, (flags == -1) ? Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine : flags);
0460 
0461     p.restore();
0462 }
0463 
0464 /*
0465  * Return value: If expand, bottom of the printed box, otherwise vertical end
0466  * of the printed contents inside the box.
0467  */
0468 int CalPrintPluginBase::drawBoxWithCaption(QPainter &p,
0469                                            QRect allbox,
0470                                            const QString &caption,
0471                                            const QString &contents,
0472                                            bool sameLine,
0473                                            bool expand,
0474                                            const QFont &captionFont,
0475                                            const QFont &textFont,
0476                                            bool richContents)
0477 {
0478     QFont oldFont(p.font());
0479     //   QFont captionFont( "sans-serif", 11, QFont::Bold );
0480     //   QFont textFont( "sans-serif", 11, QFont::Normal );
0481     //   QFont captionFont( "Tahoma", 11, QFont::Bold );
0482     //   QFont textFont( "Tahoma", 11, QFont::Normal );
0483 
0484     QRect box(allbox);
0485 
0486     // Bounding rectangle for caption, single-line, clip on the right
0487     QRect captionBox(box.left() + padding(), box.top() + padding(), 0, 0);
0488     p.setFont(captionFont);
0489     captionBox = p.boundingRect(captionBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine, caption);
0490     p.setFont(oldFont);
0491     if (captionBox.right() > box.right()) {
0492         captionBox.setRight(box.right());
0493     }
0494     if (expand && captionBox.bottom() + padding() > box.bottom()) {
0495         box.setBottom(captionBox.bottom() + padding());
0496     }
0497 
0498     // Bounding rectangle for the contents (if any), word break, clip on the bottom
0499     QRect textBox(captionBox);
0500     if (!contents.isEmpty()) {
0501         if (sameLine) {
0502             textBox.setLeft(captionBox.right() + padding());
0503         } else {
0504             textBox.setTop(captionBox.bottom() + padding());
0505         }
0506         textBox.setRight(box.right());
0507     }
0508     drawBox(p, BOX_BORDER_WIDTH, box);
0509     p.setFont(captionFont);
0510     p.drawText(captionBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine, caption);
0511 
0512     if (!contents.isEmpty()) {
0513         if (sameLine) {
0514             QString contentText = toPlainText(contents);
0515             p.setFont(textFont);
0516             p.drawText(textBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine, contentText);
0517         } else {
0518             QTextDocument rtb;
0519             int borderWidth = 2 * BOX_BORDER_WIDTH;
0520             if (richContents) {
0521                 rtb.setHtml(contents);
0522             } else {
0523                 rtb.setPlainText(contents);
0524             }
0525             int boxHeight = allbox.height();
0526             if (!sameLine) {
0527                 boxHeight -= captionBox.height();
0528             }
0529             rtb.setPageSize(QSize(textBox.width(), boxHeight));
0530             rtb.setDefaultFont(textFont);
0531             p.save();
0532             p.translate(textBox.x() - borderWidth, textBox.y());
0533             QRect clipBox(0, 0, box.width(), boxHeight);
0534             QAbstractTextDocumentLayout::PaintContext ctx;
0535             ctx.palette.setColor(QPalette::Text, p.pen().color());
0536             p.setClipRect(clipBox);
0537             ctx.clip = clipBox;
0538             rtb.documentLayout()->draw(&p, ctx);
0539             p.restore();
0540             textBox.setBottom(textBox.y() + rtb.documentLayout()->documentSize().height());
0541         }
0542     }
0543     p.setFont(oldFont);
0544 
0545     if (expand) {
0546         return box.bottom();
0547     } else {
0548         return textBox.bottom();
0549     }
0550 }
0551 
0552 int CalPrintPluginBase::drawHeader(QPainter &p, const QString &title, QDate month1, QDate month2, QRect allbox, bool expand, QColor backColor)
0553 {
0554     // print previous month for month view, print current for to-do, day and week
0555     int smallMonthWidth = (allbox.width() / 4) - 10;
0556     if (smallMonthWidth > 100) {
0557         smallMonthWidth = 100;
0558     }
0559 
0560     QRect box(allbox);
0561     QRect textRect(allbox);
0562 
0563     QFont oldFont(p.font());
0564     QFont newFont(QStringLiteral("sans-serif"), (textRect.height() < 60) ? 16 : 18, QFont::Bold);
0565     if (expand) {
0566         p.setFont(newFont);
0567         QRect boundingR = p.boundingRect(textRect, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, title);
0568         p.setFont(oldFont);
0569         int h = boundingR.height();
0570         if (h > allbox.height()) {
0571             box.setHeight(h);
0572             textRect.setHeight(h);
0573         }
0574     }
0575 
0576     if (!backColor.isValid()) {
0577         backColor = QColor(232, 232, 232);
0578     }
0579 
0580     drawShadedBox(p, BOX_BORDER_WIDTH, backColor, box);
0581 
0582     const auto oldPen{p.pen()};
0583     p.setPen(getTextColor(backColor));
0584 
0585     // prev month left, current month centered, next month right
0586     QRect monthbox2(box.right() - 10 - smallMonthWidth, box.top(), smallMonthWidth, box.height());
0587     if (month2.isValid()) {
0588         drawSmallMonth(p, QDate(month2.year(), month2.month(), 1), monthbox2);
0589         textRect.setRight(monthbox2.left());
0590     }
0591     QRect monthbox1(box.left() + 10, box.top(), smallMonthWidth, box.height());
0592     if (month1.isValid()) {
0593         drawSmallMonth(p, QDate(month1.year(), month1.month(), 1), monthbox1);
0594         textRect.setLeft(monthbox1.right());
0595     }
0596 
0597     // Set the margins
0598     p.setFont(newFont);
0599     p.drawText(textRect, Qt::AlignCenter | Qt::AlignVCenter | Qt::TextWordWrap, title);
0600 
0601     p.setPen(oldPen);
0602     p.setFont(oldFont);
0603 
0604     return textRect.bottom();
0605 }
0606 
0607 int CalPrintPluginBase::drawFooter(QPainter &p, QRect footbox)
0608 {
0609     QFont oldfont(p.font());
0610     p.setFont(QFont(QStringLiteral("sans-serif"), 6));
0611     QString dateStr = QLocale::system().toString(QDateTime::currentDateTime(), QLocale::LongFormat);
0612     p.drawText(footbox, Qt::AlignCenter | Qt::AlignVCenter | Qt::TextSingleLine, i18nc("print date: formatted-datetime", "printed: %1", dateStr));
0613     p.setFont(oldfont);
0614 
0615     return footbox.bottom();
0616 }
0617 
0618 void CalPrintPluginBase::drawSmallMonth(QPainter &p, QDate qd, QRect box)
0619 {
0620     int weekdayCol = weekdayColumn(qd.dayOfWeek());
0621     int month = qd.month();
0622     QDate monthDate(QDate(qd.year(), qd.month(), 1));
0623     // correct begin of week
0624     QDate monthDate2(monthDate.addDays(-weekdayCol));
0625 
0626     double cellWidth = double(box.width()) / double(7);
0627     int rownr = 3 + (qd.daysInMonth() + weekdayCol - 1) / 7;
0628     // 3 Pixel after month name, 2 after day names, 1 after the calendar
0629     double cellHeight = (box.height() - 5) / rownr;
0630     QFont oldFont(p.font());
0631     auto newFont = QFont(QStringLiteral("sans-serif"));
0632     newFont.setPixelSize(cellHeight);
0633     p.setFont(newFont);
0634 
0635     const QLocale locale;
0636 
0637     // draw the title
0638     QRect titleBox(box);
0639     titleBox.setHeight(p.fontMetrics().height());
0640     p.drawText(titleBox, Qt::AlignTop | Qt::AlignHCenter, locale.standaloneMonthName(month));
0641 
0642     // draw days of week
0643     QRect wdayBox(box);
0644     wdayBox.setTop(int(box.top() + 3 + cellHeight));
0645     wdayBox.setHeight(int(2 * cellHeight) - int(cellHeight));
0646 
0647     for (int col = 0; col < 7; ++col) {
0648         const auto dayLetter = locale.standaloneDayName(monthDate2.dayOfWeek(), QLocale::ShortFormat)[0].toUpper();
0649         wdayBox.setLeft(int(box.left() + col * cellWidth));
0650         wdayBox.setRight(int(box.left() + (col + 1) * cellWidth));
0651         p.drawText(wdayBox, Qt::AlignCenter, dayLetter);
0652         monthDate2 = monthDate2.addDays(1);
0653     }
0654 
0655     // draw separator line
0656     int calStartY = wdayBox.bottom() + 2;
0657     p.drawLine(box.left(), calStartY, box.right(), calStartY);
0658     monthDate = monthDate.addDays(-weekdayCol);
0659 
0660     for (int row = 0; row < (rownr - 2); row++) {
0661         for (int col = 0; col < 7; col++) {
0662             if (monthDate.month() == month) {
0663                 QRect dayRect(int(box.left() + col * cellWidth), int(calStartY + row * cellHeight), 0, 0);
0664                 dayRect.setRight(int(box.left() + (col + 1) * cellWidth));
0665                 dayRect.setBottom(int(calStartY + (row + 1) * cellHeight));
0666                 p.drawText(dayRect, Qt::AlignCenter, QString::number(monthDate.day()));
0667             }
0668             monthDate = monthDate.addDays(1);
0669         }
0670     }
0671     p.setFont(oldFont);
0672 }
0673 
0674 /*
0675  * This routine draws a header box over the main part of the calendar
0676  * containing the days of the week.
0677  */
0678 void CalPrintPluginBase::drawDaysOfWeek(QPainter &p, QDate fromDate, QDate toDate, QRect box)
0679 {
0680     double cellWidth = double(box.width() - 1) / double(fromDate.daysTo(toDate) + 1);
0681     QDate cellDate(fromDate);
0682     QRect dateBox(box);
0683     int i = 0;
0684 
0685     while (cellDate <= toDate) {
0686         dateBox.setLeft(box.left() + int(i * cellWidth));
0687         dateBox.setRight(box.left() + int((i + 1) * cellWidth));
0688         drawDaysOfWeekBox(p, cellDate, dateBox);
0689         cellDate = cellDate.addDays(1);
0690         ++i;
0691     }
0692 }
0693 
0694 void CalPrintPluginBase::drawDaysOfWeekBox(QPainter &p, QDate qd, QRect box)
0695 {
0696     drawSubHeaderBox(p, QLocale::system().dayName(qd.dayOfWeek()), box);
0697 }
0698 
0699 void CalPrintPluginBase::drawTimeLine(QPainter &p, QTime fromTime, QTime toTime, QRect box)
0700 {
0701     drawBox(p, BOX_BORDER_WIDTH, box);
0702 
0703     int totalsecs = fromTime.secsTo(toTime);
0704     float minlen = (float)box.height() * 60. / (float)totalsecs;
0705     float cellHeight = (60. * (float)minlen);
0706     float currY = box.top();
0707     // TODO: Don't use half of the width, but less, for the minutes!
0708     int xcenter = box.left() + box.width() / 2;
0709 
0710     QTime curTime(fromTime);
0711     QTime endTime(toTime);
0712     if (fromTime.minute() > 30) {
0713         curTime = QTime(fromTime.hour() + 1, 0, 0);
0714     } else if (fromTime.minute() > 0) {
0715         curTime = QTime(fromTime.hour(), 30, 0);
0716         float yy = currY + minlen * (float)fromTime.secsTo(curTime) / 60.;
0717         p.drawLine(xcenter, (int)yy, box.right(), (int)yy);
0718         curTime = QTime(fromTime.hour() + 1, 0, 0);
0719     }
0720     currY += (float(fromTime.secsTo(curTime) * minlen) / 60.);
0721 
0722     while (curTime < endTime) {
0723         p.drawLine(box.left(), (int)currY, box.right(), (int)currY);
0724         int newY = (int)(currY + cellHeight / 2.);
0725         QString numStr;
0726         if (newY < box.bottom()) {
0727             QFont oldFont(p.font());
0728             // draw the time:
0729             if (!QLocale().timeFormat().contains(QLatin1StringView("AP"))) { // 12h clock
0730                 p.drawLine(xcenter, (int)newY, box.right(), (int)newY);
0731                 numStr.setNum(curTime.hour());
0732                 if (cellHeight > 30) {
0733                     p.setFont(QFont(QStringLiteral("sans-serif"), 14, QFont::Bold));
0734                 } else {
0735                     p.setFont(QFont(QStringLiteral("sans-serif"), 12, QFont::Bold));
0736                 }
0737                 p.drawText(box.left() + 4, (int)currY + 2, box.width() / 2 - 2, (int)cellHeight, Qt::AlignTop | Qt::AlignRight, numStr);
0738                 p.setFont(QFont(QStringLiteral("helvetica"), 10, QFont::Normal));
0739                 p.drawText(xcenter + 4, (int)currY + 2, box.width() / 2 + 2, (int)(cellHeight / 2) - 3, Qt::AlignTop | Qt::AlignLeft, QStringLiteral("00"));
0740             } else {
0741                 p.drawLine(box.left(), (int)newY, box.right(), (int)newY);
0742                 QTime time(curTime.hour(), 0);
0743                 numStr = QLocale::system().toString(time, QLocale::ShortFormat);
0744                 if (box.width() < 60) {
0745                     p.setFont(QFont(QStringLiteral("sans-serif"), 7, QFont::Bold)); // for weekprint
0746                 } else {
0747                     p.setFont(QFont(QStringLiteral("sans-serif"), 12, QFont::Bold)); // for dayprint
0748                 }
0749                 p.drawText(box.left() + 2, (int)currY + 2, box.width() - 4, (int)cellHeight / 2 - 3, Qt::AlignTop | Qt::AlignLeft, numStr);
0750             }
0751             currY += cellHeight;
0752             p.setFont(oldFont);
0753         } // enough space for half-hour line and time
0754         if (curTime.secsTo(endTime) > 3600) {
0755             curTime = curTime.addSecs(3600);
0756         } else {
0757             curTime = endTime;
0758         }
0759     }
0760 }
0761 
0762 void CalPrintPluginBase::drawAgendaDayBox(QPainter &p,
0763                                           const KCalendarCore::Event::List &events,
0764                                           QDate qd,
0765                                           bool expandable,
0766                                           QTime fromTime,
0767                                           QTime toTime,
0768                                           QRect oldbox,
0769                                           bool includeDescription,
0770                                           bool includeCategories,
0771                                           bool excludeTime,
0772                                           const QList<QDate> &workDays)
0773 {
0774     QTime myFromTime;
0775     QTime myToTime;
0776     if (fromTime.isValid()) {
0777         myFromTime = fromTime;
0778     } else {
0779         myFromTime = QTime(0, 0, 0);
0780     }
0781     if (toTime.isValid()) {
0782         myToTime = toTime;
0783     } else {
0784         myToTime = QTime(23, 59, 59);
0785     }
0786 
0787     if (!workDays.contains(qd)) {
0788         drawShadedBox(p, BOX_BORDER_WIDTH, sHolidayBackground, oldbox);
0789     } else {
0790         drawBox(p, BOX_BORDER_WIDTH, oldbox);
0791     }
0792     QRect box(oldbox);
0793     // Account for the border with and cut away that margin from the interior
0794     //   box.setRight( box.right()-BOX_BORDER_WIDTH );
0795 
0796     if (expandable) {
0797         // Adapt start/end times to include complete events
0798         for (const KCalendarCore::Event::Ptr &event : std::as_const(events)) {
0799             Q_ASSERT(event);
0800             if (!event || (mExcludeConfidential && event->secrecy() == KCalendarCore::Incidence::SecrecyConfidential)
0801                 || (mExcludePrivate && event->secrecy() == KCalendarCore::Incidence::SecrecyPrivate)) {
0802                 continue;
0803             }
0804             // skip items without times so that we do not adjust for all day items
0805             if (event->allDay()) {
0806                 continue;
0807             }
0808             if (event->dtStart().time() < myFromTime) {
0809                 myFromTime = event->dtStart().time();
0810             }
0811             if (event->dtEnd().time() > myToTime) {
0812                 myToTime = event->dtEnd().time();
0813             }
0814         }
0815     }
0816 
0817     // calculate the height of a cell and of a minute
0818     int totalsecs = myFromTime.secsTo(myToTime);
0819     float minlen = box.height() * 60. / totalsecs;
0820     float cellHeight = 60. * minlen;
0821     float currY = box.top();
0822 
0823     // print grid:
0824     QTime curTime(QTime(myFromTime.hour(), 0, 0));
0825     currY += myFromTime.secsTo(curTime) * minlen / 60;
0826 
0827     while (curTime < myToTime && curTime.isValid()) {
0828         if (currY > box.top()) {
0829             p.drawLine(box.left(), int(currY), box.right(), int(currY));
0830         }
0831         currY += cellHeight / 2;
0832         if ((currY > box.top()) && (currY < box.bottom())) {
0833             // enough space for half-hour line
0834             QPen oldPen(p.pen());
0835             p.setPen(QColor(192, 192, 192));
0836             p.drawLine(box.left(), int(currY), box.right(), int(currY));
0837             p.setPen(oldPen);
0838         }
0839         if (curTime.secsTo(myToTime) > 3600) {
0840             curTime = curTime.addSecs(3600);
0841         } else {
0842             curTime = myToTime;
0843         }
0844         currY += cellHeight / 2;
0845     }
0846 
0847     QDateTime startPrintDate = QDateTime(qd, myFromTime);
0848     QDateTime endPrintDate = QDateTime(qd, myToTime);
0849 
0850     // Calculate horizontal positions and widths of events taking into account
0851     // overlapping events
0852 
0853     QList<CellItem *> cells;
0854 
0855     for (const KCalendarCore::Event::Ptr &event : std::as_const(events)) {
0856         if (!event || (mExcludeConfidential && event->secrecy() == KCalendarCore::Incidence::SecrecyConfidential)
0857             || (mExcludePrivate && event->secrecy() == KCalendarCore::Incidence::SecrecyPrivate)) {
0858             continue;
0859         }
0860         if (event->allDay()) {
0861             continue;
0862         }
0863         QList<QDateTime> times = event->startDateTimesForDate(qd, QTimeZone::systemTimeZone());
0864         cells.reserve(times.count());
0865         for (auto it = times.constBegin(); it != times.constEnd(); ++it) {
0866             cells.append(new PrintCellItem(event, (*it).toLocalTime(), event->endDateForStart(*it).toLocalTime()));
0867         }
0868     }
0869 
0870     QListIterator<CellItem *> it1(cells);
0871     while (it1.hasNext()) {
0872         CellItem *placeItem = it1.next();
0873         CellItem::placeItem(cells, placeItem);
0874     }
0875 
0876     QListIterator<CellItem *> it2(cells);
0877     while (it2.hasNext()) {
0878         auto placeItem = static_cast<PrintCellItem *>(it2.next());
0879         drawAgendaItem(placeItem, p, startPrintDate, endPrintDate, minlen, box, includeDescription, includeCategories, excludeTime);
0880     }
0881 }
0882 
0883 void CalPrintPluginBase::drawAgendaItem(PrintCellItem *item,
0884                                         QPainter &p,
0885                                         const QDateTime &startPrintDate,
0886                                         const QDateTime &endPrintDate,
0887                                         float minlen,
0888                                         QRect box,
0889                                         bool includeDescription,
0890                                         bool includeCategories,
0891                                         bool excludeTime)
0892 {
0893     KCalendarCore::Event::Ptr event = item->event();
0894 
0895     // start/end of print area for event
0896     QDateTime startTime = item->start();
0897     QDateTime endTime = item->end();
0898     if ((startTime < endPrintDate && endTime > startPrintDate) || (endTime > startPrintDate && startTime < endPrintDate)) {
0899         if (startTime < startPrintDate) {
0900             startTime = startPrintDate;
0901         }
0902         if (endTime > endPrintDate) {
0903             endTime = endPrintDate;
0904         }
0905         int currentWidth = box.width() / item->subCells();
0906         int currentX = box.left() + item->subCell() * currentWidth;
0907         int currentYPos = int(box.top() + startPrintDate.secsTo(startTime) * minlen / 60.);
0908         int currentHeight = int(box.top() + startPrintDate.secsTo(endTime) * minlen / 60.) - currentYPos;
0909 
0910         QRect eventBox(currentX, currentYPos, currentWidth, currentHeight);
0911         QString str;
0912         if (excludeTime) {
0913             if (event->location().isEmpty()) {
0914                 str = cleanStr(event->summary());
0915             } else {
0916                 str = i18nc("summary, location", "%1, %2", cleanStr(event->summary()), cleanStr(event->location()));
0917             }
0918         } else {
0919             if (event->location().isEmpty()) {
0920                 str = i18nc("starttime - endtime summary",
0921                             "%1-%2 %3",
0922                             QLocale::system().toString(item->start().time(), QLocale::ShortFormat),
0923                             QLocale::system().toString(item->end().time(), QLocale::ShortFormat),
0924                             cleanStr(event->summary()));
0925             } else {
0926                 str = i18nc("starttime - endtime summary, location",
0927                             "%1-%2 %3, %4",
0928                             QLocale::system().toString(item->start().time(), QLocale::ShortFormat),
0929                             QLocale::system().toString(item->end().time(), QLocale::ShortFormat),
0930                             cleanStr(event->summary()),
0931                             cleanStr(event->location()));
0932             }
0933         }
0934         if (includeCategories && !event->categoriesStr().isEmpty()) {
0935             str = i18nc("summary, categories", "%1, %2", str, event->categoriesStr());
0936         }
0937         if (includeDescription && !event->description().isEmpty()) {
0938             str += QLatin1Char('\n');
0939             if (event->descriptionIsRich()) {
0940                 str += toPlainText(event->description());
0941             } else {
0942                 str += event->description();
0943             }
0944         }
0945         QFont oldFont(p.font());
0946         if (eventBox.height() < 24) {
0947             if (eventBox.height() < 12) {
0948                 if (eventBox.height() < 8) {
0949                     p.setFont(QFont(QStringLiteral("sans-serif"), 4));
0950                 } else {
0951                     p.setFont(QFont(QStringLiteral("sans-serif"), 5));
0952                 }
0953             } else {
0954                 p.setFont(QFont(QStringLiteral("sans-serif"), 6));
0955             }
0956         } else {
0957             p.setFont(QFont(QStringLiteral("sans-serif"), 8));
0958         }
0959         showEventBox(p, EVENT_BORDER_WIDTH, eventBox, event, str);
0960         p.setFont(oldFont);
0961     }
0962 }
0963 
0964 void CalPrintPluginBase::drawDayBox(QPainter &p,
0965                                     QDate qd,
0966                                     QTime fromTime,
0967                                     QTime toTime,
0968                                     QRect box,
0969                                     bool fullDate,
0970                                     bool printRecurDaily,
0971                                     bool printRecurWeekly,
0972                                     bool singleLineLimit,
0973                                     bool includeDescription,
0974                                     bool includeCategories)
0975 {
0976     QString dayNumStr;
0977     const auto local = QLocale::system();
0978 
0979     QTime myFromTime;
0980     QTime myToTime;
0981     if (fromTime.isValid()) {
0982         myFromTime = fromTime;
0983     } else {
0984         myFromTime = QTime(0, 0, 0);
0985     }
0986     if (toTime.isValid()) {
0987         myToTime = toTime;
0988     } else {
0989         myToTime = QTime(23, 59, 59);
0990     }
0991 
0992     if (fullDate) {
0993         dayNumStr = i18nc("weekday, shortmonthname daynumber",
0994                           "%1, %2 %3",
0995                           QLocale::system().dayName(qd.dayOfWeek()),
0996                           QLocale::system().monthName(qd.month(), QLocale::ShortFormat),
0997                           QString::number(qd.day()));
0998     } else {
0999         dayNumStr = QString::number(qd.day());
1000     }
1001 
1002     QRect subHeaderBox(box);
1003     subHeaderBox.setHeight(mSubHeaderHeight);
1004     drawShadedBox(p, BOX_BORDER_WIDTH, p.background(), box);
1005     drawShadedBox(p, 0, QColor(232, 232, 232), subHeaderBox);
1006     drawBox(p, BOX_BORDER_WIDTH, box);
1007     QString hstring(holidayString(qd));
1008     const QFont oldFont(p.font());
1009 
1010     QRect headerTextBox(subHeaderBox.adjusted(5, 0, -5, 0));
1011     p.setFont(QFont(QStringLiteral("sans-serif"), 10, QFont::Bold));
1012     QRect dayNumRect;
1013     p.drawText(headerTextBox, Qt::AlignRight | Qt::AlignVCenter, dayNumStr, &dayNumRect);
1014     if (!hstring.isEmpty()) {
1015         p.setFont(QFont(QStringLiteral("sans-serif"), 8, QFont::Bold, true));
1016         QFontMetrics fm(p.font());
1017         hstring = fm.elidedText(hstring, Qt::ElideRight, headerTextBox.width() - dayNumRect.width() - 5);
1018         p.drawText(headerTextBox, Qt::AlignLeft | Qt::AlignVCenter, hstring);
1019         p.setFont(QFont(QStringLiteral("sans-serif"), 10, QFont::Bold));
1020     }
1021 
1022     const KCalendarCore::Event::List eventList =
1023         mCalendar->events(qd, QTimeZone::systemTimeZone(), KCalendarCore::EventSortStartDate, KCalendarCore::SortDirectionAscending);
1024 
1025     QString timeText;
1026     p.setFont(QFont(QStringLiteral("sans-serif"), 7));
1027 
1028     int textY = mSubHeaderHeight; // gives the relative y-coord of the next printed entry
1029     unsigned int visibleEventsCounter = 0;
1030     for (const KCalendarCore::Event::Ptr &currEvent : std::as_const(eventList)) {
1031         Q_ASSERT(currEvent);
1032         if (!currEvent->allDay()) {
1033             if (currEvent->dtEnd().toLocalTime().time() <= myFromTime || currEvent->dtStart().toLocalTime().time() > myToTime) {
1034                 continue;
1035             }
1036         }
1037         if ((!printRecurDaily && currEvent->recurrenceType() == KCalendarCore::Recurrence::rDaily)
1038             || (!printRecurWeekly && currEvent->recurrenceType() == KCalendarCore::Recurrence::rWeekly)) {
1039             continue;
1040         }
1041         if ((mExcludeConfidential && currEvent->secrecy() == KCalendarCore::Incidence::SecrecyConfidential)
1042             || (mExcludePrivate && currEvent->secrecy() == KCalendarCore::Incidence::SecrecyPrivate)) {
1043             continue;
1044         }
1045         if (currEvent->allDay() || currEvent->isMultiDay()) {
1046             timeText.clear();
1047         } else {
1048             timeText = local.toString(currEvent->dtStart().toLocalTime().time(), QLocale::ShortFormat) + QLatin1Char(' ');
1049         }
1050         p.save();
1051         if (mUseColors) {
1052             setColorsByIncidenceCategory(p, currEvent);
1053         }
1054         QString summaryStr = currEvent->summary();
1055         if (!currEvent->location().isEmpty()) {
1056             summaryStr = i18nc("summary, location", "%1, %2", summaryStr, currEvent->location());
1057         }
1058         if (includeCategories && !currEvent->categoriesStr().isEmpty()) {
1059             summaryStr = i18nc("summary, categories", "%1, %2", summaryStr, currEvent->categoriesStr());
1060         }
1061         drawIncidence(p, box, timeText, summaryStr, currEvent->description(), textY, singleLineLimit, includeDescription, currEvent->descriptionIsRich());
1062         p.restore();
1063         visibleEventsCounter++;
1064 
1065         if (textY >= box.height()) {
1066             const QChar downArrow(0x21e3);
1067 
1068             const unsigned int invisibleIncidences = (eventList.count() - visibleEventsCounter) + mCalendar->todos(qd).count();
1069             if (invisibleIncidences > 0) {
1070                 const QString warningMsg = QStringLiteral("%1 (%2)").arg(downArrow).arg(invisibleIncidences);
1071 
1072                 QFontMetrics fm(p.font());
1073                 QRect msgRect = fm.boundingRect(warningMsg);
1074                 msgRect.setRect(box.right() - msgRect.width() - 2, box.bottom() - msgRect.height() - 2, msgRect.width(), msgRect.height());
1075 
1076                 p.save();
1077                 p.setPen(Qt::red); // krazy:exclude=qenums we don't allow custom print colors
1078                 p.drawText(msgRect, Qt::AlignLeft, warningMsg);
1079                 p.restore();
1080             }
1081             break;
1082         }
1083     }
1084 
1085     if (textY < box.height()) {
1086         KCalendarCore::Todo::List todos = mCalendar->todos(qd);
1087         for (const KCalendarCore::Todo::Ptr &todo : std::as_const(todos)) {
1088             if (!todo->allDay()) {
1089                 if ((todo->hasDueDate() && todo->dtDue().toLocalTime().time() <= myFromTime)
1090                     || (todo->hasStartDate() && todo->dtStart().toLocalTime().time() > myToTime)) {
1091                     continue;
1092                 }
1093             }
1094             if ((!printRecurDaily && todo->recurrenceType() == KCalendarCore::Recurrence::rDaily)
1095                 || (!printRecurWeekly && todo->recurrenceType() == KCalendarCore::Recurrence::rWeekly)) {
1096                 continue;
1097             }
1098             if ((mExcludeConfidential && todo->secrecy() == KCalendarCore::Incidence::SecrecyConfidential)
1099                 || (mExcludePrivate && todo->secrecy() == KCalendarCore::Incidence::SecrecyPrivate)) {
1100                 continue;
1101             }
1102             if (todo->hasStartDate() && !todo->allDay()) {
1103                 timeText = QLocale().toString(todo->dtStart().toLocalTime().time(), QLocale::ShortFormat) + QLatin1Char(' ');
1104             } else {
1105                 timeText.clear();
1106             }
1107             p.save();
1108             if (mUseColors) {
1109                 setColorsByIncidenceCategory(p, todo);
1110             }
1111             QString summaryStr = todo->summary();
1112             if (!todo->location().isEmpty()) {
1113                 summaryStr = i18nc("summary, location", "%1, %2", summaryStr, todo->location());
1114             }
1115 
1116             QString str;
1117             if (todo->hasDueDate()) {
1118                 if (!todo->allDay()) {
1119                     str = i18nc("to-do summary (Due: datetime)",
1120                                 "%1 (Due: %2)",
1121                                 summaryStr,
1122                                 QLocale().toString(todo->dtDue().toLocalTime(), QLocale::ShortFormat));
1123                 } else {
1124                     str = i18nc("to-do summary (Due: date)",
1125                                 "%1 (Due: %2)",
1126                                 summaryStr,
1127                                 QLocale().toString(todo->dtDue().toLocalTime().date(), QLocale::ShortFormat));
1128                 }
1129             } else {
1130                 str = summaryStr;
1131             }
1132             drawIncidence(p, box, timeText, i18n("To-do: %1", str), todo->description(), textY, singleLineLimit, includeDescription, todo->descriptionIsRich());
1133             p.restore();
1134         }
1135     }
1136     if (mShowNoteLines) {
1137         drawNoteLines(p, box, box.y() + textY);
1138     }
1139 
1140     p.setFont(oldFont);
1141 }
1142 
1143 void CalPrintPluginBase::drawIncidence(QPainter &p,
1144                                        QRect dayBox,
1145                                        const QString &time,
1146                                        const QString &summary,
1147                                        const QString &description,
1148                                        int &textY,
1149                                        bool singleLineLimit,
1150                                        bool includeDescription,
1151                                        bool richDescription)
1152 {
1153     qCDebug(CALENDARSUPPORT_LOG) << "summary =" << summary << ", singleLineLimit=" << singleLineLimit;
1154 
1155     int flags = Qt::AlignLeft | Qt::OpaqueMode;
1156     QFontMetrics fm = p.fontMetrics();
1157     const int borderWidth = p.pen().width() + 1;
1158 
1159     QString firstLine{time};
1160     if (!firstLine.isEmpty()) {
1161         firstLine += QStringLiteral(" ");
1162     }
1163     firstLine += summary;
1164 
1165     if (singleLineLimit) {
1166         if (includeDescription && !description.isEmpty()) {
1167             firstLine += QStringLiteral(". ") + toPlainText(description);
1168         }
1169 
1170         int totalHeight = fm.height() + borderWidth;
1171         int textBoxHeight = (totalHeight > (dayBox.height() - textY)) ? dayBox.height() - textY : totalHeight;
1172         QRect boxRect(dayBox.x() + p.pen().width(), dayBox.y() + textY, dayBox.width(), textBoxHeight);
1173         drawBox(p, 1, boxRect);
1174         p.drawText(boxRect.adjusted(3, 0, -3, 0), flags, firstLine);
1175         textY += textBoxHeight;
1176     } else {
1177         QTextDocument textDoc;
1178         QTextCursor textCursor(&textDoc);
1179         textCursor.insertText(firstLine);
1180         if (includeDescription && !description.isEmpty()) {
1181             textCursor.insertText(QStringLiteral("\n"));
1182             if (richDescription) {
1183                 textCursor.insertHtml(description);
1184             } else {
1185                 textCursor.insertText(toPlainText(description));
1186             }
1187         }
1188 
1189         QRect textBox = QRect(dayBox.x(), dayBox.y() + textY + 1, dayBox.width(), dayBox.height() - textY);
1190         textDoc.setPageSize(QSize(textBox.width(), textBox.height()));
1191 
1192         textBox.setHeight(textDoc.documentLayout()->documentSize().height());
1193         if (textBox.bottom() > dayBox.bottom()) {
1194             textBox.setBottom(dayBox.bottom());
1195         }
1196 
1197         QRect boxRext(dayBox.x() + p.pen().width(), dayBox.y() + textY, dayBox.width(), textBox.height());
1198         drawBox(p, 1, boxRext);
1199 
1200         QRect clipRect(0, 0, textBox.width(), textBox.height());
1201         QAbstractTextDocumentLayout::PaintContext ctx;
1202         ctx.palette.setColor(QPalette::Text, p.pen().color());
1203         ctx.clip = clipRect;
1204         p.save();
1205         p.translate(textBox.x(), textBox.y());
1206         p.setClipRect(clipRect);
1207         textDoc.documentLayout()->draw(&p, ctx);
1208         p.restore();
1209 
1210         textY += textBox.height();
1211 
1212         if (textDoc.pageCount() > 1) {
1213             // show that we have overflowed the box
1214             QPolygon poly(3);
1215             int x = dayBox.x() + dayBox.width();
1216             int y = dayBox.y() + dayBox.height();
1217             poly.setPoint(0, x - 10, y);
1218             poly.setPoint(1, x, y - 10);
1219             poly.setPoint(2, x, y);
1220             QBrush oldBrush(p.brush());
1221             p.setBrush(QBrush(Qt::black));
1222             p.drawPolygon(poly);
1223             p.setBrush(oldBrush);
1224             textY = dayBox.height();
1225         }
1226     }
1227 }
1228 
1229 class MonthEventStruct
1230 {
1231 public:
1232     MonthEventStruct()
1233         : event(nullptr)
1234     {
1235     }
1236 
1237     MonthEventStruct(const QDateTime &s, const QDateTime &e, const KCalendarCore::Event::Ptr &ev)
1238         : start(s)
1239         , end(e)
1240         , event(ev)
1241     {
1242         if (event->allDay()) {
1243             start = QDateTime(start.date(), QTime(0, 0, 0));
1244             end = QDateTime(end.date().addDays(1), QTime(0, 0, 0)).addSecs(-1);
1245         }
1246     }
1247 
1248     bool operator<(const MonthEventStruct &mes)
1249     {
1250         return start < mes.start;
1251     }
1252 
1253     QDateTime start;
1254     QDateTime end;
1255     KCalendarCore::Event::Ptr event;
1256 };
1257 
1258 void CalPrintPluginBase::drawMonth(QPainter &p, QDate dt, QRect box, int maxdays, int subDailyFlags, int holidaysFlags)
1259 {
1260     p.save();
1261     QRect subheaderBox(box);
1262     subheaderBox.setHeight(subHeaderHeight());
1263     QRect borderBox(box);
1264     borderBox.setTop(subheaderBox.bottom() + 1);
1265     drawSubHeaderBox(p, QLocale().standaloneMonthName(dt.month()), subheaderBox);
1266     // correct for half the border width
1267     int correction = (BOX_BORDER_WIDTH /*-1*/) / 2;
1268     QRect daysBox(borderBox);
1269     daysBox.adjust(correction, correction, -correction, -correction);
1270 
1271     int daysinmonth = dt.daysInMonth();
1272     if (maxdays <= 0) {
1273         maxdays = daysinmonth;
1274     }
1275 
1276     float dayheight = float(daysBox.height()) / float(maxdays);
1277 
1278     QColor holidayColor(240, 240, 240);
1279     QColor workdayColor(255, 255, 255);
1280     int dayNrWidth = p.fontMetrics().boundingRect(QStringLiteral("99")).width();
1281 
1282     // Fill the remaining space (if a month has less days than others) with a crossed-out pattern
1283     if (daysinmonth < maxdays) {
1284         QRect dayBox(box.left(), daysBox.top() + qRound(dayheight * daysinmonth), box.width(), 0);
1285         dayBox.setBottom(daysBox.bottom());
1286         p.fillRect(dayBox, Qt::DiagCrossPattern);
1287     }
1288     // Backgrounded boxes for each day, plus day numbers
1289     QBrush oldbrush(p.brush());
1290 
1291     QList<QDate> workDays;
1292 
1293     {
1294         QDate startDate(dt.year(), dt.month(), 1);
1295         QDate endDate(dt.year(), dt.month(), daysinmonth);
1296 
1297         workDays = CalendarSupport::workDays(startDate, endDate);
1298     }
1299 
1300     for (int d = 0; d < daysinmonth; ++d) {
1301         QDate day(dt.year(), dt.month(), d + 1);
1302         QRect dayBox(daysBox.left() /*+rand()%50*/, daysBox.top() + qRound(dayheight * d), daysBox.width() /*-rand()%50*/, 0);
1303         // FIXME: When using a border width of 0 for event boxes,
1304         // don't let the rectangles overlap, i.e. subtract 1 from the top or bottom!
1305         dayBox.setBottom(daysBox.top() + qRound(dayheight * (d + 1)) - 1);
1306 
1307         p.setBrush(workDays.contains(day) ? workdayColor : holidayColor);
1308         p.drawRect(dayBox);
1309         QRect dateBox(dayBox);
1310         dateBox.setWidth(dayNrWidth + 3);
1311         p.drawText(dateBox, Qt::AlignRight | Qt::AlignVCenter | Qt::TextSingleLine, QString::number(d + 1));
1312     }
1313     p.setBrush(oldbrush);
1314     int xstartcont = box.left() + dayNrWidth + 5;
1315 
1316     QDate start(dt.year(), dt.month(), 1);
1317     QDate end = start.addMonths(1);
1318     end = end.addDays(-1);
1319 
1320     const KCalendarCore::Event::List events = mCalendar->events(start, end);
1321     QMap<int, QStringList> textEvents;
1322     QList<CellItem *> timeboxItems;
1323 
1324     // 1) For multi-day events, show boxes spanning several cells, use CellItem
1325     //    print the summary vertically
1326     // 2) For sub-day events, print the concated summaries into the remaining
1327     //    space of the box (optional, depending on the given flags)
1328     // 3) Draw some kind of timeline showing free and busy times
1329 
1330     // Holidays
1331     // QList<KCalendarCore::Event::Ptr> holidays;
1332     for (QDate d(start); d <= end; d = d.addDays(1)) {
1333         KCalendarCore::Event::Ptr e = holidayEvent(d);
1334         if (e) {
1335             // holidays.append(e);
1336             if (holidaysFlags & TimeBoxes) {
1337                 timeboxItems.append(new PrintCellItem(e, QDateTime(d, QTime(0, 0, 0)), QDateTime(d.addDays(1), QTime(0, 0, 0))));
1338             }
1339             if (holidaysFlags & Text) {
1340                 textEvents[d.day()] << e->summary();
1341             }
1342         }
1343     }
1344 
1345     QList<MonthEventStruct> monthentries;
1346 
1347     for (const KCalendarCore::Event::Ptr &e : std::as_const(events)) {
1348         if (!e || (mExcludeConfidential && e->secrecy() == KCalendarCore::Incidence::SecrecyConfidential)
1349             || (mExcludePrivate && e->secrecy() == KCalendarCore::Incidence::SecrecyPrivate)) {
1350             continue;
1351         }
1352         if (e->recurs()) {
1353             if (e->recursOn(start, QTimeZone::systemTimeZone())) {
1354                 // This occurrence has possibly started before the beginning of the
1355                 // month, so obtain the start date before the beginning of the month
1356                 QList<QDateTime> starttimes = e->startDateTimesForDate(start, QTimeZone::systemTimeZone());
1357                 for (auto it = starttimes.constBegin(); it != starttimes.constEnd(); ++it) {
1358                     monthentries.append(MonthEventStruct((*it).toLocalTime(), e->endDateForStart(*it).toLocalTime(), e));
1359                 }
1360             }
1361             // Loop through all remaining days of the month and check if the event
1362             // begins on that day (don't use Event::recursOn, as that will
1363             // also return events that have started earlier. These start dates
1364             // however, have already been treated!
1365             KCalendarCore::Recurrence *recur = e->recurrence();
1366             QDate d1(start.addDays(1));
1367             while (d1 <= end) {
1368                 if (recur->recursOn(d1, QTimeZone::systemTimeZone())) {
1369                     KCalendarCore::TimeList times(recur->recurTimesOn(d1, QTimeZone::systemTimeZone()));
1370                     for (KCalendarCore::TimeList::ConstIterator it = times.constBegin(); it != times.constEnd(); ++it) {
1371                         QDateTime d1start(d1, *it, QTimeZone::LocalTime);
1372                         monthentries.append(MonthEventStruct(d1start, e->endDateForStart(d1start).toLocalTime(), e));
1373                     }
1374                 }
1375                 d1 = d1.addDays(1);
1376             }
1377         } else {
1378             monthentries.append(MonthEventStruct(e->dtStart().toLocalTime(), e->dtEnd().toLocalTime(), e));
1379         }
1380     }
1381 
1382     // TODO: to port the month entries sorting
1383 
1384     //  qSort( monthentries.begin(), monthentries.end() );
1385 
1386     QList<MonthEventStruct>::ConstIterator mit = monthentries.constBegin();
1387     QDateTime endofmonth(end, QTime(0, 0, 0));
1388     endofmonth = endofmonth.addDays(1);
1389     for (; mit != monthentries.constEnd(); ++mit) {
1390         if ((*mit).start.date() == (*mit).end.date()) {
1391             // Show also single-day events as time line boxes
1392             if (subDailyFlags & TimeBoxes) {
1393                 timeboxItems.append(new PrintCellItem((*mit).event, (*mit).start, (*mit).end));
1394             }
1395             // Show as text in the box
1396             if (subDailyFlags & Text) {
1397                 textEvents[(*mit).start.date().day()] << (*mit).event->summary();
1398             }
1399         } else {
1400             // Multi-day events are always shown as time line boxes
1401             QDateTime thisstart((*mit).start);
1402             QDateTime thisend((*mit).end);
1403             if (thisstart.date() < start) {
1404                 thisstart.setDate(start);
1405             }
1406             if (thisend > endofmonth) {
1407                 thisend = endofmonth;
1408             }
1409             timeboxItems.append(new PrintCellItem((*mit).event, thisstart, thisend));
1410         }
1411     }
1412 
1413     // For Multi-day events, line them up nicely so that the boxes don't overlap
1414     QListIterator<CellItem *> it1(timeboxItems);
1415     while (it1.hasNext()) {
1416         CellItem *placeItem = it1.next();
1417         CellItem::placeItem(timeboxItems, placeItem);
1418     }
1419     QDateTime starttime(start, QTime(0, 0, 0));
1420     int newxstartcont = xstartcont;
1421 
1422     QFont oldfont(p.font());
1423     p.setFont(QFont(QStringLiteral("sans-serif"), 7));
1424     while (it1.hasNext()) {
1425         auto placeItem = static_cast<PrintCellItem *>(it1.next());
1426         int minsToStart = starttime.secsTo(placeItem->start()) / 60;
1427         int minsToEnd = starttime.secsTo(placeItem->end()) / 60;
1428 
1429         QRect eventBox(xstartcont + placeItem->subCell() * 17,
1430                        daysBox.top() + qRound(double(minsToStart * daysBox.height()) / double(maxdays * 24 * 60)),
1431                        14,
1432                        0);
1433         eventBox.setBottom(daysBox.top() + qRound(double(minsToEnd * daysBox.height()) / double(maxdays * 24 * 60)));
1434         drawVerticalBox(p, 0, eventBox, placeItem->event()->summary());
1435         newxstartcont = qMax(newxstartcont, eventBox.right());
1436     }
1437     xstartcont = newxstartcont;
1438 
1439     // For Single-day events, simply print their summaries into the remaining
1440     // space of the day's cell
1441     for (int d = 0; d < daysinmonth; ++d) {
1442         QStringList dayEvents(textEvents[d + 1]);
1443         QString txt = dayEvents.join(QLatin1StringView(", "));
1444         QRect dayBox(xstartcont, daysBox.top() + qRound(dayheight * d), 0, 0);
1445         dayBox.setRight(box.right());
1446         dayBox.setBottom(daysBox.top() + qRound(dayheight * (d + 1)));
1447         printEventString(p, dayBox, txt, Qt::AlignTop | Qt::AlignLeft | Qt::TextWrapAnywhere);
1448     }
1449     p.setFont(oldfont);
1450     drawBox(p, BOX_BORDER_WIDTH, borderBox);
1451     p.restore();
1452 }
1453 
1454 void CalPrintPluginBase::drawMonthTable(QPainter &p,
1455                                         QDate qd,
1456                                         QTime fromTime,
1457                                         QTime toTime,
1458                                         bool weeknumbers,
1459                                         bool recurDaily,
1460                                         bool recurWeekly,
1461                                         bool singleLineLimit,
1462                                         bool includeDescription,
1463                                         bool includeCategories,
1464                                         QRect box)
1465 {
1466     int yoffset = mSubHeaderHeight;
1467     int xoffset = 0;
1468     QDate monthDate(QDate(qd.year(), qd.month(), 1));
1469     QDate monthFirst(monthDate);
1470     QDate monthLast(monthDate.addMonths(1).addDays(-1));
1471 
1472     int weekdayCol = weekdayColumn(monthDate.dayOfWeek());
1473     monthDate = monthDate.addDays(-weekdayCol);
1474 
1475     if (weeknumbers) {
1476         xoffset += 14;
1477     }
1478 
1479     int rows = (weekdayCol + qd.daysInMonth() - 1) / 7 + 1;
1480     double cellHeight = (box.height() - yoffset) / (1. * rows);
1481     double cellWidth = (box.width() - xoffset) / 7.;
1482 
1483     // Precalculate the grid...
1484     // rows is at most 6, so using 8 entries in the array is fine, too!
1485     int coledges[8];
1486     int rowedges[8];
1487     for (int i = 0; i <= 7; ++i) {
1488         rowedges[i] = int(box.top() + yoffset + i * cellHeight);
1489         coledges[i] = int(box.left() + xoffset + i * cellWidth);
1490     }
1491 
1492     if (weeknumbers) {
1493         QFont oldFont(p.font());
1494         QFont newFont(p.font());
1495         newFont.setPointSize(6);
1496         p.setFont(newFont);
1497         QDate weekDate(monthDate);
1498         for (int row = 0; row < rows; ++row) {
1499             int calWeek = weekDate.weekNumber();
1500             QRect rc(box.left(), rowedges[row], coledges[0] - 3 - box.left(), rowedges[row + 1] - rowedges[row]);
1501             p.drawText(rc, Qt::AlignRight | Qt::AlignVCenter, QString::number(calWeek));
1502             weekDate = weekDate.addDays(7);
1503         }
1504         p.setFont(oldFont);
1505     }
1506 
1507     QRect daysOfWeekBox(box);
1508     daysOfWeekBox.setHeight(mSubHeaderHeight);
1509     daysOfWeekBox.setLeft(box.left() + xoffset);
1510     drawDaysOfWeek(p, monthDate, monthDate.addDays(6), daysOfWeekBox);
1511 
1512     QColor back = p.background().color();
1513     bool darkbg = false;
1514     for (int row = 0; row < rows; ++row) {
1515         for (int col = 0; col < 7; ++col) {
1516             // show days from previous/next month with a grayed background
1517             if ((monthDate < monthFirst) || (monthDate > monthLast)) {
1518                 p.setBackground(back.darker(120));
1519                 darkbg = true;
1520             }
1521             QRect dayBox(coledges[col], rowedges[row], coledges[col + 1] - coledges[col], rowedges[row + 1] - rowedges[row]);
1522             drawDayBox(p, monthDate, fromTime, toTime, dayBox, false, recurDaily, recurWeekly, singleLineLimit, includeDescription, includeCategories);
1523             if (darkbg) {
1524                 p.setBackground(back);
1525                 darkbg = false;
1526             }
1527             monthDate = monthDate.addDays(1);
1528         }
1529     }
1530 }
1531 
1532 void CalPrintPluginBase::drawTodoLines(QPainter &p,
1533                                        const QString &entry,
1534                                        int x,
1535                                        int &y,
1536                                        int width,
1537                                        int pageHeight,
1538                                        bool richTextEntry,
1539                                        QList<TodoParentStart *> &startPoints,
1540                                        bool connectSubTodos)
1541 {
1542     QString plainEntry = (richTextEntry) ? toPlainText(entry) : entry;
1543 
1544     QRect textrect(0, 0, width, -1);
1545     int flags = Qt::AlignLeft;
1546     QFontMetrics fm = p.fontMetrics();
1547 
1548     QStringList lines = plainEntry.split(QLatin1Char('\n'));
1549     for (int currentLine = 0; currentLine < lines.count(); currentLine++) {
1550         // split paragraphs into lines
1551         KWordWrap ww = KWordWrap::formatText(fm, textrect, flags, lines[currentLine]);
1552         QStringList textLine = ww.wrappedString().split(QLatin1Char('\n'));
1553 
1554         // print each individual line
1555         for (int lineCount = 0; lineCount < textLine.count(); lineCount++) {
1556             if (y >= pageHeight) {
1557                 if (connectSubTodos) {
1558                     for (int i = 0; i < startPoints.size(); ++i) {
1559                         TodoParentStart *rct;
1560                         rct = startPoints.at(i);
1561                         int start = rct->mRect.bottom() + 1;
1562                         int center = rct->mRect.left() + (rct->mRect.width() / 2);
1563                         int to = y;
1564                         if (!rct->mSamePage) {
1565                             start = 0;
1566                         }
1567                         if (rct->mHasLine) {
1568                             p.drawLine(center, start, center, to);
1569                         }
1570                         rct->mSamePage = false;
1571                     }
1572                 }
1573                 y = 0;
1574                 mPrinter->newPage();
1575             }
1576             y += fm.height();
1577             p.drawText(x, y, textLine[lineCount]);
1578         }
1579     }
1580 }
1581 
1582 void CalPrintPluginBase::drawTodo(int &count,
1583                                   const KCalendarCore::Todo::Ptr &todo,
1584                                   QPainter &p,
1585                                   KCalendarCore::TodoSortField sortField,
1586                                   KCalendarCore::SortDirection sortDir,
1587                                   bool connectSubTodos,
1588                                   bool strikeoutCompleted,
1589                                   bool desc,
1590                                   int posPriority,
1591                                   int posSummary,
1592                                   int posCategories,
1593                                   int posStartDt,
1594                                   int posDueDt,
1595                                   int posPercentComplete,
1596                                   int level,
1597                                   int x,
1598                                   int &y,
1599                                   int width,
1600                                   int pageHeight,
1601                                   const KCalendarCore::Todo::List &todoList,
1602                                   TodoParentStart *r)
1603 {
1604     QString outStr;
1605     const auto locale = QLocale::system();
1606     QRect rect;
1607     TodoParentStart startpt;
1608     // This list keeps all starting points of the parent to-dos so the connection
1609     // lines of the tree can easily be drawn (needed if a new page is started)
1610     static QList<TodoParentStart *> startPoints;
1611     if (level < 1) {
1612         startPoints.clear();
1613     }
1614 
1615     // Don't print confidential or private items if so configured (sub-items are also ignored!)
1616     if ((mExcludeConfidential && todo->secrecy() == KCalendarCore::Incidence::SecrecyConfidential)
1617         || (mExcludePrivate && todo->secrecy() == KCalendarCore::Incidence::SecrecyPrivate)) {
1618         return;
1619     }
1620 
1621     QFontMetrics fm = p.fontMetrics();
1622     y += 10;
1623     // Start a new page if the item does not fit on the page any more (only
1624     // first line is checked! Word-wrapped summaries might still overflow!)
1625     if (y + fm.height() >= pageHeight) {
1626         y = 0;
1627         mPrinter->newPage();
1628         // reset the parent start points to indicate not on same page
1629         for (int i = 0; i < startPoints.size(); ++i) {
1630             TodoParentStart *rct;
1631             rct = startPoints.at(i);
1632             rct->mSamePage = false;
1633         }
1634     }
1635 
1636     int left = posSummary + (level * 10);
1637 
1638     // If this is a sub-to-do, r will not be 0, and we want the LH side
1639     // of the priority line up to the RH side of the parent to-do's priority
1640     int lhs = posPriority;
1641     if (r) {
1642         lhs = r->mRect.right() + 1;
1643     }
1644 
1645     outStr.setNum(todo->priority());
1646     rect = p.boundingRect(lhs, y + 10, 5, -1, Qt::AlignCenter, outStr);
1647     // Make it a more reasonable size
1648     rect.setWidth(18);
1649     rect.setHeight(18);
1650     const int top = rect.top();
1651 
1652     // Draw a checkbox
1653     p.setBrush(QBrush(Qt::NoBrush));
1654     p.drawRect(rect);
1655     if (todo->isCompleted()) {
1656         // cross out the rectangle for completed to-dos
1657         p.drawLine(rect.topLeft(), rect.bottomRight());
1658         p.drawLine(rect.topRight(), rect.bottomLeft());
1659     }
1660     lhs = rect.right() + 5;
1661 
1662     // Priority
1663     if (posPriority >= 0 && todo->priority() > 0) {
1664         p.drawText(rect, Qt::AlignCenter, outStr);
1665     }
1666     startpt.mRect = rect; // save for later
1667 
1668     // Connect the dots
1669     if (r && level > 0 && connectSubTodos) {
1670         int bottom;
1671         int center(r->mRect.left() + (r->mRect.width() / 2));
1672         int to(rect.top() + (rect.height() / 2));
1673         int endx(rect.left());
1674         p.drawLine(center, to, endx, to); // side connector
1675         if (r->mSamePage) {
1676             bottom = r->mRect.bottom() + 1;
1677         } else {
1678             bottom = 0;
1679         }
1680         p.drawLine(center, bottom, center, to);
1681     }
1682 
1683     int posSoFar = width; // Position of leftmost optional field.
1684 
1685     // due date
1686     if (posDueDt >= 0 && todo->hasDueDate()) {
1687         outStr = locale.toString(todo->dtDue().toLocalTime().date(), QLocale::ShortFormat);
1688         rect = p.boundingRect(posDueDt, top, x + width, -1, Qt::AlignTop | Qt::AlignLeft, outStr);
1689         p.drawText(rect, Qt::AlignTop | Qt::AlignLeft, outStr);
1690         posSoFar = posDueDt;
1691     }
1692 
1693     // start date
1694     if (posStartDt >= 0 && todo->hasStartDate()) {
1695         outStr = locale.toString(todo->dtStart().toLocalTime().date(), QLocale::ShortFormat);
1696         rect = p.boundingRect(posStartDt, top, x + width, -1, Qt::AlignTop | Qt::AlignLeft, outStr);
1697         p.drawText(rect, Qt::AlignTop | Qt::AlignLeft, outStr);
1698         posSoFar = posStartDt;
1699     }
1700 
1701     // percentage completed
1702     if (posPercentComplete >= 0) {
1703         int lwidth = 24;
1704         int lheight = p.fontMetrics().ascent();
1705         // first, draw the progress bar
1706         int progress = static_cast<int>(((lwidth * todo->percentComplete()) / 100.0 + 0.5));
1707 
1708         p.setBrush(QBrush(Qt::NoBrush));
1709         p.drawRect(posPercentComplete, top, lwidth, lheight);
1710         if (progress > 0) {
1711             p.setBrush(QColor(128, 128, 128));
1712             p.drawRect(posPercentComplete, top, progress, lheight);
1713         }
1714 
1715         // now, write the percentage
1716         outStr = i18n("%1%", todo->percentComplete());
1717         rect = p.boundingRect(posPercentComplete + lwidth + 3, top, x + width, -1, Qt::AlignTop | Qt::AlignLeft, outStr);
1718         p.drawText(rect, Qt::AlignTop | Qt::AlignLeft, outStr);
1719         posSoFar = posPercentComplete;
1720     }
1721 
1722     // categories
1723     QRect categoriesRect{0, 0, 0, 0};
1724     if (posCategories >= 0) {
1725         outStr = todo->categoriesStr();
1726         outStr.replace(QLatin1Char(','), QLatin1Char('\n'));
1727         rect = p.boundingRect(posCategories, top, posSoFar - posCategories, -1, Qt::TextWordWrap, outStr);
1728         p.drawText(rect, Qt::TextWordWrap, outStr, &categoriesRect);
1729         posSoFar = posCategories;
1730     }
1731 
1732     // summary
1733     outStr = todo->summary();
1734     rect = p.boundingRect(lhs, top, posSoFar - lhs - 5, -1, Qt::TextWordWrap, outStr);
1735     QFont oldFont(p.font());
1736     if (strikeoutCompleted && todo->isCompleted()) {
1737         QFont newFont(p.font());
1738         newFont.setStrikeOut(true);
1739         p.setFont(newFont);
1740     }
1741     QRect summaryRect;
1742     p.drawText(rect, Qt::TextWordWrap, outStr, &summaryRect);
1743     p.setFont(oldFont);
1744 
1745     y = std::max(categoriesRect.bottom(), summaryRect.bottom());
1746 
1747     // description
1748     if (desc && !todo->description().isEmpty()) {
1749         drawTodoLines(p, todo->description(), left, y, width - (left + 10 - x), pageHeight, todo->descriptionIsRich(), startPoints, connectSubTodos);
1750     }
1751 
1752     // Make a list of all the sub-to-dos related to this to-do.
1753     KCalendarCore::Todo::List t;
1754     for (const KCalendarCore::Incidence::Ptr &incidence : mCalendar->incidences()) {
1755         // In the future, to-dos might also be related to events
1756         // Manually check if the sub-to-do is in the list of to-dos to print
1757         // The problem is that relations() does not apply filters, so
1758         // we need to compare manually with the complete filtered list!
1759         KCalendarCore::Todo::Ptr subtodo = incidence.dynamicCast<KCalendarCore::Todo>();
1760         if (!subtodo) {
1761             continue;
1762         }
1763 
1764         if (subtodo->relatedTo() != todo->uid()) {
1765             continue;
1766         }
1767 
1768 #ifdef AKONADI_PORT_DISABLED
1769         if (subtodo && todoList.contains(subtodo)) {
1770 #else
1771         bool subtodoOk = false;
1772         if (subtodo) {
1773             for (const KCalendarCore::Todo::Ptr &tt : std::as_const(todoList)) {
1774                 if (tt == subtodo) {
1775                     subtodoOk = true;
1776                     break;
1777                 }
1778             }
1779         }
1780         if (subtodoOk) {
1781 #endif
1782             t.append(subtodo);
1783         }
1784     }
1785 
1786     // has sub-todos?
1787     startpt.mHasLine = (t.size() > 0);
1788     startPoints.append(&startpt);
1789 
1790     // Sort the sub-to-dos and print them
1791 #ifdef AKONADI_PORT_DISABLED
1792     KCalendarCore::Todo::List sl = mCalendar->sortTodos(&t, sortField, sortDir);
1793 #else
1794     KCalendarCore::Todo::List tl;
1795     tl.reserve(t.count());
1796     for (const KCalendarCore::Todo::Ptr &todo : std::as_const(t)) {
1797         tl.append(todo);
1798     }
1799     KCalendarCore::Todo::List sl = mCalendar->sortTodos(std::move(tl), sortField, sortDir);
1800 #endif
1801 
1802     int subcount = 0;
1803     for (const KCalendarCore::Todo::Ptr &isl : std::as_const(sl)) {
1804         count++;
1805         if (++subcount == sl.size()) {
1806             startpt.mHasLine = false;
1807         }
1808         drawTodo(count,
1809                  isl,
1810                  p,
1811                  sortField,
1812                  sortDir,
1813                  connectSubTodos,
1814                  strikeoutCompleted,
1815                  desc,
1816                  posPriority,
1817                  posSummary,
1818                  posCategories,
1819                  posStartDt,
1820                  posDueDt,
1821                  posPercentComplete,
1822                  level + 1,
1823                  x,
1824                  y,
1825                  width,
1826                  pageHeight,
1827                  todoList,
1828                  &startpt);
1829     }
1830     startPoints.removeAll(&startpt);
1831 }
1832 
1833 int CalPrintPluginBase::weekdayColumn(int weekday)
1834 {
1835     int w = weekday + 7 - QLocale().firstDayOfWeek();
1836     return w % 7;
1837 }
1838 
1839 void CalPrintPluginBase::drawTextLines(QPainter &p, const QString &entry, int x, int &y, int width, int pageHeight, bool richTextEntry)
1840 {
1841     QString plainEntry = (richTextEntry) ? toPlainText(entry) : entry;
1842 
1843     QRect textrect(0, 0, width, -1);
1844     int flags = Qt::AlignLeft;
1845     QFontMetrics fm = p.fontMetrics();
1846 
1847     QStringList lines = plainEntry.split(QLatin1Char('\n'));
1848     for (int currentLine = 0; currentLine < lines.count(); currentLine++) {
1849         // split paragraphs into lines
1850         KWordWrap ww = KWordWrap::formatText(fm, textrect, flags, lines[currentLine]);
1851         QStringList textLine = ww.wrappedString().split(QLatin1Char('\n'));
1852         // print each individual line
1853         for (int lineCount = 0; lineCount < textLine.count(); lineCount++) {
1854             y += fm.height();
1855             if (y >= pageHeight) {
1856                 if (mPrintFooter) {
1857                     drawFooter(p, {0, pageHeight, width, footerHeight()});
1858                 }
1859                 y = fm.height();
1860                 mPrinter->newPage();
1861             }
1862             p.drawText(x, y, textLine[lineCount]);
1863         }
1864     }
1865 }
1866 
1867 void CalPrintPluginBase::drawSplitHeaderRight(QPainter &p, QDate fd, QDate td, QDate, int width, int height)
1868 {
1869     QFont oldFont(p.font());
1870 
1871     QPen oldPen(p.pen());
1872     QPen pen(Qt::black, 4);
1873 
1874     QString title;
1875     QLocale locale;
1876     if (fd.month() == td.month()) {
1877         title = i18nc("Date range: Month dayStart - dayEnd",
1878                       "%1 %2\u2013%3",
1879                       locale.monthName(fd.month(), QLocale::LongFormat),
1880                       locale.toString(fd, QStringLiteral("dd")),
1881                       locale.toString(td, QStringLiteral("dd")));
1882     } else {
1883         title = i18nc("Date range: monthStart dayStart - monthEnd dayEnd",
1884                       "%1 %2\u2013%3 %4",
1885                       locale.monthName(fd.month(), QLocale::LongFormat),
1886                       locale.toString(fd, QStringLiteral("dd")),
1887                       locale.monthName(td.month(), QLocale::LongFormat),
1888                       locale.toString(td, QStringLiteral("dd")));
1889     }
1890 
1891     if (height < 60) {
1892         p.setFont(QFont(QStringLiteral("Times"), 22));
1893     } else {
1894         p.setFont(QFont(QStringLiteral("Times"), 28));
1895     }
1896 
1897     int lineSpacing = p.fontMetrics().lineSpacing();
1898     p.drawText(0, 0, width, lineSpacing, Qt::AlignRight | Qt::AlignTop, title);
1899 
1900     title.truncate(0);
1901 
1902     p.setPen(pen);
1903     p.drawLine(300, lineSpacing, width, lineSpacing);
1904     p.setPen(oldPen);
1905 
1906     if (height < 60) {
1907         p.setFont(QFont(QStringLiteral("Times"), 14, QFont::Bold, true));
1908     } else {
1909         p.setFont(QFont(QStringLiteral("Times"), 18, QFont::Bold, true));
1910     }
1911 
1912     title += QString::number(fd.year());
1913     p.drawText(0, lineSpacing + padding(), width, lineSpacing, Qt::AlignRight | Qt::AlignTop, title);
1914 
1915     p.setFont(oldFont);
1916 }
1917 
1918 void CalPrintPluginBase::drawNoteLines(QPainter &p, QRect box, int startY)
1919 {
1920     int lineHeight = int(p.fontMetrics().lineSpacing() * 1.5);
1921     int linePos = box.y();
1922     int startPos = startY;
1923     // adjust line to start at multiple from top of box for alignment
1924     while (linePos < startPos) {
1925         linePos += lineHeight;
1926     }
1927     QPen oldPen(p.pen());
1928     p.setPen(Qt::DotLine);
1929     while (linePos < box.bottom()) {
1930         p.drawLine(box.left() + padding(), linePos, box.right() - padding(), linePos);
1931         linePos += lineHeight;
1932     }
1933     p.setPen(oldPen);
1934 }
1935 
1936 QString CalPrintPluginBase::toPlainText(const QString &htmlText)
1937 {
1938     // this converts possible rich text to plain text
1939     return QTextDocumentFragment::fromHtml(htmlText).toPlainText();
1940 }