1 /*
2 SPDX-FileCopyrightText: 1998 Preston Brown <pbrown@kde.org>
3 SPDX-FileCopyrightText: 2003 Reinhold Kainhofer <reinhold@kainhofer.com>
4 SPDX-FileCopyrightText: 2008 Ron Goodheart <rong.dev@gmail.com>
5 SPDX-FileCopyrightText: 2012-2013 Allen Winter <winter@kde.org>
6
7 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
8 */
9
10 #include "calprintpluginbase.h"
11 #include "cellitem.h"
12 #include "kcalprefs.h"
13 #include "utils.h"
14
15 #include <Akonadi/Item>
16
17 #include "calendarsupport_debug.h"
18 #include <KConfig>
19 #include <KConfigGroup>
20 #include <KWordWrap>
21
22 #include <KLocalizedString>
23 #include <QAbstractTextDocumentLayout>
24 #include <QFrame>
25 #include <QLabel>
26 #include <QLocale>
27 #include <QTextCursor>
28 #include <QTextDocument>
29 #include <QTextDocumentFragment>
30 #include <QTimeZone>
31 #include <QVBoxLayout>
32 #include <qmath.h> // qCeil krazy:exclude=camelcase since no QMath
33
34 using namespace CalendarSupport;
35
cleanStr(const QString & instr)36 static QString cleanStr(const QString &instr)
37 {
38 QString ret = instr;
39 return ret.replace(QLatin1Char('\n'), QLatin1Char(' '));
40 }
41
42 const QColor CalPrintPluginBase::sHolidayBackground = QColor(244, 244, 244);
43
44 /******************************************************************
45 ** The Todo positioning structure **
46 ******************************************************************/
47 class CalPrintPluginBase::TodoParentStart
48 {
49 public:
TodoParentStart(QRect pt=QRect (),bool hasLine=false,bool page=true)50 TodoParentStart(QRect pt = QRect(), bool hasLine = false, bool page = true)
51 : mRect(pt)
52 , mHasLine(hasLine)
53 , mSamePage(page)
54 {
55 }
56
57 QRect mRect;
58 bool mHasLine;
59 bool mSamePage;
60 };
61
62 /******************************************************************
63 ** The Print item **
64 ******************************************************************/
65
66 class PrintCellItem : public CellItem
67 {
68 public:
PrintCellItem(const KCalendarCore::Event::Ptr & event,const QDateTime & start,const QDateTime & end)69 PrintCellItem(const KCalendarCore::Event::Ptr &event, const QDateTime &start, const QDateTime &end)
70 : mEvent(event)
71 , mStart(start)
72 , mEnd(end)
73 {
74 }
75
event() const76 KCalendarCore::Event::Ptr event() const
77 {
78 return mEvent;
79 }
80
label() const81 QString label() const override
82 {
83 return mEvent->summary();
84 }
85
start() const86 QDateTime start() const
87 {
88 return mStart;
89 }
90
end() const91 QDateTime end() const
92 {
93 return mEnd;
94 }
95
96 /** Calculate the start and end date/time of the recurrence that
97 happens on the given day */
overlaps(CellItem * o) const98 bool overlaps(CellItem *o) const override
99 {
100 auto other = static_cast<PrintCellItem *>(o);
101 return !(other->start() >= end() || other->end() <= start());
102 }
103
104 private:
105 KCalendarCore::Event::Ptr mEvent;
106 QDateTime mStart, mEnd;
107 };
108
109 /******************************************************************
110 ** The Print plugin **
111 ******************************************************************/
112
CalPrintPluginBase()113 CalPrintPluginBase::CalPrintPluginBase()
114 : PrintPlugin()
115 , mUseColors(true)
116 , mPrintFooter(true)
117 , mHeaderHeight(-1)
118 , mSubHeaderHeight(SUBHEADER_HEIGHT)
119 , mFooterHeight(-1)
120 , mMargin(MARGIN_SIZE)
121 , mPadding(PADDING_SIZE)
122 {
123 }
124
~CalPrintPluginBase()125 CalPrintPluginBase::~CalPrintPluginBase()
126 {
127 }
128
createConfigWidget(QWidget * w)129 QWidget *CalPrintPluginBase::createConfigWidget(QWidget *w)
130 {
131 auto wdg = new QFrame(w);
132 auto layout = new QVBoxLayout(wdg);
133
134 auto title = new QLabel(description(), wdg);
135 QFont titleFont(title->font());
136 titleFont.setPointSize(20);
137 titleFont.setBold(true);
138 title->setFont(titleFont);
139
140 layout->addWidget(title);
141 layout->addWidget(new QLabel(info(), wdg));
142 layout->addSpacing(20);
143 layout->addWidget(new QLabel(i18n("This printing style does not have any configuration options."), wdg));
144 layout->addStretch();
145 return wdg;
146 }
147
doPrint(QPrinter * printer)148 void CalPrintPluginBase::doPrint(QPrinter *printer)
149 {
150 if (!printer) {
151 return;
152 }
153 mPrinter = printer;
154 QPainter p;
155
156 mPrinter->setColorMode(mUseColors ? QPrinter::Color : QPrinter::GrayScale);
157
158 p.begin(mPrinter);
159 // TODO: Fix the margins!!!
160 // the painter initially begins at 72 dpi per the Qt docs.
161 // we want half-inch margins.
162 int margins = margin();
163 p.setViewport(margins, margins, p.viewport().width() - 2 * margins, p.viewport().height() - 2 * margins);
164 // QRect vp( p.viewport() );
165 // vp.setRight( vp.right()*2 );
166 // vp.setBottom( vp.bottom()*2 );
167 // p.setWindow( vp );
168 int pageWidth = p.window().width();
169 int pageHeight = p.window().height();
170 // int pageWidth = p.viewport().width();
171 // int pageHeight = p.viewport().height();
172
173 print(p, pageWidth, pageHeight);
174
175 p.end();
176 mPrinter = nullptr;
177 }
178
doLoadConfig()179 void CalPrintPluginBase::doLoadConfig()
180 {
181 if (mConfig) {
182 KConfigGroup group(mConfig, groupName());
183 mConfig->sync();
184 QDateTime dt = QDateTime::currentDateTime();
185 mFromDate = group.readEntry("FromDate", dt).date();
186 mToDate = group.readEntry("ToDate", dt).date();
187 mUseColors = group.readEntry("UseColors", true);
188 mPrintFooter = group.readEntry("PrintFooter", true);
189 mShowNoteLines = group.readEntry("Note Lines", false);
190 mExcludeConfidential = group.readEntry("Exclude confidential", true);
191 mExcludePrivate = group.readEntry("Exclude private", true);
192 } else {
193 qCDebug(CALENDARSUPPORT_LOG) << "No config available in loadConfig!!!!";
194 }
195 }
196
doSaveConfig()197 void CalPrintPluginBase::doSaveConfig()
198 {
199 if (mConfig) {
200 KConfigGroup group(mConfig, groupName());
201 QDateTime dt = QDateTime::currentDateTime(); // any valid QDateTime will do
202 dt.setDate(mFromDate);
203 group.writeEntry("FromDate", dt);
204 dt.setDate(mToDate);
205 group.writeEntry("ToDate", dt);
206 group.writeEntry("UseColors", mUseColors);
207 group.writeEntry("PrintFooter", mPrintFooter);
208 group.writeEntry("Note Lines", mShowNoteLines);
209 group.writeEntry("Exclude confidential", mExcludeConfidential);
210 group.writeEntry("Exclude private", mExcludePrivate);
211 mConfig->sync();
212 } else {
213 qCDebug(CALENDARSUPPORT_LOG) << "No config available in saveConfig!!!!";
214 }
215 }
216
useColors() const217 bool CalPrintPluginBase::useColors() const
218 {
219 return mUseColors;
220 }
221
setUseColors(bool useColors)222 void CalPrintPluginBase::setUseColors(bool useColors)
223 {
224 mUseColors = useColors;
225 }
226
printFooter() const227 bool CalPrintPluginBase::printFooter() const
228 {
229 return mPrintFooter;
230 }
231
setPrintFooter(bool printFooter)232 void CalPrintPluginBase::setPrintFooter(bool printFooter)
233 {
234 mPrintFooter = printFooter;
235 }
236
orientation() const237 QPageLayout::Orientation CalPrintPluginBase::orientation() const
238 {
239 return mPrinter ? mPrinter->pageLayout().orientation() : QPageLayout::Portrait;
240 }
241
getTextColor(const QColor & c) const242 QColor CalPrintPluginBase::getTextColor(const QColor &c) const
243 {
244 double luminance = (c.red() * 0.299) + (c.green() * 0.587) + (c.blue() * 0.114);
245 return (luminance > 128.0) ? QColor(0, 0, 0) : QColor(255, 255, 255);
246 }
247
dayStart() const248 QTime CalPrintPluginBase::dayStart() const
249 {
250 QTime start(8, 0, 0);
251 QDateTime dayBegins = KCalPrefs::instance()->dayBegins();
252 if (dayBegins.isValid()) {
253 start = dayBegins.time();
254 }
255 return start;
256 }
257
setColorsByIncidenceCategory(QPainter & p,const KCalendarCore::Incidence::Ptr & incidence) const258 void CalPrintPluginBase::setColorsByIncidenceCategory(QPainter &p, const KCalendarCore::Incidence::Ptr &incidence) const
259 {
260 QColor bgColor = categoryBgColor(incidence);
261 if (bgColor.isValid()) {
262 p.setBrush(bgColor);
263 }
264 QColor tColor(getTextColor(bgColor));
265 if (tColor.isValid()) {
266 p.setPen(tColor);
267 }
268 }
269
categoryColor(const QStringList & categories) const270 QColor CalPrintPluginBase::categoryColor(const QStringList &categories) const
271 {
272 if (categories.isEmpty()) {
273 return KCalPrefs::instance()->unsetCategoryColor();
274 }
275 // FIXME: Correctly treat events with multiple categories
276 const QString cat = categories.at(0);
277 QColor bgColor;
278 if (cat.isEmpty()) {
279 bgColor = KCalPrefs::instance()->unsetCategoryColor();
280 } else {
281 bgColor = KCalPrefs::instance()->categoryColor(cat);
282 }
283 return bgColor;
284 }
285
categoryBgColor(const KCalendarCore::Incidence::Ptr & incidence) const286 QColor CalPrintPluginBase::categoryBgColor(const KCalendarCore::Incidence::Ptr &incidence) const
287 {
288 if (incidence) {
289 QColor backColor = categoryColor(incidence->categories());
290 if (incidence->type() == KCalendarCore::Incidence::TypeTodo) {
291 if ((incidence.staticCast<KCalendarCore::Todo>())->isOverdue()) {
292 backColor = QColor(255, 100, 100); // was KOPrefs::instance()->todoOverdueColor();
293 }
294 }
295 return backColor;
296 } else {
297 return QColor();
298 }
299 }
300
holidayString(QDate date) const301 QString CalPrintPluginBase::holidayString(QDate date) const
302 {
303 const QStringList lst = holiday(date);
304 return lst.join(i18nc("@item:intext delimiter for joining holiday names", ","));
305 }
306
holidayEvent(QDate date) const307 KCalendarCore::Event::Ptr CalPrintPluginBase::holidayEvent(QDate date) const
308 {
309 QString hstring(holidayString(date));
310 if (hstring.isEmpty()) {
311 return KCalendarCore::Event::Ptr();
312 }
313
314 KCalendarCore::Event::Ptr holiday(new KCalendarCore::Event);
315 holiday->setSummary(hstring);
316 holiday->setCategories(i18n("Holiday"));
317
318 QDateTime kdt(date, QTime(0, 0), Qt::LocalTime);
319 holiday->setDtStart(kdt);
320 holiday->setDtEnd(kdt);
321 holiday->setAllDay(true);
322
323 return holiday;
324 }
325
headerHeight() const326 int CalPrintPluginBase::headerHeight() const
327 {
328 if (mHeaderHeight >= 0) {
329 return mHeaderHeight;
330 } else if (orientation() == QPageLayout::Portrait) {
331 return PORTRAIT_HEADER_HEIGHT;
332 } else {
333 return LANDSCAPE_HEADER_HEIGHT;
334 }
335 }
336
setHeaderHeight(const int height)337 void CalPrintPluginBase::setHeaderHeight(const int height)
338 {
339 mHeaderHeight = height;
340 }
341
subHeaderHeight() const342 int CalPrintPluginBase::subHeaderHeight() const
343 {
344 return mSubHeaderHeight;
345 }
346
setSubHeaderHeight(const int height)347 void CalPrintPluginBase::setSubHeaderHeight(const int height)
348 {
349 mSubHeaderHeight = height;
350 }
351
footerHeight() const352 int CalPrintPluginBase::footerHeight() const
353 {
354 if (!mPrintFooter) {
355 return 0;
356 }
357
358 if (mFooterHeight >= 0) {
359 return mFooterHeight;
360 } else if (orientation() == QPageLayout::Portrait) {
361 return PORTRAIT_FOOTER_HEIGHT;
362 } else {
363 return LANDSCAPE_FOOTER_HEIGHT;
364 }
365 }
366
setFooterHeight(const int height)367 void CalPrintPluginBase::setFooterHeight(const int height)
368 {
369 mFooterHeight = height;
370 }
371
margin() const372 int CalPrintPluginBase::margin() const
373 {
374 return mMargin;
375 }
376
setMargin(const int margin)377 void CalPrintPluginBase::setMargin(const int margin)
378 {
379 mMargin = margin;
380 }
381
padding() const382 int CalPrintPluginBase::padding() const
383 {
384 return mPadding;
385 }
386
setPadding(const int padding)387 void CalPrintPluginBase::setPadding(const int padding)
388 {
389 mPadding = padding;
390 }
391
borderWidth() const392 int CalPrintPluginBase::borderWidth() const
393 {
394 return mBorder;
395 }
396
setBorderWidth(const int borderwidth)397 void CalPrintPluginBase::setBorderWidth(const int borderwidth)
398 {
399 mBorder = borderwidth;
400 }
401
drawBox(QPainter & p,int linewidth,QRect rect)402 void CalPrintPluginBase::drawBox(QPainter &p, int linewidth, QRect rect)
403 {
404 QPen pen(p.pen());
405 QPen oldpen(pen);
406 // no border
407 if (linewidth >= 0) {
408 pen.setWidth(linewidth);
409 p.setPen(pen);
410 } else {
411 p.setPen(Qt::NoPen);
412 }
413 p.drawRect(rect);
414 p.setPen(oldpen);
415 }
416
drawShadedBox(QPainter & p,int linewidth,const QBrush & brush,QRect rect)417 void CalPrintPluginBase::drawShadedBox(QPainter &p, int linewidth, const QBrush &brush, QRect rect)
418 {
419 QBrush oldbrush(p.brush());
420 p.setBrush(brush);
421 drawBox(p, linewidth, rect);
422 p.setBrush(oldbrush);
423 }
424
printEventString(QPainter & p,QRect box,const QString & str,int flags)425 void CalPrintPluginBase::printEventString(QPainter &p, QRect box, const QString &str, int flags)
426 {
427 QRect newbox(box);
428 newbox.adjust(3, 1, -1, -1);
429 p.drawText(newbox, (flags == -1) ? (Qt::AlignTop | Qt::AlignLeft | Qt::TextWordWrap) : flags, str);
430 }
431
showEventBox(QPainter & p,int linewidth,QRect box,const KCalendarCore::Incidence::Ptr & incidence,const QString & str,int flags)432 void CalPrintPluginBase::showEventBox(QPainter &p, int linewidth, QRect box, const KCalendarCore::Incidence::Ptr &incidence, const QString &str, int flags)
433 {
434 QPen oldpen(p.pen());
435 QBrush oldbrush(p.brush());
436 QColor bgColor(categoryBgColor(incidence));
437 if (mUseColors && bgColor.isValid()) {
438 p.setBrush(bgColor);
439 } else {
440 p.setBrush(QColor(232, 232, 232));
441 }
442 drawBox(p, (linewidth > 0) ? linewidth : EVENT_BORDER_WIDTH, box);
443 if (mUseColors && bgColor.isValid()) {
444 p.setPen(getTextColor(bgColor));
445 }
446 printEventString(p, box, str, flags);
447 p.setPen(oldpen);
448 p.setBrush(oldbrush);
449 }
450
drawSubHeaderBox(QPainter & p,const QString & str,QRect box)451 void CalPrintPluginBase::drawSubHeaderBox(QPainter &p, const QString &str, QRect box)
452 {
453 drawShadedBox(p, BOX_BORDER_WIDTH, QColor(232, 232, 232), box);
454 QFont oldfont(p.font());
455 p.setFont(QFont(QStringLiteral("sans-serif"), 10, QFont::Bold));
456 p.drawText(box, Qt::AlignHCenter | Qt::AlignTop, str);
457 p.setFont(oldfont);
458 }
459
drawVerticalBox(QPainter & p,int linewidth,QRect box,const QString & str,int flags)460 void CalPrintPluginBase::drawVerticalBox(QPainter &p, int linewidth, QRect box, const QString &str, int flags)
461 {
462 p.save();
463 p.rotate(-90);
464 QRect rotatedBox(-box.top() - box.height(), box.left(), box.height(), box.width());
465 showEventBox(p, linewidth, rotatedBox, KCalendarCore::Incidence::Ptr(), str, (flags == -1) ? Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine : flags);
466
467 p.restore();
468 }
469
470 /*
471 * Return value: If expand, bottom of the printed box, otherwise vertical end
472 * of the printed contents inside the box.
473 */
drawBoxWithCaption(QPainter & p,QRect allbox,const QString & caption,const QString & contents,bool sameLine,bool expand,const QFont & captionFont,const QFont & textFont,bool richContents)474 int CalPrintPluginBase::drawBoxWithCaption(QPainter &p,
475 QRect allbox,
476 const QString &caption,
477 const QString &contents,
478 bool sameLine,
479 bool expand,
480 const QFont &captionFont,
481 const QFont &textFont,
482 bool richContents)
483 {
484 QFont oldFont(p.font());
485 // QFont captionFont( "sans-serif", 11, QFont::Bold );
486 // QFont textFont( "sans-serif", 11, QFont::Normal );
487 // QFont captionFont( "Tahoma", 11, QFont::Bold );
488 // QFont textFont( "Tahoma", 11, QFont::Normal );
489
490 QRect box(allbox);
491
492 // Bounding rectangle for caption, single-line, clip on the right
493 QRect captionBox(box.left() + padding(), box.top() + padding(), 0, 0);
494 p.setFont(captionFont);
495 captionBox = p.boundingRect(captionBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine, caption);
496 p.setFont(oldFont);
497 if (captionBox.right() > box.right()) {
498 captionBox.setRight(box.right());
499 }
500 if (expand && captionBox.bottom() + padding() > box.bottom()) {
501 box.setBottom(captionBox.bottom() + padding());
502 }
503
504 // Bounding rectangle for the contents (if any), word break, clip on the bottom
505 QRect textBox(captionBox);
506 if (!contents.isEmpty()) {
507 if (sameLine) {
508 textBox.setLeft(captionBox.right() + padding());
509 } else {
510 textBox.setTop(captionBox.bottom() + padding());
511 }
512 textBox.setRight(box.right());
513 }
514 drawBox(p, BOX_BORDER_WIDTH, box);
515 p.setFont(captionFont);
516 p.drawText(captionBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine, caption);
517
518 if (!contents.isEmpty()) {
519 if (sameLine) {
520 QString contentText = toPlainText(contents);
521 p.setFont(textFont);
522 p.drawText(textBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine, contentText);
523 } else {
524 QTextDocument rtb;
525 int borderWidth = 2 * BOX_BORDER_WIDTH;
526 if (richContents) {
527 rtb.setHtml(contents);
528 } else {
529 rtb.setPlainText(contents);
530 }
531 int boxHeight = allbox.height();
532 if (!sameLine) {
533 boxHeight -= captionBox.height();
534 }
535 rtb.setPageSize(QSize(textBox.width(), boxHeight));
536 rtb.setDefaultFont(textFont);
537 p.save();
538 p.translate(textBox.x() - borderWidth, textBox.y());
539 QRect clipBox(0, 0, box.width(), boxHeight);
540 QAbstractTextDocumentLayout::PaintContext ctx;
541 ctx.palette.setColor(QPalette::Text, p.pen().color());
542 p.setClipRect(clipBox);
543 ctx.clip = clipBox;
544 rtb.documentLayout()->draw(&p, ctx);
545 p.restore();
546 textBox.setBottom(textBox.y() + rtb.documentLayout()->documentSize().height());
547 }
548 }
549 p.setFont(oldFont);
550
551 if (expand) {
552 return box.bottom();
553 } else {
554 return textBox.bottom();
555 }
556 }
557
drawHeader(QPainter & p,const QString & title,QDate month1,QDate month2,QRect allbox,bool expand,QColor backColor)558 int CalPrintPluginBase::drawHeader(QPainter &p, const QString &title, QDate month1, QDate month2, QRect allbox, bool expand, QColor backColor)
559 {
560 // print previous month for month view, print current for to-do, day and week
561 int smallMonthWidth = (allbox.width() / 4) - 10;
562 if (smallMonthWidth > 100) {
563 smallMonthWidth = 100;
564 }
565
566 QRect box(allbox);
567 QRect textRect(allbox);
568
569 QFont oldFont(p.font());
570 QFont newFont(QStringLiteral("sans-serif"), (textRect.height() < 60) ? 16 : 18, QFont::Bold);
571 if (expand) {
572 p.setFont(newFont);
573 QRect boundingR = p.boundingRect(textRect, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, title);
574 p.setFont(oldFont);
575 int h = boundingR.height();
576 if (h > allbox.height()) {
577 box.setHeight(h);
578 textRect.setHeight(h);
579 }
580 }
581
582 if (!backColor.isValid()) {
583 backColor = QColor(232, 232, 232);
584 }
585
586 drawShadedBox(p, BOX_BORDER_WIDTH, backColor, box);
587
588 const auto oldPen {p.pen()};
589 p.setPen(getTextColor(backColor));
590
591 // prev month left, current month centered, next month right
592 QRect monthbox2(box.right() - 10 - smallMonthWidth, box.top(), smallMonthWidth, box.height());
593 if (month2.isValid()) {
594 drawSmallMonth(p, QDate(month2.year(), month2.month(), 1), monthbox2);
595 textRect.setRight(monthbox2.left());
596 }
597 QRect monthbox1(box.left() + 10, box.top(), smallMonthWidth, box.height());
598 if (month1.isValid()) {
599 drawSmallMonth(p, QDate(month1.year(), month1.month(), 1), monthbox1);
600 textRect.setLeft(monthbox1.right());
601 }
602
603 // Set the margins
604 p.setFont(newFont);
605 p.drawText(textRect, Qt::AlignCenter | Qt::AlignVCenter | Qt::TextWordWrap, title);
606
607 p.setPen(oldPen);
608 p.setFont(oldFont);
609
610 return textRect.bottom();
611 }
612
drawFooter(QPainter & p,QRect footbox)613 int CalPrintPluginBase::drawFooter(QPainter &p, QRect footbox)
614 {
615 QFont oldfont(p.font());
616 p.setFont(QFont(QStringLiteral("sans-serif"), 6));
617 QString dateStr = QLocale::system().toString(QDateTime::currentDateTime(), QLocale::LongFormat);
618 p.drawText(footbox, Qt::AlignCenter | Qt::AlignVCenter | Qt::TextSingleLine, i18nc("print date: formatted-datetime", "printed: %1", dateStr));
619 p.setFont(oldfont);
620
621 return footbox.bottom();
622 }
623
drawSmallMonth(QPainter & p,QDate qd,QRect box)624 void CalPrintPluginBase::drawSmallMonth(QPainter &p, QDate qd, QRect box)
625 {
626 int weekdayCol = weekdayColumn(qd.dayOfWeek());
627 int month = qd.month();
628 QDate monthDate(QDate(qd.year(), qd.month(), 1));
629 // correct begin of week
630 QDate monthDate2(monthDate.addDays(-weekdayCol));
631
632 double cellWidth = double(box.width()) / double(7);
633 int rownr = 3 + (qd.daysInMonth() + weekdayCol - 1) / 7;
634 // 3 Pixel after month name, 2 after day names, 1 after the calendar
635 double cellHeight = (box.height() - 5) / rownr;
636 QFont oldFont(p.font());
637 auto newFont = QFont(QStringLiteral("sans-serif"));
638 newFont.setPixelSize(cellHeight);
639 p.setFont(newFont);
640
641 // draw the title
642 QRect titleBox(box);
643 titleBox.setHeight(p.fontMetrics().height());
644 p.drawText(titleBox, Qt::AlignTop | Qt::AlignHCenter, QLocale::system().monthName(month));
645
646 // draw days of week
647 QRect wdayBox(box);
648 wdayBox.setTop(int(box.top() + 3 + cellHeight));
649 wdayBox.setHeight(int(2 * cellHeight) - int(cellHeight));
650
651 for (int col = 0; col < 7; ++col) {
652 QString tmpStr = QLocale::system().dayName(monthDate2.dayOfWeek())[0].toUpper();
653 wdayBox.setLeft(int(box.left() + col * cellWidth));
654 wdayBox.setRight(int(box.left() + (col + 1) * cellWidth));
655 p.drawText(wdayBox, Qt::AlignCenter, tmpStr);
656 monthDate2 = monthDate2.addDays(1);
657 }
658
659 // draw separator line
660 int calStartY = wdayBox.bottom() + 2;
661 p.drawLine(box.left(), calStartY, box.right(), calStartY);
662 monthDate = monthDate.addDays(-weekdayCol);
663
664 for (int row = 0; row < (rownr - 2); row++) {
665 for (int col = 0; col < 7; col++) {
666 if (monthDate.month() == month) {
667 QRect dayRect(int(box.left() + col * cellWidth), int(calStartY + row * cellHeight), 0, 0);
668 dayRect.setRight(int(box.left() + (col + 1) * cellWidth));
669 dayRect.setBottom(int(calStartY + (row + 1) * cellHeight));
670 p.drawText(dayRect, Qt::AlignCenter, QString::number(monthDate.day()));
671 }
672 monthDate = monthDate.addDays(1);
673 }
674 }
675 p.setFont(oldFont);
676 }
677
678 /*
679 * This routine draws a header box over the main part of the calendar
680 * containing the days of the week.
681 */
drawDaysOfWeek(QPainter & p,QDate fromDate,QDate toDate,QRect box)682 void CalPrintPluginBase::drawDaysOfWeek(QPainter &p, QDate fromDate, QDate toDate, QRect box)
683 {
684 double cellWidth = double(box.width() - 1) / double(fromDate.daysTo(toDate) + 1);
685 QDate cellDate(fromDate);
686 QRect dateBox(box);
687 int i = 0;
688
689 while (cellDate <= toDate) {
690 dateBox.setLeft(box.left() + int(i * cellWidth));
691 dateBox.setRight(box.left() + int((i + 1) * cellWidth));
692 drawDaysOfWeekBox(p, cellDate, dateBox);
693 cellDate = cellDate.addDays(1);
694 ++i;
695 }
696 }
697
drawDaysOfWeekBox(QPainter & p,QDate qd,QRect box)698 void CalPrintPluginBase::drawDaysOfWeekBox(QPainter &p, QDate qd, QRect box)
699 {
700 drawSubHeaderBox(p, QLocale::system().dayName(qd.dayOfWeek()), box);
701 }
702
drawTimeLine(QPainter & p,QTime fromTime,QTime toTime,QRect box)703 void CalPrintPluginBase::drawTimeLine(QPainter &p, QTime fromTime, QTime toTime, QRect box)
704 {
705 drawBox(p, BOX_BORDER_WIDTH, box);
706
707 int totalsecs = fromTime.secsTo(toTime);
708 float minlen = (float)box.height() * 60. / (float)totalsecs;
709 float cellHeight = (60. * (float)minlen);
710 float currY = box.top();
711 // TODO: Don't use half of the width, but less, for the minutes!
712 int xcenter = box.left() + box.width() / 2;
713
714 QTime curTime(fromTime);
715 QTime endTime(toTime);
716 if (fromTime.minute() > 30) {
717 curTime = QTime(fromTime.hour() + 1, 0, 0);
718 } else if (fromTime.minute() > 0) {
719 curTime = QTime(fromTime.hour(), 30, 0);
720 float yy = currY + minlen * (float)fromTime.secsTo(curTime) / 60.;
721 p.drawLine(xcenter, (int)yy, box.right(), (int)yy);
722 curTime = QTime(fromTime.hour() + 1, 0, 0);
723 }
724 currY += (float(fromTime.secsTo(curTime) * minlen) / 60.);
725
726 while (curTime < endTime) {
727 p.drawLine(box.left(), (int)currY, box.right(), (int)currY);
728 int newY = (int)(currY + cellHeight / 2.);
729 QString numStr;
730 if (newY < box.bottom()) {
731 QFont oldFont(p.font());
732 // draw the time:
733 if (!QLocale().timeFormat().contains(QLatin1String("AP"))) { // 12h clock
734 p.drawLine(xcenter, (int)newY, box.right(), (int)newY);
735 numStr.setNum(curTime.hour());
736 if (cellHeight > 30) {
737 p.setFont(QFont(QStringLiteral("sans-serif"), 14, QFont::Bold));
738 } else {
739 p.setFont(QFont(QStringLiteral("sans-serif"), 12, QFont::Bold));
740 }
741 p.drawText(box.left() + 4, (int)currY + 2, box.width() / 2 - 2, (int)cellHeight, Qt::AlignTop | Qt::AlignRight, numStr);
742 p.setFont(QFont(QStringLiteral("helvetica"), 10, QFont::Normal));
743 p.drawText(xcenter + 4, (int)currY + 2, box.width() / 2 + 2, (int)(cellHeight / 2) - 3, Qt::AlignTop | Qt::AlignLeft, QStringLiteral("00"));
744 } else {
745 p.drawLine(box.left(), (int)newY, box.right(), (int)newY);
746 QTime time(curTime.hour(), 0);
747 numStr = QLocale::system().toString(time, QLocale::ShortFormat);
748 if (box.width() < 60) {
749 p.setFont(QFont(QStringLiteral("sans-serif"), 7, QFont::Bold)); // for weekprint
750 } else {
751 p.setFont(QFont(QStringLiteral("sans-serif"), 12, QFont::Bold)); // for dayprint
752 }
753 p.drawText(box.left() + 2, (int)currY + 2, box.width() - 4, (int)cellHeight / 2 - 3, Qt::AlignTop | Qt::AlignLeft, numStr);
754 }
755 currY += cellHeight;
756 p.setFont(oldFont);
757 } // enough space for half-hour line and time
758 if (curTime.secsTo(endTime) > 3600) {
759 curTime = curTime.addSecs(3600);
760 } else {
761 curTime = endTime;
762 }
763 }
764 }
765
drawAgendaDayBox(QPainter & p,const KCalendarCore::Event::List & events,QDate qd,bool expandable,QTime fromTime,QTime toTime,QRect oldbox,bool includeDescription,bool includeCategories,bool excludeTime,const QList<QDate> & workDays)766 void CalPrintPluginBase::drawAgendaDayBox(QPainter &p,
767 const KCalendarCore::Event::List &events,
768 QDate qd,
769 bool expandable,
770 QTime fromTime,
771 QTime toTime,
772 QRect oldbox,
773 bool includeDescription,
774 bool includeCategories,
775 bool excludeTime,
776 const QList<QDate> &workDays)
777 {
778 QTime myFromTime;
779 QTime myToTime;
780 if (fromTime.isValid()) {
781 myFromTime = fromTime;
782 } else {
783 myFromTime = QTime(0, 0, 0);
784 }
785 if (toTime.isValid()) {
786 myToTime = toTime;
787 } else {
788 myToTime = QTime(23, 59, 59);
789 }
790
791 if (!workDays.contains(qd)) {
792 drawShadedBox(p, BOX_BORDER_WIDTH, sHolidayBackground, oldbox);
793 } else {
794 drawBox(p, BOX_BORDER_WIDTH, oldbox);
795 }
796 QRect box(oldbox);
797 // Account for the border with and cut away that margin from the interior
798 // box.setRight( box.right()-BOX_BORDER_WIDTH );
799
800 if (expandable) {
801 // Adapt start/end times to include complete events
802 for (const KCalendarCore::Event::Ptr &event : std::as_const(events)) {
803 Q_ASSERT(event);
804 if (!event
805 || (mExcludeConfidential && event->secrecy() == KCalendarCore::Incidence::SecrecyConfidential)
806 || (mExcludePrivate && event->secrecy() == KCalendarCore::Incidence::SecrecyPrivate)) {
807 continue;
808 }
809 // skip items without times so that we do not adjust for all day items
810 if (event->allDay()) {
811 continue;
812 }
813 if (event->dtStart().time() < myFromTime) {
814 myFromTime = event->dtStart().time();
815 }
816 if (event->dtEnd().time() > myToTime) {
817 myToTime = event->dtEnd().time();
818 }
819 }
820 }
821
822 // calculate the height of a cell and of a minute
823 int totalsecs = myFromTime.secsTo(myToTime);
824 float minlen = box.height() * 60. / totalsecs;
825 float cellHeight = 60. * minlen;
826 float currY = box.top();
827
828 // print grid:
829 QTime curTime(QTime(myFromTime.hour(), 0, 0));
830 currY += myFromTime.secsTo(curTime) * minlen / 60;
831
832 while (curTime < myToTime && curTime.isValid()) {
833 if (currY > box.top()) {
834 p.drawLine(box.left(), int(currY), box.right(), int(currY));
835 }
836 currY += cellHeight / 2;
837 if ((currY > box.top()) && (currY < box.bottom())) {
838 // enough space for half-hour line
839 QPen oldPen(p.pen());
840 p.setPen(QColor(192, 192, 192));
841 p.drawLine(box.left(), int(currY), box.right(), int(currY));
842 p.setPen(oldPen);
843 }
844 if (curTime.secsTo(myToTime) > 3600) {
845 curTime = curTime.addSecs(3600);
846 } else {
847 curTime = myToTime;
848 }
849 currY += cellHeight / 2;
850 }
851
852 QDateTime startPrintDate = QDateTime(qd, myFromTime);
853 QDateTime endPrintDate = QDateTime(qd, myToTime);
854
855 // Calculate horizontal positions and widths of events taking into account
856 // overlapping events
857
858 QList<CellItem *> cells;
859
860 for (const KCalendarCore::Event::Ptr &event : std::as_const(events)) {
861 if (!event
862 || (mExcludeConfidential && event->secrecy() == KCalendarCore::Incidence::SecrecyConfidential)
863 || (mExcludePrivate && event->secrecy() == KCalendarCore::Incidence::SecrecyPrivate)) {
864 continue;
865 }
866 if (event->allDay()) {
867 continue;
868 }
869 QList<QDateTime> times = event->startDateTimesForDate(qd, QTimeZone::systemTimeZone());
870 cells.reserve(times.count());
871 for (auto it = times.constBegin(); it != times.constEnd(); ++it) {
872 cells.append(new PrintCellItem(event, (*it).toLocalTime(), event->endDateForStart(*it).toLocalTime()));
873 }
874 }
875
876 QListIterator<CellItem *> it1(cells);
877 while (it1.hasNext()) {
878 CellItem *placeItem = it1.next();
879 CellItem::placeItem(cells, placeItem);
880 }
881
882 QListIterator<CellItem *> it2(cells);
883 while (it2.hasNext()) {
884 auto placeItem = static_cast<PrintCellItem *>(it2.next());
885 drawAgendaItem(placeItem, p, startPrintDate, endPrintDate, minlen, box, includeDescription, includeCategories, excludeTime);
886 }
887 }
888
drawAgendaItem(PrintCellItem * item,QPainter & p,const QDateTime & startPrintDate,const QDateTime & endPrintDate,float minlen,QRect box,bool includeDescription,bool includeCategories,bool excludeTime)889 void CalPrintPluginBase::drawAgendaItem(PrintCellItem *item,
890 QPainter &p,
891 const QDateTime &startPrintDate,
892 const QDateTime &endPrintDate,
893 float minlen,
894 QRect box,
895 bool includeDescription,
896 bool includeCategories,
897 bool excludeTime)
898 {
899 KCalendarCore::Event::Ptr event = item->event();
900
901 // start/end of print area for event
902 QDateTime startTime = item->start();
903 QDateTime endTime = item->end();
904 if ((startTime < endPrintDate && endTime > startPrintDate) || (endTime > startPrintDate && startTime < endPrintDate)) {
905 if (startTime < startPrintDate) {
906 startTime = startPrintDate;
907 }
908 if (endTime > endPrintDate) {
909 endTime = endPrintDate;
910 }
911 int currentWidth = box.width() / item->subCells();
912 int currentX = box.left() + item->subCell() * currentWidth;
913 int currentYPos = int(box.top() + startPrintDate.secsTo(startTime) * minlen / 60.);
914 int currentHeight = int(box.top() + startPrintDate.secsTo(endTime) * minlen / 60.) - currentYPos;
915
916 QRect eventBox(currentX, currentYPos, currentWidth, currentHeight);
917 QString str;
918 if (excludeTime) {
919 if (event->location().isEmpty()) {
920 str = cleanStr(event->summary());
921 } else {
922 str = i18nc("summary, location", "%1, %2", cleanStr(event->summary()), cleanStr(event->location()));
923 }
924 } else {
925 if (event->location().isEmpty()) {
926 str = i18nc("starttime - endtime summary",
927 "%1-%2 %3",
928 QLocale::system().toString(item->start().time(), QLocale::ShortFormat),
929 QLocale::system().toString(item->end().time(), QLocale::ShortFormat),
930 cleanStr(event->summary()));
931 } else {
932 str = i18nc("starttime - endtime summary, location",
933 "%1-%2 %3, %4",
934 QLocale::system().toString(item->start().time(), QLocale::ShortFormat),
935 QLocale::system().toString(item->end().time(), QLocale::ShortFormat),
936 cleanStr(event->summary()),
937 cleanStr(event->location()));
938 }
939 }
940 if (includeCategories && !event->categoriesStr().isEmpty()) {
941 str = i18nc("summary, categories", "%1, %2", str, event->categoriesStr());
942 }
943 if (includeDescription && !event->description().isEmpty()) {
944 str += QLatin1Char('\n');
945 if (event->descriptionIsRich()) {
946 str += toPlainText(event->description());
947 } else {
948 str += event->description();
949 }
950 }
951 QFont oldFont(p.font());
952 if (eventBox.height() < 24) {
953 if (eventBox.height() < 12) {
954 if (eventBox.height() < 8) {
955 p.setFont(QFont(QStringLiteral("sans-serif"), 4));
956 } else {
957 p.setFont(QFont(QStringLiteral("sans-serif"), 5));
958 }
959 } else {
960 p.setFont(QFont(QStringLiteral("sans-serif"), 6));
961 }
962 } else {
963 p.setFont(QFont(QStringLiteral("sans-serif"), 8));
964 }
965 showEventBox(p, EVENT_BORDER_WIDTH, eventBox, event, str);
966 p.setFont(oldFont);
967 }
968 }
969
drawDayBox(QPainter & p,QDate qd,QTime fromTime,QTime toTime,QRect box,bool fullDate,bool printRecurDaily,bool printRecurWeekly,bool singleLineLimit,bool includeDescription,bool includeCategories)970 void CalPrintPluginBase::drawDayBox(QPainter &p,
971 QDate qd,
972 QTime fromTime,
973 QTime toTime,
974 QRect box,
975 bool fullDate,
976 bool printRecurDaily,
977 bool printRecurWeekly,
978 bool singleLineLimit,
979 bool includeDescription,
980 bool includeCategories)
981 {
982 QString dayNumStr;
983 const auto local = QLocale::system();
984
985 QTime myFromTime;
986 QTime myToTime;
987 if (fromTime.isValid()) {
988 myFromTime = fromTime;
989 } else {
990 myFromTime = QTime(0, 0, 0);
991 }
992 if (toTime.isValid()) {
993 myToTime = toTime;
994 } else {
995 myToTime = QTime(23, 59, 59);
996 }
997
998 if (fullDate) {
999 dayNumStr = i18nc("weekday, shortmonthname daynumber",
1000 "%1, %2 %3",
1001 QLocale::system().dayName(qd.dayOfWeek()),
1002 QLocale::system().monthName(qd.month(), QLocale::ShortFormat),
1003 QString::number(qd.day()));
1004 } else {
1005 dayNumStr = QString::number(qd.day());
1006 }
1007
1008 QRect subHeaderBox(box);
1009 subHeaderBox.setHeight(mSubHeaderHeight);
1010 drawShadedBox(p, BOX_BORDER_WIDTH, p.background(), box);
1011 drawShadedBox(p, 0, QColor(232, 232, 232), subHeaderBox);
1012 drawBox(p, BOX_BORDER_WIDTH, box);
1013 QString hstring(holidayString(qd));
1014 const QFont oldFont(p.font());
1015
1016 QRect headerTextBox(subHeaderBox.adjusted(5, 0, -5, 0));
1017 p.setFont(QFont(QStringLiteral("sans-serif"), 10, QFont::Bold));
1018 QRect dayNumRect;
1019 p.drawText(headerTextBox, Qt::AlignRight | Qt::AlignVCenter, dayNumStr, &dayNumRect);
1020 if (!hstring.isEmpty()) {
1021 p.setFont(QFont(QStringLiteral("sans-serif"), 8, QFont::Bold, true));
1022 QFontMetrics fm(p.font());
1023 hstring = fm.elidedText(hstring, Qt::ElideRight, headerTextBox.width() - dayNumRect.width() - 5);
1024 p.drawText(headerTextBox, Qt::AlignLeft | Qt::AlignVCenter, hstring);
1025 p.setFont(QFont(QStringLiteral("sans-serif"), 10, QFont::Bold));
1026 }
1027
1028 const KCalendarCore::Event::List eventList =
1029 mCalendar->events(qd, QTimeZone::systemTimeZone(), KCalendarCore::EventSortStartDate, KCalendarCore::SortDirectionAscending);
1030
1031 QString timeText;
1032 p.setFont(QFont(QStringLiteral("sans-serif"), 7));
1033
1034 int textY = mSubHeaderHeight; // gives the relative y-coord of the next printed entry
1035 unsigned int visibleEventsCounter = 0;
1036 for (const KCalendarCore::Event::Ptr &currEvent : std::as_const(eventList)) {
1037 Q_ASSERT(currEvent);
1038 if (!currEvent->allDay()) {
1039 if (currEvent->dtEnd().toLocalTime().time() <= myFromTime || currEvent->dtStart().toLocalTime().time() > myToTime) {
1040 continue;
1041 }
1042 }
1043 if ((!printRecurDaily && currEvent->recurrenceType() == KCalendarCore::Recurrence::rDaily)
1044 || (!printRecurWeekly && currEvent->recurrenceType() == KCalendarCore::Recurrence::rWeekly)) {
1045 continue;
1046 }
1047 if ((mExcludeConfidential && currEvent->secrecy() == KCalendarCore::Incidence::SecrecyConfidential)
1048 || (mExcludePrivate && currEvent->secrecy() == KCalendarCore::Incidence::SecrecyPrivate)) {
1049 continue;
1050 }
1051 if (currEvent->allDay() || currEvent->isMultiDay()) {
1052 timeText.clear();
1053 } else {
1054 timeText = local.toString(currEvent->dtStart().toLocalTime().time(), QLocale::ShortFormat) + QLatin1Char(' ');
1055 }
1056 p.save();
1057 if (mUseColors) {
1058 setColorsByIncidenceCategory(p, currEvent);
1059 }
1060 QString summaryStr = currEvent->summary();
1061 if (!currEvent->location().isEmpty()) {
1062 summaryStr = i18nc("summary, location", "%1, %2", summaryStr, currEvent->location());
1063 }
1064 if (includeCategories && !currEvent->categoriesStr().isEmpty()) {
1065 summaryStr = i18nc("summary, categories", "%1, %2", summaryStr, currEvent->categoriesStr());
1066 }
1067 drawIncidence(p, box, timeText, summaryStr, currEvent->description(), textY, singleLineLimit, includeDescription, currEvent->descriptionIsRich());
1068 p.restore();
1069 visibleEventsCounter++;
1070
1071 if (textY >= box.height()) {
1072 const QChar downArrow(0x21e3);
1073
1074 const unsigned int invisibleIncidences = (eventList.count() - visibleEventsCounter) + mCalendar->todos(qd).count();
1075 if (invisibleIncidences > 0) {
1076 const QString warningMsg = QStringLiteral("%1 (%2)").arg(downArrow).arg(invisibleIncidences);
1077
1078 QFontMetrics fm(p.font());
1079 QRect msgRect = fm.boundingRect(warningMsg);
1080 msgRect.setRect(box.right() - msgRect.width() - 2, box.bottom() - msgRect.height() - 2, msgRect.width(), msgRect.height());
1081
1082 p.save();
1083 p.setPen(Qt::red); // krazy:exclude=qenums we don't allow custom print colors
1084 p.drawText(msgRect, Qt::AlignLeft, warningMsg);
1085 p.restore();
1086 }
1087 break;
1088 }
1089 }
1090
1091 if (textY < box.height()) {
1092 KCalendarCore::Todo::List todos = mCalendar->todos(qd);
1093 for (const KCalendarCore::Todo::Ptr &todo : std::as_const(todos)) {
1094 if (!todo->allDay()) {
1095 if ((todo->hasDueDate() && todo->dtDue().toLocalTime().time() <= myFromTime)
1096 || (todo->hasStartDate() && todo->dtStart().toLocalTime().time() > myToTime)) {
1097 continue;
1098 }
1099 }
1100 if ((!printRecurDaily && todo->recurrenceType() == KCalendarCore::Recurrence::rDaily)
1101 || (!printRecurWeekly && todo->recurrenceType() == KCalendarCore::Recurrence::rWeekly)) {
1102 continue;
1103 }
1104 if ((mExcludeConfidential && todo->secrecy() == KCalendarCore::Incidence::SecrecyConfidential)
1105 || (mExcludePrivate && todo->secrecy() == KCalendarCore::Incidence::SecrecyPrivate)) {
1106 continue;
1107 }
1108 if (todo->hasStartDate() && !todo->allDay()) {
1109 timeText = QLocale().toString(todo->dtStart().toLocalTime().time(), QLocale::ShortFormat) + QLatin1Char(' ');
1110 } else {
1111 timeText.clear();
1112 }
1113 p.save();
1114 if (mUseColors) {
1115 setColorsByIncidenceCategory(p, todo);
1116 }
1117 QString summaryStr = todo->summary();
1118 if (!todo->location().isEmpty()) {
1119 summaryStr = i18nc("summary, location", "%1, %2", summaryStr, todo->location());
1120 }
1121
1122 QString str;
1123 if (todo->hasDueDate()) {
1124 if (!todo->allDay()) {
1125 str = i18nc("to-do summary (Due: datetime)",
1126 "%1 (Due: %2)",
1127 summaryStr,
1128 QLocale().toString(todo->dtDue().toLocalTime(), QLocale::ShortFormat));
1129 } else {
1130 str = i18nc("to-do summary (Due: date)",
1131 "%1 (Due: %2)",
1132 summaryStr,
1133 QLocale().toString(todo->dtDue().toLocalTime().date(), QLocale::ShortFormat));
1134 }
1135 } else {
1136 str = summaryStr;
1137 }
1138 drawIncidence(p, box, timeText, i18n("To-do: %1", str), todo->description(), textY, singleLineLimit, includeDescription, todo->descriptionIsRich());
1139 p.restore();
1140 }
1141 }
1142 if (mShowNoteLines) {
1143 drawNoteLines(p, box, box.y() + textY);
1144 }
1145
1146 p.setFont(oldFont);
1147 }
1148
drawIncidence(QPainter & p,QRect dayBox,const QString & time,const QString & summary,const QString & description,int & textY,bool singleLineLimit,bool includeDescription,bool richDescription)1149 void CalPrintPluginBase::drawIncidence(QPainter &p,
1150 QRect dayBox,
1151 const QString &time,
1152 const QString &summary,
1153 const QString &description,
1154 int &textY,
1155 bool singleLineLimit,
1156 bool includeDescription,
1157 bool richDescription)
1158 {
1159 qCDebug(CALENDARSUPPORT_LOG) << "summary =" << summary << ", singleLineLimit=" << singleLineLimit;
1160
1161 int flags = Qt::AlignLeft | Qt::OpaqueMode;
1162 QFontMetrics fm = p.fontMetrics();
1163 const int borderWidth = p.pen().width() + 1;
1164
1165 QString firstLine {time};
1166 if (!firstLine.isEmpty()) {
1167 firstLine += QStringLiteral(" ");
1168 }
1169 firstLine += summary;
1170
1171 if (singleLineLimit) {
1172 if (includeDescription && !description.isEmpty()) {
1173 firstLine += QStringLiteral(". ") + toPlainText(description);
1174 }
1175
1176 int totalHeight = fm.height() + borderWidth;
1177 int textBoxHeight = (totalHeight > (dayBox.height() - textY)) ? dayBox.height() - textY : totalHeight;
1178 QRect boxRect(dayBox.x() + p.pen().width(), dayBox.y() + textY, dayBox.width(), textBoxHeight);
1179 drawBox(p, 1, boxRect);
1180 p.drawText(boxRect.adjusted(3, 0, -3, 0), flags, firstLine);
1181 textY += textBoxHeight;
1182 } else {
1183 QTextDocument textDoc;
1184 QTextCursor textCursor(&textDoc);
1185 textCursor.insertText(firstLine);
1186 if (includeDescription && !description.isEmpty()) {
1187 textCursor.insertText(QStringLiteral("\n"));
1188 if (richDescription) {
1189 textCursor.insertHtml(description);
1190 } else {
1191 textCursor.insertText(toPlainText(description));
1192 }
1193 }
1194
1195 QRect textBox = QRect(dayBox.x(), dayBox.y() + textY + 1, dayBox.width(), dayBox.height() - textY);
1196 textDoc.setPageSize(QSize(textBox.width(), textBox.height()));
1197
1198 textBox.setHeight(textDoc.documentLayout()->documentSize().height());
1199 if (textBox.bottom() > dayBox.bottom()) {
1200 textBox.setBottom(dayBox.bottom());
1201 }
1202
1203 QRect boxRext(dayBox.x() + p.pen().width(), dayBox.y() + textY, dayBox.width(), textBox.height());
1204 drawBox(p, 1, boxRext);
1205
1206 QRect clipRect(0, 0, textBox.width(), textBox.height());
1207 QAbstractTextDocumentLayout::PaintContext ctx;
1208 ctx.palette.setColor(QPalette::Text, p.pen().color());
1209 ctx.clip = clipRect;
1210 p.save();
1211 p.translate(textBox.x(), textBox.y());
1212 p.setClipRect(clipRect);
1213 textDoc.documentLayout()->draw(&p, ctx);
1214 p.restore();
1215
1216 textY += textBox.height();
1217
1218 if (textDoc.pageCount() > 1) {
1219 // show that we have overflowed the box
1220 QPolygon poly(3);
1221 int x = dayBox.x() + dayBox.width();
1222 int y = dayBox.y() + dayBox.height();
1223 poly.setPoint(0, x - 10, y);
1224 poly.setPoint(1, x, y - 10);
1225 poly.setPoint(2, x, y);
1226 QBrush oldBrush(p.brush());
1227 p.setBrush(QBrush(Qt::black));
1228 p.drawPolygon(poly);
1229 p.setBrush(oldBrush);
1230 textY = dayBox.height();
1231 }
1232 }
1233 }
1234
1235 class MonthEventStruct
1236 {
1237 public:
MonthEventStruct()1238 MonthEventStruct()
1239 : event(nullptr)
1240 {
1241 }
1242
MonthEventStruct(const QDateTime & s,const QDateTime & e,const KCalendarCore::Event::Ptr & ev)1243 MonthEventStruct(const QDateTime &s, const QDateTime &e, const KCalendarCore::Event::Ptr &ev)
1244 {
1245 event = ev;
1246 start = s;
1247 end = e;
1248 if (event->allDay()) {
1249 start = QDateTime(start.date(), QTime(0, 0, 0));
1250 end = QDateTime(end.date().addDays(1), QTime(0, 0, 0)).addSecs(-1);
1251 }
1252 }
1253
operator <(const MonthEventStruct & mes)1254 bool operator<(const MonthEventStruct &mes)
1255 {
1256 return start < mes.start;
1257 }
1258
1259 QDateTime start;
1260 QDateTime end;
1261 KCalendarCore::Event::Ptr event;
1262 };
1263
drawMonth(QPainter & p,QDate dt,QRect box,int maxdays,int subDailyFlags,int holidaysFlags)1264 void CalPrintPluginBase::drawMonth(QPainter &p,
1265 QDate dt,
1266 QRect box,
1267 int maxdays,
1268 int subDailyFlags,
1269 int holidaysFlags)
1270 {
1271 p.save();
1272 QRect subheaderBox(box);
1273 subheaderBox.setHeight(subHeaderHeight());
1274 QRect borderBox(box);
1275 borderBox.setTop(subheaderBox.bottom() + 1);
1276 drawSubHeaderBox(p, QLocale::system().monthName(dt.month()), subheaderBox);
1277 // correct for half the border width
1278 int correction = (BOX_BORDER_WIDTH /*-1*/) / 2;
1279 QRect daysBox(borderBox);
1280 daysBox.adjust(correction, correction, -correction, -correction);
1281
1282 int daysinmonth = dt.daysInMonth();
1283 if (maxdays <= 0) {
1284 maxdays = daysinmonth;
1285 }
1286
1287 float dayheight = float(daysBox.height()) / float(maxdays);
1288
1289 QColor holidayColor(240, 240, 240);
1290 QColor workdayColor(255, 255, 255);
1291 int dayNrWidth = p.fontMetrics().boundingRect(QStringLiteral("99")).width();
1292
1293 // Fill the remaining space (if a month has less days than others) with a crossed-out pattern
1294 if (daysinmonth < maxdays) {
1295 QRect dayBox(box.left(), daysBox.top() + qRound(dayheight * daysinmonth), box.width(), 0);
1296 dayBox.setBottom(daysBox.bottom());
1297 p.fillRect(dayBox, Qt::DiagCrossPattern);
1298 }
1299 // Backgrounded boxes for each day, plus day numbers
1300 QBrush oldbrush(p.brush());
1301
1302 QList<QDate> workDays;
1303
1304 {
1305 QDate startDate(dt.year(), dt.month(), 1);
1306 QDate endDate(dt.year(), dt.month(), daysinmonth);
1307
1308 workDays = CalendarSupport::workDays(startDate, endDate);
1309 }
1310
1311 for (int d = 0; d < daysinmonth; ++d) {
1312 QDate day(dt.year(), dt.month(), d + 1);
1313 QRect dayBox(daysBox.left() /*+rand()%50*/, daysBox.top() + qRound(dayheight * d), daysBox.width() /*-rand()%50*/, 0);
1314 // FIXME: When using a border width of 0 for event boxes,
1315 // don't let the rectangles overlap, i.e. subtract 1 from the top or bottom!
1316 dayBox.setBottom(daysBox.top() + qRound(dayheight * (d + 1)) - 1);
1317
1318 p.setBrush(workDays.contains(day) ? workdayColor : holidayColor);
1319 p.drawRect(dayBox);
1320 QRect dateBox(dayBox);
1321 dateBox.setWidth(dayNrWidth + 3);
1322 p.drawText(dateBox, Qt::AlignRight | Qt::AlignVCenter | Qt::TextSingleLine, QString::number(d + 1));
1323 }
1324 p.setBrush(oldbrush);
1325 int xstartcont = box.left() + dayNrWidth + 5;
1326
1327 QDate start(dt.year(), dt.month(), 1);
1328 QDate end = start.addMonths(1);
1329 end = end.addDays(-1);
1330
1331 const KCalendarCore::Event::List events = mCalendar->events(start, end);
1332 QMap<int, QStringList> textEvents;
1333 QList<CellItem *> timeboxItems;
1334
1335 // 1) For multi-day events, show boxes spanning several cells, use CellItem
1336 // print the summary vertically
1337 // 2) For sub-day events, print the concated summaries into the remaining
1338 // space of the box (optional, depending on the given flags)
1339 // 3) Draw some kind of timeline showing free and busy times
1340
1341 // Holidays
1342 // QList<KCalendarCore::Event::Ptr> holidays;
1343 for (QDate d(start); d <= end; d = d.addDays(1)) {
1344 KCalendarCore::Event::Ptr e = holidayEvent(d);
1345 if (e) {
1346 // holidays.append(e);
1347 if (holidaysFlags & TimeBoxes) {
1348 timeboxItems.append(new PrintCellItem(e, QDateTime(d, QTime(0, 0, 0)), QDateTime(d.addDays(1), QTime(0, 0, 0))));
1349 }
1350 if (holidaysFlags & Text) {
1351 textEvents[d.day()] << e->summary();
1352 }
1353 }
1354 }
1355
1356 QVector<MonthEventStruct> monthentries;
1357
1358 for (const KCalendarCore::Event::Ptr &e : std::as_const(events)) {
1359 if (!e
1360 || (mExcludeConfidential && e->secrecy() == KCalendarCore::Incidence::SecrecyConfidential)
1361 || (mExcludePrivate && e->secrecy() == KCalendarCore::Incidence::SecrecyPrivate)) {
1362 continue;
1363 }
1364 if (e->recurs()) {
1365 if (e->recursOn(start, QTimeZone::systemTimeZone())) {
1366 // This occurrence has possibly started before the beginning of the
1367 // month, so obtain the start date before the beginning of the month
1368 QList<QDateTime> starttimes = e->startDateTimesForDate(start, QTimeZone::systemTimeZone());
1369 for (auto it = starttimes.constBegin(); it != starttimes.constEnd(); ++it) {
1370 monthentries.append(MonthEventStruct((*it).toLocalTime(), e->endDateForStart(*it).toLocalTime(), e));
1371 }
1372 }
1373 // Loop through all remaining days of the month and check if the event
1374 // begins on that day (don't use Event::recursOn, as that will
1375 // also return events that have started earlier. These start dates
1376 // however, have already been treated!
1377 KCalendarCore::Recurrence *recur = e->recurrence();
1378 QDate d1(start.addDays(1));
1379 while (d1 <= end) {
1380 if (recur->recursOn(d1, QTimeZone::systemTimeZone())) {
1381 KCalendarCore::TimeList times(recur->recurTimesOn(d1, QTimeZone::systemTimeZone()));
1382 for (KCalendarCore::TimeList::ConstIterator it = times.constBegin(); it != times.constEnd(); ++it) {
1383 QDateTime d1start(d1, *it, Qt::LocalTime);
1384 monthentries.append(MonthEventStruct(d1start, e->endDateForStart(d1start).toLocalTime(), e));
1385 }
1386 }
1387 d1 = d1.addDays(1);
1388 }
1389 } else {
1390 monthentries.append(MonthEventStruct(e->dtStart().toLocalTime(), e->dtEnd().toLocalTime(), e));
1391 }
1392 }
1393
1394 // TODO: to port the month entries sorting
1395
1396 // qSort( monthentries.begin(), monthentries.end() );
1397
1398 QVector<MonthEventStruct>::ConstIterator mit = monthentries.constBegin();
1399 QDateTime endofmonth(end, QTime(0, 0, 0));
1400 endofmonth = endofmonth.addDays(1);
1401 for (; mit != monthentries.constEnd(); ++mit) {
1402 if ((*mit).start.date() == (*mit).end.date()) {
1403 // Show also single-day events as time line boxes
1404 if (subDailyFlags & TimeBoxes) {
1405 timeboxItems.append(new PrintCellItem((*mit).event, (*mit).start, (*mit).end));
1406 }
1407 // Show as text in the box
1408 if (subDailyFlags & Text) {
1409 textEvents[(*mit).start.date().day()] << (*mit).event->summary();
1410 }
1411 } else {
1412 // Multi-day events are always shown as time line boxes
1413 QDateTime thisstart((*mit).start);
1414 QDateTime thisend((*mit).end);
1415 if (thisstart.date() < start) {
1416 thisstart.setDate(start);
1417 }
1418 if (thisend > endofmonth) {
1419 thisend = endofmonth;
1420 }
1421 timeboxItems.append(new PrintCellItem((*mit).event, thisstart, thisend));
1422 }
1423 }
1424
1425 // For Multi-day events, line them up nicely so that the boxes don't overlap
1426 QListIterator<CellItem *> it1(timeboxItems);
1427 while (it1.hasNext()) {
1428 CellItem *placeItem = it1.next();
1429 CellItem::placeItem(timeboxItems, placeItem);
1430 }
1431 QDateTime starttime(start, QTime(0, 0, 0));
1432 int newxstartcont = xstartcont;
1433
1434 QFont oldfont(p.font());
1435 p.setFont(QFont(QStringLiteral("sans-serif"), 7));
1436 while (it1.hasNext()) {
1437 auto placeItem = static_cast<PrintCellItem *>(it1.next());
1438 int minsToStart = starttime.secsTo(placeItem->start()) / 60;
1439 int minsToEnd = starttime.secsTo(placeItem->end()) / 60;
1440
1441 QRect eventBox(xstartcont + placeItem->subCell() * 17,
1442 daysBox.top() + qRound(double(minsToStart * daysBox.height()) / double(maxdays * 24 * 60)),
1443 14,
1444 0);
1445 eventBox.setBottom(daysBox.top() + qRound(double(minsToEnd * daysBox.height()) / double(maxdays * 24 * 60)));
1446 drawVerticalBox(p, 0, eventBox, placeItem->event()->summary());
1447 newxstartcont = qMax(newxstartcont, eventBox.right());
1448 }
1449 xstartcont = newxstartcont;
1450
1451 // For Single-day events, simply print their summaries into the remaining
1452 // space of the day's cell
1453 for (int d = 0; d < daysinmonth; ++d) {
1454 QStringList dayEvents(textEvents[d + 1]);
1455 QString txt = dayEvents.join(QLatin1String(", "));
1456 QRect dayBox(xstartcont, daysBox.top() + qRound(dayheight * d), 0, 0);
1457 dayBox.setRight(box.right());
1458 dayBox.setBottom(daysBox.top() + qRound(dayheight * (d + 1)));
1459 printEventString(p, dayBox, txt, Qt::AlignTop | Qt::AlignLeft | Qt::TextWrapAnywhere);
1460 }
1461 p.setFont(oldfont);
1462 drawBox(p, BOX_BORDER_WIDTH, borderBox);
1463 p.restore();
1464 }
1465
drawMonthTable(QPainter & p,QDate qd,QTime fromTime,QTime toTime,bool weeknumbers,bool recurDaily,bool recurWeekly,bool singleLineLimit,bool includeDescription,bool includeCategories,QRect box)1466 void CalPrintPluginBase::drawMonthTable(QPainter &p,
1467 QDate qd,
1468 QTime fromTime,
1469 QTime toTime,
1470 bool weeknumbers,
1471 bool recurDaily,
1472 bool recurWeekly,
1473 bool singleLineLimit,
1474 bool includeDescription,
1475 bool includeCategories,
1476 QRect box)
1477 {
1478 int yoffset = mSubHeaderHeight;
1479 int xoffset = 0;
1480 QDate monthDate(QDate(qd.year(), qd.month(), 1));
1481 QDate monthFirst(monthDate);
1482 QDate monthLast(monthDate.addMonths(1).addDays(-1));
1483
1484 int weekdayCol = weekdayColumn(monthDate.dayOfWeek());
1485 monthDate = monthDate.addDays(-weekdayCol);
1486
1487 if (weeknumbers) {
1488 xoffset += 14;
1489 }
1490
1491 int rows = (weekdayCol + qd.daysInMonth() - 1) / 7 + 1;
1492 double cellHeight = (box.height() - yoffset) / (1. * rows);
1493 double cellWidth = (box.width() - xoffset) / 7.;
1494
1495 // Precalculate the grid...
1496 // rows is at most 6, so using 8 entries in the array is fine, too!
1497 int coledges[8];
1498 int rowedges[8];
1499 for (int i = 0; i <= 7; ++i) {
1500 rowedges[i] = int(box.top() + yoffset + i * cellHeight);
1501 coledges[i] = int(box.left() + xoffset + i * cellWidth);
1502 }
1503
1504 if (weeknumbers) {
1505 QFont oldFont(p.font());
1506 QFont newFont(p.font());
1507 newFont.setPointSize(6);
1508 p.setFont(newFont);
1509 QDate weekDate(monthDate);
1510 for (int row = 0; row < rows; ++row) {
1511 int calWeek = weekDate.weekNumber();
1512 QRect rc(box.left(), rowedges[row], coledges[0] - 3 - box.left(), rowedges[row + 1] - rowedges[row]);
1513 p.drawText(rc, Qt::AlignRight | Qt::AlignVCenter, QString::number(calWeek));
1514 weekDate = weekDate.addDays(7);
1515 }
1516 p.setFont(oldFont);
1517 }
1518
1519 QRect daysOfWeekBox(box);
1520 daysOfWeekBox.setHeight(mSubHeaderHeight);
1521 daysOfWeekBox.setLeft(box.left() + xoffset);
1522 drawDaysOfWeek(p, monthDate, monthDate.addDays(6), daysOfWeekBox);
1523
1524 QColor back = p.background().color();
1525 bool darkbg = false;
1526 for (int row = 0; row < rows; ++row) {
1527 for (int col = 0; col < 7; ++col) {
1528 // show days from previous/next month with a grayed background
1529 if ((monthDate < monthFirst) || (monthDate > monthLast)) {
1530 p.setBackground(back.darker(120));
1531 darkbg = true;
1532 }
1533 QRect dayBox(coledges[col], rowedges[row], coledges[col + 1] - coledges[col], rowedges[row + 1] - rowedges[row]);
1534 drawDayBox(p,
1535 monthDate,
1536 fromTime,
1537 toTime,
1538 dayBox,
1539 false,
1540 recurDaily,
1541 recurWeekly,
1542 singleLineLimit,
1543 includeDescription,
1544 includeCategories);
1545 if (darkbg) {
1546 p.setBackground(back);
1547 darkbg = false;
1548 }
1549 monthDate = monthDate.addDays(1);
1550 }
1551 }
1552 }
1553
drawTodoLines(QPainter & p,const QString & entry,int x,int & y,int width,int pageHeight,bool richTextEntry,QList<TodoParentStart * > & startPoints,bool connectSubTodos)1554 void CalPrintPluginBase::drawTodoLines(QPainter &p,
1555 const QString &entry,
1556 int x,
1557 int &y,
1558 int width,
1559 int pageHeight,
1560 bool richTextEntry,
1561 QList<TodoParentStart *> &startPoints,
1562 bool connectSubTodos)
1563 {
1564 QString plainEntry = (richTextEntry) ? toPlainText(entry) : entry;
1565
1566 QRect textrect(0, 0, width, -1);
1567 int flags = Qt::AlignLeft;
1568 QFontMetrics fm = p.fontMetrics();
1569
1570 QStringList lines = plainEntry.split(QLatin1Char('\n'));
1571 for (int currentLine = 0; currentLine < lines.count(); currentLine++) {
1572 // split paragraphs into lines
1573 KWordWrap ww = KWordWrap::formatText(fm, textrect, flags, lines[currentLine]);
1574 QStringList textLine = ww.wrappedString().split(QLatin1Char('\n'));
1575
1576 // print each individual line
1577 for (int lineCount = 0; lineCount < textLine.count(); lineCount++) {
1578 if (y >= pageHeight) {
1579 if (connectSubTodos) {
1580 for (int i = 0; i < startPoints.size(); ++i) {
1581 TodoParentStart *rct;
1582 rct = startPoints.at(i);
1583 int start = rct->mRect.bottom() + 1;
1584 int center = rct->mRect.left() + (rct->mRect.width() / 2);
1585 int to = y;
1586 if (!rct->mSamePage) {
1587 start = 0;
1588 }
1589 if (rct->mHasLine) {
1590 p.drawLine(center, start, center, to);
1591 }
1592 rct->mSamePage = false;
1593 }
1594 }
1595 y = 0;
1596 mPrinter->newPage();
1597 }
1598 y += fm.height();
1599 p.drawText(x, y, textLine[lineCount]);
1600 }
1601 }
1602 }
1603
drawTodo(int & count,const KCalendarCore::Todo::Ptr & todo,QPainter & p,KCalendarCore::TodoSortField sortField,KCalendarCore::SortDirection sortDir,bool connectSubTodos,bool strikeoutCompleted,bool desc,int posPriority,int posSummary,int posCategories,int posStartDt,int posDueDt,int posPercentComplete,int level,int x,int & y,int width,int pageHeight,const KCalendarCore::Todo::List & todoList,TodoParentStart * r)1604 void CalPrintPluginBase::drawTodo(int &count,
1605 const KCalendarCore::Todo::Ptr &todo,
1606 QPainter &p,
1607 KCalendarCore::TodoSortField sortField,
1608 KCalendarCore::SortDirection sortDir,
1609 bool connectSubTodos,
1610 bool strikeoutCompleted,
1611 bool desc,
1612 int posPriority,
1613 int posSummary,
1614 int posCategories,
1615 int posStartDt,
1616 int posDueDt,
1617 int posPercentComplete,
1618 int level,
1619 int x,
1620 int &y,
1621 int width,
1622 int pageHeight,
1623 const KCalendarCore::Todo::List &todoList,
1624 TodoParentStart *r)
1625 {
1626 QString outStr;
1627 const auto locale = QLocale::system();
1628 QRect rect;
1629 TodoParentStart startpt;
1630 // This list keeps all starting points of the parent to-dos so the connection
1631 // lines of the tree can easily be drawn (needed if a new page is started)
1632 static QList<TodoParentStart *> startPoints;
1633 if (level < 1) {
1634 startPoints.clear();
1635 }
1636
1637 y += 10;
1638
1639 int left = posSummary + (level * 10);
1640
1641 // If this is a sub-to-do, r will not be 0, and we want the LH side
1642 // of the priority line up to the RH side of the parent to-do's priority
1643 int lhs = posPriority;
1644 if (r) {
1645 lhs = r->mRect.right() + 1;
1646 }
1647
1648 outStr.setNum(todo->priority());
1649 rect = p.boundingRect(lhs, y + 10, 5, -1, Qt::AlignCenter, outStr);
1650 // Make it a more reasonable size
1651 rect.setWidth(18);
1652 rect.setHeight(18);
1653 const int top = rect.top();
1654
1655 // Draw a checkbox
1656 p.setBrush(QBrush(Qt::NoBrush));
1657 p.drawRect(rect);
1658 if (todo->isCompleted()) {
1659 // cross out the rectangle for completed to-dos
1660 p.drawLine(rect.topLeft(), rect.bottomRight());
1661 p.drawLine(rect.topRight(), rect.bottomLeft());
1662 }
1663 lhs = rect.right() + 5;
1664
1665 // Priority
1666 if (posPriority >= 0 && todo->priority() > 0) {
1667 p.drawText(rect, Qt::AlignCenter, outStr);
1668 }
1669 startpt.mRect = rect; // save for later
1670
1671 // Connect the dots
1672 if (r && level > 0 && connectSubTodos) {
1673 int bottom;
1674 int center(r->mRect.left() + (r->mRect.width() / 2));
1675 int to(rect.top() + (rect.height() / 2));
1676 int endx(rect.left());
1677 p.drawLine(center, to, endx, to); // side connector
1678 if (r->mSamePage) {
1679 bottom = r->mRect.bottom() + 1;
1680 } else {
1681 bottom = 0;
1682 }
1683 p.drawLine(center, bottom, center, to);
1684 }
1685
1686 int posSoFar = width; // Position of leftmost optional field.
1687
1688 // due date
1689 if (posDueDt >= 0 && todo->hasDueDate()) {
1690 outStr = locale.toString(todo->dtDue().toLocalTime().date(), QLocale::ShortFormat);
1691 rect = p.boundingRect(posDueDt, top, x + width, -1, Qt::AlignTop | Qt::AlignLeft, outStr);
1692 p.drawText(rect, Qt::AlignTop | Qt::AlignLeft, outStr);
1693 posSoFar = posDueDt;
1694 }
1695
1696 // start date
1697 if (posStartDt >= 0 && todo->hasStartDate()) {
1698 outStr = locale.toString(todo->dtStart().toLocalTime().date(), QLocale::ShortFormat);
1699 rect = p.boundingRect(posStartDt, top, x + width, -1, Qt::AlignTop | Qt::AlignLeft, outStr);
1700 p.drawText(rect, Qt::AlignTop | Qt::AlignLeft, outStr);
1701 posSoFar = posStartDt;
1702 }
1703
1704 // percentage completed
1705 if (posPercentComplete >= 0) {
1706 int lwidth = 24;
1707 int lheight = p.fontMetrics().ascent();
1708 // first, draw the progress bar
1709 int progress = static_cast<int>(((lwidth * todo->percentComplete()) / 100.0 + 0.5));
1710
1711 p.setBrush(QBrush(Qt::NoBrush));
1712 p.drawRect(posPercentComplete, top, lwidth, lheight);
1713 if (progress > 0) {
1714 p.setBrush(QColor(128, 128, 128));
1715 p.drawRect(posPercentComplete, top, progress, lheight);
1716 }
1717
1718 // now, write the percentage
1719 outStr = i18n("%1%", todo->percentComplete());
1720 rect = p.boundingRect(posPercentComplete + lwidth + 3, top, x + width, -1, Qt::AlignTop | Qt::AlignLeft, outStr);
1721 p.drawText(rect, Qt::AlignTop | Qt::AlignLeft, outStr);
1722 posSoFar = posPercentComplete;
1723 }
1724
1725 // categories
1726 QRect categoriesRect {0, 0, 0, 0};
1727 if (posCategories >= 0) {
1728 outStr = todo->categoriesStr();
1729 outStr.replace(QLatin1Char(','), QLatin1Char('\n'));
1730 rect = p.boundingRect(posCategories, top, posSoFar - posCategories, -1, Qt::TextWordWrap, outStr);
1731 p.drawText(rect, Qt::TextWordWrap, outStr, &categoriesRect);
1732 posSoFar = posCategories;
1733 }
1734
1735 // summary
1736 outStr = todo->summary();
1737 rect = p.boundingRect(lhs, top, posSoFar - lhs - 5, -1, Qt::TextWordWrap, outStr);
1738 QFont oldFont(p.font());
1739 if (strikeoutCompleted && todo->isCompleted()) {
1740 QFont newFont(p.font());
1741 newFont.setStrikeOut(true);
1742 p.setFont(newFont);
1743 }
1744 QRect summaryRect;
1745 p.drawText(rect, Qt::TextWordWrap, outStr, &summaryRect);
1746 p.setFont(oldFont);
1747
1748 y = std::max(categoriesRect.bottom(), summaryRect.bottom());
1749
1750 // description
1751 if (desc && !todo->description().isEmpty()) {
1752 drawTodoLines(p, todo->description(), left, y, width - (left + 10 - x), pageHeight, todo->descriptionIsRich(), startPoints, connectSubTodos);
1753 }
1754
1755 // Make a list of all the sub-to-dos related to this to-do.
1756 KCalendarCore::Todo::List t;
1757 const KCalendarCore::Incidence::List relations = mCalendar->childIncidences(todo->uid());
1758
1759 for (const KCalendarCore::Incidence::Ptr &incidence : relations) {
1760 // In the future, to-dos might also be related to events
1761 // Manually check if the sub-to-do is in the list of to-dos to print
1762 // The problem is that relations() does not apply filters, so
1763 // we need to compare manually with the complete filtered list!
1764 KCalendarCore::Todo::Ptr subtodo = incidence.dynamicCast<KCalendarCore::Todo>();
1765 if (!subtodo) {
1766 continue;
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 if ((mExcludeConfidential && subtodo->secrecy() == KCalendarCore::Incidence::SecrecyConfidential)
1783 || (mExcludePrivate && subtodo->secrecy() == KCalendarCore::Incidence::SecrecyPrivate)) {
1784 continue;
1785 }
1786 t.append(subtodo);
1787 }
1788 }
1789
1790 // has sub-todos?
1791 startpt.mHasLine = (relations.size() > 0);
1792 startPoints.append(&startpt);
1793
1794 // Sort the sub-to-dos and print them
1795 #ifdef AKONADI_PORT_DISABLED
1796 KCalendarCore::Todo::List sl = mCalendar->sortTodos(&t, sortField, sortDir);
1797 #else
1798 KCalendarCore::Todo::List tl;
1799 tl.reserve(t.count());
1800 for (const KCalendarCore::Todo::Ptr &todo : std::as_const(t)) {
1801 tl.append(todo);
1802 }
1803 KCalendarCore::Todo::List sl = mCalendar->sortTodos(tl, sortField, sortDir);
1804 #endif
1805
1806 int subcount = 0;
1807 for (const KCalendarCore::Todo::Ptr &isl : std::as_const(sl)) {
1808 count++;
1809 if (++subcount == sl.size()) {
1810 startpt.mHasLine = false;
1811 }
1812 drawTodo(count,
1813 isl,
1814 p,
1815 sortField,
1816 sortDir,
1817 connectSubTodos,
1818 strikeoutCompleted,
1819 desc,
1820 posPriority,
1821 posSummary,
1822 posCategories,
1823 posStartDt,
1824 posDueDt,
1825 posPercentComplete,
1826 level + 1,
1827 x,
1828 y,
1829 width,
1830 pageHeight,
1831 todoList,
1832 &startpt);
1833 }
1834 startPoints.removeAll(&startpt);
1835 }
1836
1837 int CalPrintPluginBase::weekdayColumn(int weekday)
1838 {
1839 int w = weekday + 7 - QLocale().firstDayOfWeek();
1840 return w % 7;
1841 }
1842
1843 void CalPrintPluginBase::drawTextLines(QPainter &p, const QString &entry, int x, int &y, int width, int pageHeight, bool richTextEntry)
1844 {
1845 QString plainEntry = (richTextEntry) ? toPlainText(entry) : entry;
1846
1847 QRect textrect(0, 0, width, -1);
1848 int flags = Qt::AlignLeft;
1849 QFontMetrics fm = p.fontMetrics();
1850
1851 QStringList lines = plainEntry.split(QLatin1Char('\n'));
1852 for (int currentLine = 0; currentLine < lines.count(); currentLine++) {
1853 // split paragraphs into lines
1854 KWordWrap ww = KWordWrap::formatText(fm, textrect, flags, lines[currentLine]);
1855 QStringList textLine = ww.wrappedString().split(QLatin1Char('\n'));
1856 // print each individual line
1857 for (int lineCount = 0; lineCount < textLine.count(); lineCount++) {
1858 y += fm.height();
1859 if (y >= pageHeight) {
1860 if (mPrintFooter) {
1861 drawFooter(p, {0, pageHeight, width, footerHeight()});
1862 }
1863 y = fm.height();
1864 mPrinter->newPage();
1865 }
1866 p.drawText(x, y, textLine[lineCount]);
1867 }
1868 }
1869 }
1870
1871 void CalPrintPluginBase::drawSplitHeaderRight(QPainter &p, QDate fd, QDate td, QDate, int width, int height)
1872 {
1873 QFont oldFont(p.font());
1874
1875 QPen oldPen(p.pen());
1876 QPen pen(Qt::black, 4);
1877
1878 QString title;
1879 QLocale locale;
1880 if (fd.month() == td.month()) {
1881 title = i18nc("Date range: Month dayStart - dayEnd",
1882 "%1 %2\u2013%3",
1883 locale.monthName(fd.month(), QLocale::LongFormat),
1884 locale.toString(fd, QStringLiteral("dd")),
1885 locale.toString(td, QStringLiteral("dd")));
1886 } else {
1887 title = i18nc("Date range: monthStart dayStart - monthEnd dayEnd",
1888 "%1 %2\u2013%3 %4",
1889 locale.monthName(fd.month(), QLocale::LongFormat),
1890 locale.toString(fd, QStringLiteral("dd")),
1891 locale.monthName(td.month(), QLocale::LongFormat),
1892 locale.toString(td, QStringLiteral("dd")));
1893 }
1894
1895 if (height < 60) {
1896 p.setFont(QFont(QStringLiteral("Times"), 22));
1897 } else {
1898 p.setFont(QFont(QStringLiteral("Times"), 28));
1899 }
1900
1901 int lineSpacing = p.fontMetrics().lineSpacing();
1902 p.drawText(0, 0, width, lineSpacing, Qt::AlignRight | Qt::AlignTop, title);
1903
1904 title.truncate(0);
1905
1906 p.setPen(pen);
1907 p.drawLine(300, lineSpacing, width, lineSpacing);
1908 p.setPen(oldPen);
1909
1910 if (height < 60) {
1911 p.setFont(QFont(QStringLiteral("Times"), 14, QFont::Bold, true));
1912 } else {
1913 p.setFont(QFont(QStringLiteral("Times"), 18, QFont::Bold, true));
1914 }
1915
1916 title += QString::number(fd.year());
1917 p.drawText(0, lineSpacing + padding(), width, lineSpacing, Qt::AlignRight | Qt::AlignTop, title);
1918
1919 p.setFont(oldFont);
1920 }
1921
1922 void CalPrintPluginBase::drawNoteLines(QPainter &p, QRect box, int startY)
1923 {
1924 int lineHeight = int(p.fontMetrics().lineSpacing() * 1.5);
1925 int linePos = box.y();
1926 int startPos = startY;
1927 // adjust line to start at multiple from top of box for alignment
1928 while (linePos < startPos) {
1929 linePos += lineHeight;
1930 }
1931 QPen oldPen(p.pen());
1932 p.setPen(Qt::DotLine);
1933 while (linePos < box.bottom()) {
1934 p.drawLine(box.left() + padding(), linePos, box.right() - padding(), linePos);
1935 linePos += lineHeight;
1936 }
1937 p.setPen(oldPen);
1938 }
1939
1940 QString CalPrintPluginBase::toPlainText(const QString &htmlText)
1941 {
1942 // this converts possible rich text to plain text
1943 return QTextDocumentFragment::fromHtml(htmlText).toPlainText();
1944 }
1945