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 }