kde-playground/kdepim/pimprint/calendar/calprintbase.cpp
2015-04-14 21:49:29 +00:00

1419 lines
49 KiB
C++

/*
This file is part of the PimPrint library.
Copyright (C) 2012-2013 Allen Winter <winter@kde.org>
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 <calendarsupport/kcalprefs.h>
#include <calendarsupport/utils.h>
#include <KCalendarSystem>
#include <KGlobal>
#include <KSystemTimeZones>
#include <QAbstractTextDocumentLayout>
#include <QPrinter>
#include <QTextCursor>
#include <QTextDocument>
#include <QTextDocumentFragment>
#include <boost/concept_check.hpp>
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<QDate> 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<QDate> &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<CellItem *> cells;
Q_FOREACH (const KCalCore::Event::Ptr &event, eventList) {
if (event->allDay()) {
continue;
}
QList<KDateTime>::ConstIterator it;
QList<KDateTime> times = event->startDateTimesForDate(date);
for (it = times.constBegin(); it != times.constEnd(); ++it) {
cells.append(new PrintCellItem(event, (*it), event->endDateForStart(*it)));
}
}
QListIterator<CellItem *> it1(cells);
while (it1.hasNext()) {
CellItem *placeItem = it1.next();
CellItem::placeItem(cells, placeItem);
}
QListIterator<CellItem *> it2(cells);
while (it2.hasNext()) {
PrintCellItem *placeItem = static_cast<PrintCellItem *>(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 <numid>%3</numid>",
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<KCalCore::Todo>())->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;
}