/* This file is part of the PimPrint library. Copyright (C) 2012-2013 Allen Winter This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "calprintbase.h" #include #include #include #include #include #include #include #include #include #include #include using namespace PimPrint::Calendar; //TODO: compute default page height and width based QPainter window static int sPortraitPageHeight = 792; // page height, for portrait orientation static int sLandscapePageHeight = 612; // page height, for landscape orientation static int sPortraitHeaderHeight = 72; // header height, for portrait orientation static int sLandscapeHeaderHeight = 54; // header height, for landscape orientation static int sSubHeaderHeight = 20; // subheader height, for all orientations static int sPortraitFooterHeight = 16; // footer height, for portrait orientation static int sLandscapeFooterHeight = 14; // footer height, for landscape orientation static int sMarginSize = 36; // margins, for all orientations (.5 inch) static int sPaddingSize = 7; // padding between the various top-level boxes static int sBoxBorderWidth = 2; // width of the border of all top-level boxes static int sItemBoxBorderWidth = 0; // width of the border of all item boxes static int sTimeLineWidth = 50; // width of timeline (eg. day and timetable) /** Private class that helps to provide binary compatibility between releases. @internal */ //@cond PRIVATE class PimPrint::Calendar::CalPrintBase::Private { public: Private() : mPrinter(0), mPageHeight(-1), mPageWidth(-1), mCalendar(0), mCalSystem(0), mPrintStyle(CalPrintBase::None), mHeaderHeight(-1), mSubHeaderHeight(sSubHeaderHeight), mFooterHeight(-1), mMargins(sMarginSize), mPadding(sPaddingSize), mBoxBorderWidth(sBoxBorderWidth), mItemBoxBorderWidth(sItemBoxBorderWidth), mTimeLineWidth(sTimeLineWidth), mInfoOptions(0), mTypeOptions(0), mRangeOptions(0), mExtraOptions(0), mUseColor(true), mUseLandscape(false), mPrintFooter(false) { } QPrinter *mPrinter; // the QPrinter QPainter mPainter; // the QPainter int mPageHeight; // page height, depends on page orientation int mPageWidth; // page height, depends on page orientation KCalCore::Calendar::Ptr mCalendar; // the Calendar KCalendarSystem *mCalSystem; // the Calendar System CalPrintBase::Style mPrintStyle; // the style to print int mHeaderHeight; // height of header, depends on page orientation int mSubHeaderHeight; // subheader height, same for all orientations int mFooterHeight; // height of footer, depends on page orientation int mMargins; // margins, for all orientations int mPadding; // padding between the various top-level boxes int mBoxBorderWidth; // width of the border of all top-level boxes int mItemBoxBorderWidth; // width of the border of all item boxes int mTimeLineWidth; // width of timelines InfoOptions mInfoOptions; // flags for information to print. TypeOptions mTypeOptions; // flags for types to print. RangeOptions mRangeOptions; // flags for time ranges to print. ExtraOptions mExtraOptions; // flags for extra stuff to print. bool mUseColor; // flag, print in color bool mUseLandscape; // flag, print in landscape orientation bool mPrintFooter; // flag, print footer }; //@endcond CalPrintBase::CalPrintBase(QPrinter *printer) : d(new PimPrint::Calendar::CalPrintBase::Private) { init(printer); } CalPrintBase::~CalPrintBase() { finish(); delete d; } void CalPrintBase::init(QPrinter *printer) const { if (!printer) { return; } // Init printer d->mPrinter = printer; d->mPrinter->setColorMode(QPrinter::GrayScale); // Init painter d->mPainter.begin(d->mPrinter); // FIXME: allow each margin to be set individually. // the painter initially begins at 72 dpi per the Qt docs. // Currently hard-coding half-inch margins (36 dpi) const int margin = margins(); d->mPainter.setViewport(margin, margin, d->mPainter.viewport().width() - 2 * margin, d->mPainter.viewport().height() - 2 * margin); // Init page setPageHeight(d->mPainter.window().height()); setPageWidth(d->mPainter.window().width()); } void CalPrintBase::finish() const { d->mPainter.end(); d->mPrinter = 0; } void CalPrintBase::setThePrinter(QPrinter *printer) { d->mPrinter = printer; } QPrinter *CalPrintBase::thePrinter() const { return d->mPrinter; } void CalPrintBase::setPrintCalendar(const KCalCore::Calendar::Ptr &calendar) { d->mCalendar = calendar; } KCalCore::Calendar::Ptr CalPrintBase::printCalendar() const { return d->mCalendar; } void CalPrintBase::setCalendarSystem(KCalendarSystem *calSystem) { d->mCalSystem = calSystem; } KCalendarSystem *CalPrintBase::calendarSystem() const { return d->mCalSystem; } void CalPrintBase::setPrintStyle(const Style style) { d->mPrintStyle = style; } CalPrintBase::Style CalPrintBase::printStyle() const { return d->mPrintStyle; } void CalPrintBase::setPageHeight(const int height) const { d->mPageHeight = height; } int CalPrintBase::pageHeight() const { if (d->mPageHeight >= 0) { return d->mPageHeight; } else if (!d->mUseLandscape) { return sPortraitPageHeight; //TODO: replace with p.window().height() } else { return sLandscapePageHeight; //TODO: replace with p.window().width() } } void CalPrintBase::setPageWidth(const int width) const { d->mPageWidth = width; } int CalPrintBase::pageWidth() const { if (d->mPageWidth >= 0) { return d->mPageWidth; } else if (d->mUseLandscape) { return sPortraitPageHeight; //TODO: replace with p.window().height() } else { return sLandscapePageHeight; //TODO: replace with p.windows().width() } } void CalPrintBase::setUseLandscape(const bool useLandscape) { d->mUseLandscape = useLandscape; } bool CalPrintBase::useLandscape() const { return d->mUseLandscape; } void CalPrintBase::setUseColor(const bool useColor) { d->mUseColor = useColor; } bool CalPrintBase::useColor() const { return d->mUseColor; } void CalPrintBase::setHeaderHeight(const int height) { d->mHeaderHeight = height; } int CalPrintBase::headerHeight() const { if (d->mHeaderHeight >= 0) { return d->mHeaderHeight; } else if (!d->mUseLandscape) { return sPortraitHeaderHeight; } else { return sLandscapeHeaderHeight; } } void CalPrintBase::setSubHeaderHeight(const int height) { d->mSubHeaderHeight = height; } int CalPrintBase::subHeaderHeight() const { return d->mSubHeaderHeight; } void CalPrintBase::setFooterHeight(const int height) { d->mFooterHeight = height; } int CalPrintBase::footerHeight() const { if (!d->mPrintFooter) { return 0; } if (d->mFooterHeight >= 0) { return d->mFooterHeight; } else if (!d->mUseLandscape) { return sPortraitFooterHeight; } else { return sLandscapeFooterHeight; } } void CalPrintBase::setPadding(const int padding) { d->mPadding = padding; } int CalPrintBase::padding() const { return d->mPadding; } int CalPrintBase::margins() const { return d->mMargins; } int CalPrintBase::boxBorderWidth() const { return d->mBoxBorderWidth; } int CalPrintBase::itemBoxBorderWidth() const { return d->mItemBoxBorderWidth; } int CalPrintBase::timeLineWidth() const { return d->mTimeLineWidth; } void CalPrintBase::setInfoOptions(CalPrintBase::InfoOptions flags) { d->mInfoOptions = flags; } CalPrintBase::InfoOptions CalPrintBase::infoOptions() const { return d->mInfoOptions; } void CalPrintBase::setTypeOptions(CalPrintBase::TypeOptions flags) { d->mTypeOptions = flags; } CalPrintBase::TypeOptions CalPrintBase::typeOptions() const { return d->mTypeOptions; } void CalPrintBase::setRangeOptions(CalPrintBase::RangeOptions flags) { d->mRangeOptions = flags; } CalPrintBase::RangeOptions CalPrintBase::rangeOptions() const { return d->mRangeOptions; } void CalPrintBase::setExtraOptions(CalPrintBase::ExtraOptions flags) { d->mExtraOptions = flags; } CalPrintBase::ExtraOptions CalPrintBase::extraOptions() const { return d->mExtraOptions; } void CalPrintBase::drawBox(QPainter &p, const int linewidth, const QRect &rect) const { QPen pen(p.pen()); QPen oldpen(pen); // no border if (linewidth >= 0) { pen.setWidth(linewidth); p.setPen(pen); } else { p.setPen(Qt::NoPen); } p.drawRect(rect); p.setPen(oldpen); } void CalPrintBase::drawShadedBox(QPainter &p, const int linewidth, const QBrush &brush, const QRect &rect) const { QBrush oldbrush(p.brush()); p.setBrush(brush); drawBox(p, linewidth, rect); p.setBrush(oldbrush); } void CalPrintBase::drawItemString(QPainter &p, const QRect &box, const QString &str, int flags) const { QRect newbox(box); newbox.adjust(3, 1, -1, -1); p.drawText(newbox, (flags == -1) ? (Qt::AlignTop | Qt::AlignLeft | Qt::TextWordWrap) : flags, str); } void CalPrintBase::drawItemBox(QPainter &p, const int linewidth, const QRect &box, const KCalCore::Incidence::Ptr &incidence, const QString &str, int flags) const { QPen oldpen(p.pen()); QBrush oldbrush(p.brush()); QColor bgColor(categoryBgColor(incidence)); if (d->mUseColor & bgColor.isValid()) { p.setBrush(bgColor); } else { p.setBrush(QColor(232, 232, 232)); } drawBox(p, (linewidth > 0) ? linewidth : itemBoxBorderWidth(), box); if (d->mUseColor && bgColor.isValid()) { p.setPen(getTextColor(bgColor)); } drawItemString(p, box, str, flags); p.setPen(oldpen); p.setBrush(oldbrush); } void CalPrintBase::drawVerticalBox(QPainter &p, const int linewidth, const QRect &box, const QString &str, int flags) const { p.save(); p.rotate(-90); QRect rotatedBox(-box.top() - box.height(), box.left(), box.height(), box.width()); drawItemBox(p, linewidth, rotatedBox, KCalCore::Incidence::Ptr(), str, (flags == -1) ? Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine : flags); p.restore(); } void CalPrintBase::drawSmallMonth(QPainter &p, const QDate &date, const QRect &box) const { int weekdayCol = weekdayColumn(date.dayOfWeek()); int month = date.month(); QDate monthDate(QDate(date.year(), date.month(), 1)); // correct begin of week QDate monthDate2(monthDate.addDays(-weekdayCol)); double cellWidth = double(box.width()) / double(7); int rownr = 3 + (date.daysInMonth() + weekdayCol - 1) / 7; // 3 Pixel after month name, 2 after day names, 1 after the calendar double cellHeight = (box.height() - 5) / rownr; QFont oldFont(p.font()); //TODO: option for the small month font? p.setFont(QFont("sans-serif", int(cellHeight - 2), QFont::Normal)); // draw the title KCalendarSystem *calSys = calendarSystem(); if (calSys) { QRect titleBox(box); titleBox.setHeight(int(cellHeight + 1)); p.drawText(titleBox, Qt::AlignTop | Qt::AlignHCenter, calSys->monthName(date)); } // draw days of week QRect wdayBox(box); wdayBox.setTop(int(box.top() + 3 + cellHeight)); wdayBox.setHeight(int(2 * cellHeight) - int(cellHeight)); if (calSys) { for (int col = 0; col < 7; ++col) { QString tmpStr = calSys->weekDayName(monthDate2)[0].toUpper(); wdayBox.setLeft(int(box.left() + col * cellWidth)); wdayBox.setRight(int(box.left() + (col + 1) * cellWidth)); p.drawText(wdayBox, Qt::AlignCenter, tmpStr); monthDate2 = monthDate2.addDays(1); } } // draw separator line int calStartY = wdayBox.bottom() + 2; p.drawLine(box.left(), calStartY, box.right(), calStartY); monthDate = monthDate.addDays(-weekdayCol); for (int row = 0; row < (rownr - 2); row++) { for (int col = 0; col < 7; col++) { if (monthDate.month() == month) { QRect dayRect(int(box.left() + col * cellWidth), int(calStartY + row * cellHeight), 0, 0); dayRect.setRight(int(box.left() + (col + 1) * cellWidth)); dayRect.setBottom(int(calStartY + (row + 1) * cellHeight)); p.drawText(dayRect, Qt::AlignCenter, QString::number(monthDate.day())); } monthDate = monthDate.addDays(1); } } p.setFont(oldFont); } int CalPrintBase::drawHeader(QPainter &p, const QRect &allbox, const QString &title, const QDate &leftMonth, const QDate &rightMonth, const bool expand, const QColor &backColor) const { // print previous month for month view, print current for to-do, day and week int smallMonthWidth = (allbox.width() / 4) - 10; if (smallMonthWidth > 100) { smallMonthWidth = 100; } QRect box(allbox); QRect textRect(allbox); QFont oldFont(p.font()); //TODO: option for the header font? QFont newFont("sans-serif", (textRect.height() < 60) ? 16 : 18, QFont::Bold); if (expand) { p.setFont(newFont); QRect boundingR = p.boundingRect(textRect, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, title); p.setFont(oldFont); int h = boundingR.height(); if (h > allbox.height()) { box.setHeight(h); textRect.setHeight(h); } } QBrush backBrush; if (!backColor.isValid()) { backBrush.setColor(QColor(232, 232, 232)); } else { backBrush.setColor(backColor); } drawShadedBox(p, boxBorderWidth(), backBrush, box); // prev month left, current month centered, next month right if (rightMonth.isValid()) { const QRect rightMonthBox(box.right() - 10 - smallMonthWidth, box.top(), smallMonthWidth, box.height()); drawSmallMonth(p, QDate(rightMonth.year(), rightMonth.month(), 1), rightMonthBox); textRect.setRight(rightMonthBox.left()); } if (leftMonth.isValid()) { const QRect leftMonthBox(box.left() + 10, box.top(), smallMonthWidth, box.height()); drawSmallMonth(p, QDate(leftMonth.year(), leftMonth.month(), 1), leftMonthBox); textRect.setLeft(leftMonthBox.right()); } // Set the margins p.setFont(newFont); p.drawText(textRect, Qt::AlignCenter | Qt::AlignVCenter | Qt::TextWordWrap, title); p.setFont(oldFont); return textRect.bottom(); } void CalPrintBase::drawSubHeader(QPainter &p, const QRect &box, const QString &str) const { drawShadedBox(p, boxBorderWidth(), QColor(232, 232, 232), box); QFont oldfont(p.font()); //TODO: option for the subheader font? p.setFont(QFont("sans-serif", 10, QFont::Bold)); p.drawText(box, Qt::AlignCenter | Qt::AlignVCenter, str); p.setFont(oldfont); } int CalPrintBase::drawFooter(QPainter &p, const QRect &footbox) const { QFont oldfont(p.font()); //TODO: option for the footer font? p.setFont(QFont("sans-serif", 6)); QFontMetrics fm(p.font()); QString dateStr = KGlobal::locale()->formatDateTime(QDateTime::currentDateTime(), KLocale::LongDate); p.drawText(footbox, Qt::AlignCenter | Qt::AlignVCenter | Qt::TextSingleLine, i18nc("print date: formatted-datetime", "printed: %1", dateStr)); p.setFont(oldfont); return footbox.bottom(); } void CalPrintBase::drawTimeLine(QPainter &p, const QTime &startTime, const QTime &endTime, const QRect &box) const { drawBox(p, boxBorderWidth(), box); int totalsecs = startTime.secsTo(endTime); float minlen = (float)box.height() * 60. / (float)totalsecs; float cellHeight = (60. * (float)minlen); float currY = box.top(); // TODO: Don't use half of the width, but less, for the minutes! int xcenter = box.left() + box.width() / 2; QTime curTime(startTime); if (startTime.minute() > 30) { curTime = QTime(startTime.hour() + 1, 0, 0); } else if (startTime.minute() > 0) { curTime = QTime(startTime.hour(), 30, 0); float yy = currY + minlen * (float)startTime.secsTo(curTime) / 60.; p.drawLine(xcenter, (int)yy, box.right(), (int)yy); curTime = QTime(startTime.hour() + 1, 0, 0); } currY += (float(startTime.secsTo(curTime) * minlen) / 60.); while (curTime < endTime) { p.drawLine(box.left(), (int)currY, box.right(), (int)currY); int newY = (int)(currY + cellHeight / 2.); QString numStr; if (newY < box.bottom()) { QFont oldFont(p.font()); // draw the time: if (!KGlobal::locale()->use12Clock()) { p.drawLine(xcenter, (int)newY, box.right(), (int)newY); numStr.setNum(curTime.hour()); //TODO: option for this font? if (cellHeight > 30) { p.setFont(QFont("sans-serif", 14, QFont::Bold)); } else { p.setFont(QFont("sans-serif", 12, QFont::Bold)); } p.drawText(box.left() + 4, (int)currY + 2, box.width() / 2 - 2, (int)cellHeight, Qt::AlignTop | Qt::AlignRight, numStr); //TODO: option for this font? p.setFont(QFont("helvetica", 10, QFont::Normal)); p.drawText(xcenter + 4, (int)currY + 2, box.width() / 2 + 2, (int)(cellHeight / 2) - 3, Qt::AlignTop | Qt::AlignLeft, "00"); } else { p.drawLine(box.left(), (int)newY, box.right(), (int)newY); QTime time(curTime.hour(), 0); numStr = KGlobal::locale()->formatTime(time); //TODO: option for this font? if (box.width() < 60) { p.setFont(QFont("sans-serif", 7, QFont::Bold)); // for weekprint } else { p.setFont(QFont("sans-serif", 12, QFont::Bold)); // for dayprint } p.drawText(box.left() + 2, (int)currY + 2, box.width() - 4, (int)cellHeight / 2 - 3, Qt::AlignTop | Qt::AlignLeft, numStr); } currY += cellHeight; p.setFont(oldFont); } // enough space for half-hour line and time if (curTime.secsTo(endTime) > 3600) { curTime = curTime.addSecs(3600); } else { curTime = endTime; } } } void CalPrintBase::drawTimeTable(QPainter &p, const QRect &box, const QDate &startDate, const QDate &endDate, const QTime &startTime, const QTime &endTime, bool expandAll) const { QTime myStartTime = startTime; QTime myEndTime = endTime; if (expandAll) { QDate curDate(startDate); KDateTime::Spec timeSpec = KSystemTimeZones::local(); while (curDate <= endDate) { KCalCore::Event::List eventList = printCalendar()->events(curDate, timeSpec); Q_FOREACH (const KCalCore::Event::Ptr &event, eventList) { Q_ASSERT(event); if (event->allDay()) { continue; } if (event->dtStart().time() < myStartTime) { myStartTime = event->dtStart().time(); } if (event->dtEnd().time() > myEndTime) { myEndTime = event->dtEnd().time(); } } curDate = curDate.addDays(1); } } // timeline is 1 hour: int alldayHeight = (int)(3600. * box.height() / (myStartTime.secsTo(myEndTime) + 3600.)); QRect dowBox(box); dowBox.setLeft(box.left() + timeLineWidth()); dowBox.setHeight(d->mSubHeaderHeight); drawDaysOfWeek(p, dowBox, startDate, endDate); QRect tlBox(box); tlBox.setWidth(timeLineWidth()); tlBox.setTop(dowBox.bottom() + boxBorderWidth() + alldayHeight); drawTimeLine(p, myStartTime, myEndTime, tlBox); // draw each day QDate curDate(startDate); KDateTime::Spec timeSpec = KSystemTimeZones::local(); int i = 0; double cellWidth = double(dowBox.width()) / double(startDate.daysTo(endDate) + 1); const QList workDays = CalendarSupport::workDays(startDate, endDate); while (curDate <= endDate) { QRect allDayBox(dowBox.left() + int(i * cellWidth), dowBox.bottom() + boxBorderWidth(), int((i + 1) * cellWidth) - int(i * cellWidth), alldayHeight); QRect dayBox(allDayBox); dayBox.setTop(tlBox.top()); dayBox.setBottom(box.bottom()); KCalCore::Event::List eventList = printCalendar()->events(curDate, timeSpec, KCalCore::EventSortStartDate, KCalCore::SortDirectionAscending); alldayHeight = drawAllDayBox(p, allDayBox, curDate, eventList); drawAgendaDayBox(p, dayBox, curDate, eventList, myStartTime, myEndTime, workDays); i++; curDate = curDate.addDays(1); } } void CalPrintBase::drawAgendaDayBox(QPainter &p, const QRect &box, const QDate &date, const KCalCore::Event::List &eventList, const QTime &startTime, const QTime &endTime, const QList &workDays) const { QTime myFromTime, myToTime; if (startTime.isValid()) { myFromTime = startTime; } else { myFromTime = QTime(0, 0, 0); } if (endTime.isValid()) { myToTime = endTime; } else { myToTime = QTime(23, 59, 59); } if (!workDays.contains(date)) { drawShadedBox(p, boxBorderWidth(), QColor(232, 232, 232), box); } else { drawBox(p, boxBorderWidth(), box); } const bool printConf = typeOptions().testFlag( CalPrintBase::TypeConfidential); //TODO should be false by default const bool printPrivate = typeOptions().testFlag( CalPrintBase::TypePrivate); //TODO should be false by default if (rangeOptions().testFlag(CalPrintBase::RangeTimeExpand)) {//TODO should be false by default // Adapt start/end times to include complete events Q_FOREACH (const KCalCore::Event::Ptr &event, eventList) { Q_ASSERT(event); if ((!printConf && event->secrecy() == KCalCore::Incidence::SecrecyConfidential) || (!printPrivate && event->secrecy() == KCalCore::Incidence::SecrecyPrivate)) { continue; } // skip items without times so that we do not adjust for all day items if (event->allDay()) { continue; } if (event->dtStart().time() < myFromTime) { myFromTime = event->dtStart().time(); } if (event->dtEnd().time() > myToTime) { myToTime = event->dtEnd().time(); } } } // calculate the height of a cell and of a minute int totalsecs = myFromTime.secsTo(myToTime); float minlen = box.height() * 60. / totalsecs; float cellHeight = 60. * minlen; float currY = box.top(); // print grid: QTime curTime(QTime(myFromTime.hour(), 0, 0)); currY += myFromTime.secsTo(curTime) * minlen / 60; while (curTime < myToTime && curTime.isValid()) { if (currY > box.top()) { p.drawLine(box.left(), int(currY), box.right(), int(currY)); } currY += cellHeight / 2; if ((currY > box.top()) && (currY < box.bottom())) { // enough space for half-hour line QPen oldPen(p.pen()); p.setPen(QColor(192, 192, 192)); //TODO: define this color p.drawLine(box.left(), int(currY), box.right(), int(currY)); p.setPen(oldPen); } if (curTime.secsTo(myToTime) > 3600) { curTime = curTime.addSecs(3600); } else { curTime = myToTime; } currY += cellHeight / 2; } KDateTime startPrintDate = KDateTime(date, myFromTime); KDateTime endPrintDate = KDateTime(date, myToTime); // Calculate horizontal positions and widths of events taking into account // overlapping events QList cells; Q_FOREACH (const KCalCore::Event::Ptr &event, eventList) { if (event->allDay()) { continue; } QList::ConstIterator it; QList times = event->startDateTimesForDate(date); for (it = times.constBegin(); it != times.constEnd(); ++it) { cells.append(new PrintCellItem(event, (*it), event->endDateForStart(*it))); } } QListIterator it1(cells); while (it1.hasNext()) { CellItem *placeItem = it1.next(); CellItem::placeItem(cells, placeItem); } QListIterator it2(cells); while (it2.hasNext()) { PrintCellItem *placeItem = static_cast(it2.next()); drawAgendaItem(placeItem, p, startPrintDate, endPrintDate, minlen, box); } } void CalPrintBase::drawAgendaItem(PrintCellItem *item, QPainter &p, const KDateTime &startPrintDate, const KDateTime &endPrintDate, float minlen, const QRect &box) const { KCalCore::Event::Ptr event = item->event(); // start/end of print area for event KDateTime startTime = item->start(); KDateTime endTime = item->end(); if ((startTime < endPrintDate && endTime > startPrintDate) || (endTime > startPrintDate && startTime < endPrintDate)) { if (startTime < startPrintDate) { startTime = startPrintDate; } if (endTime > endPrintDate) { endTime = endPrintDate; } int currentWidth = box.width() / item->subCells(); int currentX = box.left() + item->subCell() * currentWidth; int currentYPos = int(box.top() + startPrintDate.secsTo(startTime) * minlen / 60.); int currentHeight = int(box.top() + startPrintDate.secsTo(endTime) * minlen / 60.) - currentYPos; QRect eventBox(currentX, currentYPos, currentWidth, currentHeight); QString str; if (!infoOptions().testFlag(CalPrintBase::InfoTimeRange)) { if (event->location().isEmpty()) { str = cleanString(event->summary()); } else { str = i18nc("summary, location", "%1, %2", cleanString(event->summary()), cleanString(event->location())); } } else { if (event->location().isEmpty()) { str = i18nc("starttime - endtime summary", "%1-%2 %3", KGlobal::locale()->formatTime(item->start().toLocalZone().time()), KGlobal::locale()->formatTime(item->end().toLocalZone().time()), cleanString(event->summary())); } else { str = i18nc("starttime - endtime summary, location", "%1-%2 %3, %4", KGlobal::locale()->formatTime(item->start().toLocalZone().time()), KGlobal::locale()->formatTime(item->end().toLocalZone().time()), cleanString(event->summary()), cleanString(event->location())); } } if (infoOptions().testFlag(CalPrintBase::InfoDescription) && !event->description().isEmpty()) { str += '\n'; if (event->descriptionIsRich()) { str += toPlainText(event->description()); } else { str += event->description(); } } QFont oldFont(p.font()); //TODO: font option? if (eventBox.height() < 24) { if (eventBox.height() < 12) { if (eventBox.height() < 8) { p.setFont(QFont("sans-serif", 4)); } else { p.setFont(QFont("sans-serif", 5)); } } else { p.setFont(QFont("sans-serif", 6)); } } else { p.setFont(QFont("sans-serif", 8)); } drawItemBox(p, itemBoxBorderWidth(), eventBox, event, str); p.setFont(oldFont); } } void CalPrintBase::drawDaysOfWeekBox(QPainter &p, const QRect &box, const QDate &date) const { KCalendarSystem *calSys = calendarSystem(); drawSubHeader(p, box, (calSys) ? (calSys->weekDayName(date)) : QString()); } void CalPrintBase::drawDaysOfWeek(QPainter &p, const QRect &box, const QDate &fromDate, const QDate &toDate) const { double cellWidth = double(box.width()) / double(fromDate.daysTo(toDate) + 1); QDate cellDate(fromDate); QRect dateBox(box); int i = 0; while (cellDate <= toDate) { dateBox.setLeft(box.left() + int(i * cellWidth)); dateBox.setRight(box.left() + int((i + 1) * cellWidth)); drawDaysOfWeekBox(p, dateBox, cellDate); cellDate = cellDate.addDays(1); i++; } } int CalPrintBase::drawAllDayBox(QPainter &p, const QRect &box, const QDate &date, const KCalCore::Event::List &eventList, bool expandAll) const { KCalCore::Event::List::Iterator it; int offset = box.top(); QString multiDayStr; KCalCore::Event::List evList = eventList; KCalCore::Event::Ptr hd = holidayEvent(date); if (hd) { evList.prepend(hd); } const bool printConf = typeOptions().testFlag( CalPrintBase::TypeConfidential); //TODO should be false by default const bool printPrivate = typeOptions().testFlag( CalPrintBase::TypePrivate); //TODO should be false by default it = evList.begin(); while (it != evList.end()) { KCalCore::Event::Ptr currEvent = *it; if ((!printConf && currEvent->secrecy() == KCalCore::Incidence::SecrecyConfidential) || (!printPrivate && currEvent->secrecy() == KCalCore::Incidence::SecrecyPrivate)) { continue; } if (currEvent && currEvent->allDay()) { // set the colors according to the categories if (expandAll) { QRect eventBox(box); eventBox.setTop(offset); drawItemBox(p, itemBoxBorderWidth(), eventBox, currEvent, currEvent->summary()); offset += box.height(); } else { if (!multiDayStr.isEmpty()) { multiDayStr += ", "; } multiDayStr += currEvent->summary(); } it = evList.erase(it); } else { ++it; } } int ret = box.height(); QRect eventBox(box); if (!expandAll) { if (!multiDayStr.isEmpty()) { drawShadedBox(p, boxBorderWidth(), QColor(180, 180, 180), eventBox); drawItemString(p, eventBox, multiDayStr); } else { drawBox(p, boxBorderWidth(), eventBox); } } else { ret = offset - box.top(); eventBox.setBottom(ret); drawBox(p, boxBorderWidth(), eventBox); } return ret; } void CalPrintBase::drawDayIncidence(QPainter &p, const QRect &dayBox, const QString &time, const QString &summary, const QString &description, int &textY, bool richDescription) const { kDebug() << "summary =" << summary; int flags = Qt::AlignLeft | Qt::OpaqueMode; QFontMetrics fm = p.fontMetrics(); const int borderWidth = p.pen().width() + 1; QRect timeBound = p.boundingRect(dayBox.x() + borderWidth, dayBox.y() + textY, dayBox.width(), fm.lineSpacing(), flags, time); int summaryWidth = time.isEmpty() ? 0 : timeBound.width() + 3; QRect summaryBound = QRect(dayBox.x() + borderWidth + summaryWidth, dayBox.y() + textY + 1, dayBox.width() - summaryWidth - (borderWidth * 2), dayBox.height() - textY); QString summaryText = summary; QString descText = toPlainText(description); bool boxOverflow = false; const bool includeDescription = infoOptions().testFlag( CalPrintBase::InfoDescription); // TODO false by default if (extraOptions().testFlag(CalPrintBase::ExtraSingleLine)) {//TODO should be true by default if (includeDescription && !descText.isEmpty()) { summaryText += ", " + descText; } int totalHeight = fm.lineSpacing() + borderWidth; int textBoxHeight = (totalHeight > (dayBox.height() - textY)) ? dayBox.height() - textY : totalHeight; summaryBound.setHeight(textBoxHeight); QRect lineRect(dayBox.x() + borderWidth, dayBox.y() + textY, dayBox.width() - (borderWidth * 2), textBoxHeight); drawBox(p, 1, lineRect); if (!time.isEmpty()) { p.drawText(timeBound, flags, time); } p.drawText(summaryBound, flags, summaryText); } else { QTextDocument textDoc; QTextCursor textCursor(&textDoc); if (richDescription) { QTextCursor textCursor(&textDoc); textCursor.insertText(summaryText); if (includeDescription && !description.isEmpty()) { textCursor.insertText("\n"); textCursor.insertHtml(description); } } else { textCursor.insertText(summaryText); if (includeDescription && !descText.isEmpty()) { textCursor.insertText("\n"); textCursor.insertText(descText); } } textDoc.setPageSize(QSize(summaryBound.width(), summaryBound.height())); p.save(); QRect clipBox(0, 0, summaryBound.width(), summaryBound.height()); p.setFont(p.font()); p.translate(summaryBound.x(), summaryBound.y()); summaryBound.setHeight(textDoc.documentLayout()->documentSize().height()); if (summaryBound.bottom() > dayBox.bottom()) { summaryBound.setBottom(dayBox.bottom()); } clipBox.setHeight(summaryBound.height()); p.restore(); p.save(); QRect backBox(timeBound.x(), timeBound.y(), dayBox.width() - (borderWidth * 2), clipBox.height()); drawBox(p, 1, backBox); if (!time.isEmpty()) { if (timeBound.bottom() > dayBox.bottom()) { timeBound.setBottom(dayBox.bottom()); } timeBound.moveTop(timeBound.y() + (summaryBound.height() - timeBound.height()) / 2); p.drawText(timeBound, flags, time); } p.translate(summaryBound.x(), summaryBound.y()); textDoc.drawContents(&p, clipBox); p.restore(); boxOverflow = textDoc.pageCount() > 1; } if (summaryBound.bottom() < dayBox.bottom()) { QPen oldPen(p.pen()); p.setPen(QPen()); p.drawLine(dayBox.x(), summaryBound.bottom(), dayBox.x() + dayBox.width(), summaryBound.bottom()); p.setPen(oldPen); } textY += summaryBound.height(); // show that we have overflowed the box if (boxOverflow) { QPolygon poly(3); int x = dayBox.x() + dayBox.width(); int y = dayBox.y() + dayBox.height(); poly.setPoint(0, x - 10, y); poly.setPoint(1, x, y - 10); poly.setPoint(2, x, y); QBrush oldBrush(p.brush()); p.setBrush(QBrush(Qt::black)); p.drawPolygon(poly); p.setBrush(oldBrush); textY = dayBox.height(); } } void CalPrintBase::drawNoteLines(QPainter &p, const QRect &box, int startY) const { int lineHeight = int(p.fontMetrics().lineSpacing() * 1.5); int linePos = box.y(); int startPos = startY; // adjust line to start at multiple from top of box for alignment while (linePos < startPos) { linePos += lineHeight; } QPen oldPen(p.pen()); p.setPen(Qt::DotLine); while (linePos < box.bottom()) { p.drawLine(box.left() + padding(), linePos, box.right() - padding(), linePos); linePos += lineHeight; } p.setPen(oldPen); } void CalPrintBase::drawDayBox(QPainter &p, const QRect &box, const QDate &date, const QTime &startTime, const QTime &endTime, bool fullDate) const { QString dayNumStr; const KLocale *local = KGlobal::locale(); QTime myStartTime, myEndTime; if (startTime.isValid()) { myStartTime = startTime; } else { myStartTime = QTime(0, 0, 0); } if (endTime.isValid()) { myEndTime = endTime; } else { myEndTime = QTime(23, 59, 59); } if (fullDate && calendarSystem()) { dayNumStr = i18nc("weekday, shortmonthname daynumber", "%1, %2 %3", calendarSystem()->weekDayName(date), calendarSystem()->monthName(date, KCalendarSystem::ShortName), date.day()); } else { dayNumStr = QString::number(date.day()); } QRect subHeaderBox(box); subHeaderBox.setHeight(subHeaderHeight()); drawShadedBox(p, boxBorderWidth(), p.background(), box); drawShadedBox(p, 0, QColor(232, 232, 232), subHeaderBox); drawBox(p, boxBorderWidth(), box); QString hstring(holidayString(date)); const QFont oldFont(p.font()); QRect headerTextBox(subHeaderBox); headerTextBox.setLeft(subHeaderBox.left() + 5); headerTextBox.setRight(subHeaderBox.right() - 5); if (!hstring.isEmpty()) { //TODO: option for this font p.setFont(QFont("sans-serif", 8, QFont::Bold, true)); p.drawText(headerTextBox, Qt::AlignLeft | Qt::AlignVCenter, hstring); } //TODO: option for this font p.setFont(QFont("sans-serif", 10, QFont::Bold)); p.drawText(headerTextBox, Qt::AlignRight | Qt::AlignVCenter, dayNumStr); const KCalCore::Event::List eventList = printCalendar()->events(date, KSystemTimeZones::local(), KCalCore::EventSortStartDate, KCalCore::SortDirectionAscending); QString timeText; //TODO: option for this font p.setFont(QFont("sans-serif", 7)); const bool printConf = typeOptions().testFlag( CalPrintBase::TypeConfidential); //TODO should be false by default const bool printPrivate = typeOptions().testFlag( CalPrintBase::TypePrivate); //TODO should be false by default const bool printRecurDaily = rangeOptions().testFlag( CalPrintBase::RangeRecurDaily); //TODO should be false by default? const bool printRecurWeekly = rangeOptions().testFlag( CalPrintBase::RangeRecurWeekly); //TODO should be false by defualt? int textY = subHeaderHeight(); // gives the relative y-coord of the next printed entry unsigned int visibleEventsCounter = 0; Q_FOREACH (const KCalCore::Event::Ptr &currEvent, eventList) { Q_ASSERT(currEvent); if (!currEvent->allDay()) { if (currEvent->dtEnd().toLocalZone().time() <= myStartTime || currEvent->dtStart().toLocalZone().time() > myEndTime) { continue; } } if ((!printRecurDaily && currEvent->recurrenceType() == KCalCore::Recurrence::rDaily) || (!printRecurWeekly && currEvent->recurrenceType() == KCalCore::Recurrence::rWeekly)) { continue; } if ((!printConf && currEvent->secrecy() == KCalCore::Incidence::SecrecyConfidential) || (!printPrivate && currEvent->secrecy() == KCalCore::Incidence::SecrecyPrivate)) { continue; } if (currEvent->allDay() || currEvent->isMultiDay()) { timeText.clear(); } else { timeText = local->formatTime(currEvent->dtStart().toLocalZone().time()) + ' '; } p.save(); setColorsByIncidenceCategory(p, currEvent); QString summaryStr = currEvent->summary(); if (!currEvent->location().isEmpty()) { summaryStr = i18nc("summary, location", "%1, %2", summaryStr, currEvent->location()); } drawDayIncidence(p, box, timeText, summaryStr, currEvent->description(), textY, currEvent->descriptionIsRich()); p.restore(); visibleEventsCounter++; if (textY >= box.height()) { const QChar downArrow(0x21e3); const unsigned int invisibleIncidences = (eventList.count() - visibleEventsCounter) + printCalendar()->todos(date).count(); if (invisibleIncidences > 0) { const QString warningMsg = QString("%1 (%2)").arg(downArrow).arg(invisibleIncidences); QFontMetrics fm(p.font()); QRect msgRect = fm.boundingRect(warningMsg); msgRect.setRect(box.right() - msgRect.width() - 2, box.bottom() - msgRect.height() - 2, msgRect.width(), msgRect.height()); p.save(); p.setPen(Qt::red); //krazy:exclude=qenums we don't allow custom print colors p.drawText(msgRect, Qt::AlignLeft, warningMsg); p.restore(); } break; } } if (textY < box.height()) { KCalCore::Todo::List todoList = printCalendar()->todos(date); Q_FOREACH (const KCalCore::Todo::Ptr &todo, todoList) { if (!todo->allDay()) { if ((todo->hasDueDate() && todo->dtDue().toLocalZone().time() <= myStartTime) || (todo->hasStartDate() && todo->dtStart().toLocalZone().time() > myEndTime)) { continue; } } if ((!printRecurDaily && todo->recurrenceType() == KCalCore::Recurrence::rDaily) || (!printRecurWeekly && todo->recurrenceType() == KCalCore::Recurrence::rWeekly)) { continue; } if ((!printConf && todo->secrecy() == KCalCore::Incidence::SecrecyConfidential) || (!printPrivate && todo->secrecy() == KCalCore::Incidence::SecrecyPrivate)) { continue; } if (todo->hasStartDate() && !todo->allDay()) { timeText = KGlobal::locale()->formatTime(todo->dtStart().toLocalZone().time()) + ' '; } else { timeText.clear(); } p.save(); setColorsByIncidenceCategory(p, todo); QString summaryStr = todo->summary(); if (!todo->location().isEmpty()) { summaryStr = i18nc("summary, location", "%1, %2", summaryStr, todo->location()); } QString str; if (todo->hasDueDate()) { if (!todo->allDay()) { str = i18nc("to-do summary (Due: datetime)", "%1 (Due: %2)", summaryStr, KGlobal::locale()->formatDateTime(todo->dtDue().toLocalZone())); } else { str = i18nc("to-do summary (Due: date)", "%1 (Due: %2)", summaryStr, KGlobal::locale()->formatDate( todo->dtDue().toLocalZone().date(), KLocale::ShortDate)); } } else { str = summaryStr; } drawDayIncidence(p, box, timeText, i18n("To-do: %1", str), todo->description(), textY, todo->descriptionIsRich()); p.restore(); } } if (extraOptions().testFlag(CalPrintBase::ExtraNoteLines)) {//TODO should be false by default drawNoteLines(p, box, box.y() + textY); } p.setFont(oldFont); } // Utility Functions that could be moved into CalendarSupport or somesuch int CalPrintBase::weekdayColumn(const int weekday) const { int w = weekday + 7 - KGlobal::locale()->weekStartDay(); return w % 7; } QString CalPrintBase::cleanString(const QString &str) const { QString ret = str; return ret.replace('\n', ' '); } QString CalPrintBase::toPlainText(const QString &htmlText) const { return QTextDocumentFragment::fromHtml(htmlText).toPlainText(); } QColor CalPrintBase::getTextColor(const QColor &c) const { double luminance = (c.red() * 0.299) + (c.green() * 0.587) + (c.blue() * 0.114); return (luminance > 128.0) ? QColor(0, 0, 0) : QColor(255, 255, 255); } QColor CalPrintBase::categoryColor(const QStringList &categories) const { if (categories.isEmpty()) { return CalendarSupport::KCalPrefs::instance()->unsetCategoryColor(); } // FIXME: Correctly treat events with multiple categories const QString cat = categories.first(); QColor bgColor; if (cat.isEmpty()) { bgColor = CalendarSupport::KCalPrefs::instance()->unsetCategoryColor(); } else { bgColor = CalendarSupport::KCalPrefs::instance()->categoryColor(cat); } return bgColor; } QColor CalPrintBase::categoryBgColor(const KCalCore::Incidence::Ptr &incidence) const { if (incidence) { QColor backColor = categoryColor(incidence->categories()); if (incidence->type() == KCalCore::Incidence::TypeTodo) { if ((incidence.staticCast())->isOverdue()) { //TODO make this available from CalendarSupport::KCalPrefs backColor = QColor(255, 100, 100); //was KOPrefs::instance()->todoOverdueColor(); } } return backColor; } else { return QColor(); } } void CalPrintBase::setColorsByIncidenceCategory(QPainter &p, const KCalCore::Incidence::Ptr &incidence) const { QColor bgColor = categoryBgColor(incidence); if (bgColor.isValid()) { p.setBrush(bgColor); } QColor tColor(getTextColor(bgColor)); if (tColor.isValid()) { p.setPen(tColor); } } QString CalPrintBase::holidayString(const QDate &date) const { QStringList lst = CalendarSupport::holiday(date); return lst.join(i18nc("@item:intext delimiter for joining holiday names", ",")); } KCalCore::Event::Ptr CalPrintBase::holidayEvent(const QDate &date) const { QString hstring(holidayString(date)); if (hstring.isEmpty()) { return KCalCore::Event::Ptr(); } KCalCore::Event::Ptr holiday(new KCalCore::Event); holiday->setSummary(hstring); holiday->setCategories(i18n("Holiday")); KDateTime kdt(date, QTime(), KSystemTimeZones::local()); holiday->setDtStart(kdt); holiday->setDtEnd(kdt); holiday->setAllDay(true); return holiday; }