/*
  Copyright (c) 1998 Preston Brown <pbrown@kde.org>
  Copyright (C) 2003 Reinhold Kainhofer <reinhold@kainhofer.com>
  Copyright (C) 2008 Ron Goodheart <rong.dev@gmail.com>
  Copyright (c) 2012-2013 Allen Winter <winter@kde.org>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program 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 General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

  As a special exception, permission is given to link this program
  with any edition of Qt, and distribute the resulting executable,
  without including the source code for Qt in the source distribution.
*/

#include "calprintpluginbase.h"
#include "cellitem.h"
#include "kcalprefs.h"
#include "utils.h"

#include <Akonadi/Item>

#include <KCalendarSystem>
#include <KConfigGroup>
#include <KDebug>
#include <KSystemTimeZones>
#include <KWordWrap>

#include <QAbstractTextDocumentLayout>
#include <QFrame>
#include <QLabel>
#include <QLayout>
#include <QPainter>
#include <QTextCursor>
#include <QTextDocument>
#include <QTextDocumentFragment>
#include <qmath.h> // qCeil krazy:exclude=camelcase since no QMath

using namespace CalendarSupport;

static QString cleanStr( const QString &instr )
{
  QString ret = instr;
  return ret.replace( QLatin1Char('\n'), QLatin1Char(' ') );
}

/******************************************************************
 **              The Todo positioning structure                  **
 ******************************************************************/
class CalPrintPluginBase::TodoParentStart
{
  public:
    TodoParentStart( const QRect &pt = QRect(), bool hasLine = false, bool page = true )
      : mRect( pt ), mHasLine( hasLine ), mSamePage( page ) {}

    QRect mRect;
    bool mHasLine;
    bool mSamePage;
};

/******************************************************************
 **                     The Print item                           **
 ******************************************************************/

class PrintCellItem : public CellItem
{
  public:
  PrintCellItem( const KCalCore::Event::Ptr &event, const KDateTime &start, const KDateTime &end )
      : mEvent( event ), mStart( start ), mEnd( end )
    {
    }

    KCalCore::Event::Ptr event() const { return mEvent; }

    QString label() const { return mEvent->summary(); }

    KDateTime start() const { return mStart; }
    KDateTime end() const { return mEnd; }

    /** Calculate the start and end date/time of the recurrence that
        happens on the given day */
    bool overlaps( CellItem *o ) const
    {
      PrintCellItem *other = static_cast<PrintCellItem *>( o );
      return !( other->start() >= end() || other->end() <= start() );
    }

  private:
    KCalCore::Event::Ptr mEvent;
    KDateTime mStart, mEnd;
};

/******************************************************************
 **                    The Print plugin                          **
 ******************************************************************/

CalPrintPluginBase::CalPrintPluginBase()
  : PrintPlugin(), mUseColors( true ), mPrintFooter( true ),
    mHeaderHeight( -1 ), mSubHeaderHeight( SUBHEADER_HEIGHT ), mFooterHeight( -1 ),
    mMargin( MARGIN_SIZE ), mPadding( PADDING_SIZE ), mCalSys( 0 )
{
}

CalPrintPluginBase::~CalPrintPluginBase()
{
}

QWidget *CalPrintPluginBase::createConfigWidget( QWidget *w )
{
  QFrame *wdg = new QFrame( w );
  QVBoxLayout *layout = new QVBoxLayout( wdg );

  QLabel *title = new QLabel( description(), wdg );
  QFont titleFont( title->font() );
  titleFont.setPointSize( 20 );
  titleFont.setBold( true );
  title->setFont( titleFont );

  layout->addWidget( title );
  layout->addWidget( new QLabel( info(), wdg ) );
  layout->addSpacing( 20 );
  layout->addWidget(
    new QLabel( i18n( "This printing style does not have any configuration options." ), wdg ) );
  layout->addStretch();
  return wdg;
}

void CalPrintPluginBase::doPrint( QPrinter *printer )
{
  if ( !printer ) {
    return;
  }
  mPrinter = printer;
  QPainter p;

  mPrinter->setColorMode( mUseColors ? QPrinter::Color : QPrinter::GrayScale );

  p.begin( mPrinter );
  // TODO: Fix the margins!!!
  // the painter initially begins at 72 dpi per the Qt docs.
  // we want half-inch margins.
  int margins = margin();
  p.setViewport( margins, margins,
                 p.viewport().width() - 2 * margins,
                 p.viewport().height() - 2 * margins );
//   QRect vp( p.viewport() );
// vp.setRight( vp.right()*2 );
// vp.setBottom( vp.bottom()*2 );
//   p.setWindow( vp );
  int pageWidth = p.window().width();
  int pageHeight = p.window().height();
//   int pageWidth = p.viewport().width();
//   int pageHeight = p.viewport().height();

  print( p, pageWidth, pageHeight );

  p.end();
  mPrinter = 0;
}

void CalPrintPluginBase::doLoadConfig()
{
  if ( mConfig ) {
    KConfigGroup group( mConfig, groupName() );
    mConfig->sync();
    QDateTime dt = QDateTime::currentDateTime();
    mFromDate = group.readEntry( "FromDate", dt ).date();
    mToDate = group.readEntry( "ToDate", dt ).date();
    mUseColors = group.readEntry( "UseColors", true );
    mPrintFooter = group.readEntry( "PrintFooter", true );
    loadConfig();
  } else {
    kDebug() << "No config available in loadConfig!!!!";
  }
}

void CalPrintPluginBase::doSaveConfig()
{
  if ( mConfig ) {
    KConfigGroup group( mConfig, groupName() );
    saveConfig();
    QDateTime dt = QDateTime::currentDateTime(); // any valid QDateTime will do
    dt.setDate( mFromDate );
    group.writeEntry( "FromDate", dt );
    dt.setDate( mToDate );
    group.writeEntry( "ToDate", dt );
    group.writeEntry( "UseColors", mUseColors );
    group.writeEntry( "PrintFooter", mPrintFooter );
    mConfig->sync();
  } else {
    kDebug() << "No config available in saveConfig!!!!";
  }
}

bool CalPrintPluginBase::useColors() const
{
  return mUseColors;
}

void CalPrintPluginBase::setUseColors( bool useColors )
{
  mUseColors = useColors;
}

bool CalPrintPluginBase::printFooter() const
{
  return mPrintFooter;
}

void CalPrintPluginBase::setPrintFooter( bool printFooter )
{
  mPrintFooter = printFooter;
}

QPrinter::Orientation CalPrintPluginBase::orientation() const
{
  return mPrinter ? mPrinter->orientation() : QPrinter::Portrait;
}

QColor CalPrintPluginBase::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 );
}

QTime CalPrintPluginBase::dayStart() const
{
  QTime start( 8, 0, 0 );
  QDateTime dayBegins = KCalPrefs::instance()->dayBegins();
  if ( dayBegins.isValid() ) {
    start = dayBegins.time();
  }
  return start;
}

void CalPrintPluginBase::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 );
  }
}

QColor CalPrintPluginBase::categoryColor( const QStringList &categories ) const
{
  if ( categories.isEmpty() ) {
    return KCalPrefs::instance()->unsetCategoryColor();
  }
  // FIXME: Correctly treat events with multiple categories
  const QString cat = categories.first();
  QColor bgColor;
  if ( cat.isEmpty() ) {
    bgColor = KCalPrefs::instance()->unsetCategoryColor();
  } else {
    bgColor = KCalPrefs::instance()->categoryColor( cat );
  }
  return bgColor;
}

QColor CalPrintPluginBase::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() ) {
        backColor =  QColor( 255, 100, 100 ); //was KOPrefs::instance()->todoOverdueColor();
      }
    }
    return backColor;
  } else {
    return QColor();
  }
}

QString CalPrintPluginBase::holidayString( const QDate &date ) const
{
  QStringList lst = holiday( date );
  return lst.join( i18nc( "@item:intext delimiter for joining holiday names", "," ) );
}

KCalCore::Event::Ptr CalPrintPluginBase::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;
}

const KCalendarSystem *CalPrintPluginBase::calendarSystem()
{
  if ( !mCalSys ) {
    mCalSys = KGlobal::locale()->calendar();
  }
  return mCalSys;
}

void CalPrintPluginBase::setCalendarSystem( const KCalendarSystem *calsys )
{
  mCalSys = calsys;
}

int CalPrintPluginBase::headerHeight() const
{
  if ( mHeaderHeight >= 0 ) {
    return mHeaderHeight;
  } else if ( orientation() == QPrinter::Portrait ) {
    return PORTRAIT_HEADER_HEIGHT;
  } else {
    return LANDSCAPE_HEADER_HEIGHT;
  }
}

void CalPrintPluginBase::setHeaderHeight( const int height )
{
  mHeaderHeight = height;
}

int CalPrintPluginBase::subHeaderHeight() const
{
  return mSubHeaderHeight;
}

void CalPrintPluginBase::setSubHeaderHeight( const int height )
{
  mSubHeaderHeight = height;
}

int CalPrintPluginBase::footerHeight() const
{
  if ( !mPrintFooter ) {
    return 0;
  }

  if ( mFooterHeight >= 0 ) {
    return mFooterHeight;
  } else if ( orientation() == QPrinter::Portrait ) {
    return PORTRAIT_FOOTER_HEIGHT;
  } else {
    return LANDSCAPE_FOOTER_HEIGHT;
  }
}

void CalPrintPluginBase::setFooterHeight( const int height )
{
  mFooterHeight = height;
}

int CalPrintPluginBase::margin() const
{
  return mMargin;
}

void CalPrintPluginBase::setMargin( const int margin )
{
  mMargin = margin;
}

int CalPrintPluginBase::padding() const
{
  return mPadding;
}

void CalPrintPluginBase::setPadding( const int padding )
{
  mPadding = padding;
}

int CalPrintPluginBase::borderWidth() const
{
  return mBorder;
}

void CalPrintPluginBase::setBorderWidth( const int borderwidth )
{
  mBorder = borderwidth;
}

void CalPrintPluginBase::drawBox( QPainter &p, int linewidth, const QRect &rect )
{
  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 CalPrintPluginBase::drawShadedBox( QPainter &p, int linewidth,
                                        const QBrush &brush, const QRect &rect )
{
  QBrush oldbrush( p.brush() );
  p.setBrush( brush );
  drawBox( p, linewidth, rect );
  p.setBrush( oldbrush );
}

void CalPrintPluginBase::printEventString( QPainter &p, const QRect &box,
                                           const QString &str, int flags )
{
  QRect newbox( box );
  newbox.adjust( 3, 1, -1, -1 );
  p.drawText( newbox, ( flags == -1 ) ?
              ( Qt::AlignTop | Qt::AlignLeft | Qt::TextWordWrap ) : flags, str );
}

void CalPrintPluginBase::showEventBox( QPainter &p, int linewidth, const QRect &box,
                                       const KCalCore::Incidence::Ptr &incidence, const QString &str,
                                       int flags )
{
  QPen oldpen( p.pen() );
  QBrush oldbrush( p.brush() );
  QColor bgColor( categoryBgColor( incidence ) );
  if ( mUseColors & bgColor.isValid() ) {
    p.setBrush( bgColor );
  } else {
    p.setBrush( QColor( 232, 232, 232 ) );
  }
  drawBox( p, ( linewidth > 0 ) ? linewidth : EVENT_BORDER_WIDTH, box );
  if ( mUseColors && bgColor.isValid() ) {
    p.setPen( getTextColor( bgColor ) );
  }
  printEventString( p, box, str, flags );
  p.setPen( oldpen );
  p.setBrush( oldbrush );
}

void CalPrintPluginBase::drawSubHeaderBox( QPainter &p, const QString &str, const QRect &box )
{
  drawShadedBox( p, BOX_BORDER_WIDTH, QColor( 232, 232, 232 ), box );
  QFont oldfont( p.font() );
  p.setFont( QFont( QLatin1String("sans-serif"), 10, QFont::Bold ) );
  p.drawText( box, Qt::AlignCenter | Qt::AlignVCenter, str );
  p.setFont( oldfont );
}

void CalPrintPluginBase::drawVerticalBox( QPainter &p, int linewidth, const QRect &box,
                                          const QString &str, int flags )
{
  p.save();
  p.rotate( -90 );
  QRect rotatedBox( -box.top() - box.height(), box.left(), box.height(), box.width() );
  showEventBox( p, linewidth, rotatedBox, KCalCore::Incidence::Ptr(), str,
                ( flags == -1 ) ? Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine : flags );

  p.restore();
}

/*
 * Return value: If expand, bottom of the printed box, otherwise vertical end
 * of the printed contents inside the box.
 */
int CalPrintPluginBase::drawBoxWithCaption( QPainter &p, const QRect &allbox,
                                            const QString &caption,
                                            const QString &contents,
                                            bool sameLine, bool expand,
                                            const QFont &captionFont,
                                            const QFont &textFont,
                                            bool richContents )
{
  QFont oldFont( p.font() );
//   QFont captionFont( "sans-serif", 11, QFont::Bold );
//   QFont textFont( "sans-serif", 11, QFont::Normal );
//   QFont captionFont( "Tahoma", 11, QFont::Bold );
//   QFont textFont( "Tahoma", 11, QFont::Normal );

  QRect box( allbox );

  // Bounding rectangle for caption, single-line, clip on the right
  QRect captionBox( box.left() + padding(), box.top() + padding(), 0, 0 );
  p.setFont( captionFont );
  captionBox = p.boundingRect( captionBox,
                               Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine,
                               caption );
  p.setFont( oldFont );
  if ( captionBox.right() > box.right() ) {
    captionBox.setRight( box.right() );
  }
  if ( expand && captionBox.bottom() + padding() > box.bottom() ) {
    box.setBottom( captionBox.bottom() + padding() );
  }

  // Bounding rectangle for the contents (if any), word break, clip on the bottom
  QRect textBox( captionBox );
  if ( !contents.isEmpty() ) {
    if ( sameLine ) {
      textBox.setLeft( captionBox.right() + padding() );
    } else {
      textBox.setTop( captionBox.bottom() + padding() );
    }
    textBox.setRight( box.right() );
  }
  drawBox( p, BOX_BORDER_WIDTH, box );
  p.setFont( captionFont );
  p.drawText( captionBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine,
              caption );

  if ( !contents.isEmpty() ) {
    if ( sameLine ) {
      QString contentText = toPlainText( contents );
      p.setFont( textFont );
      p.drawText( textBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine,
                  contents );
    } else {
      QTextDocument rtb;
      int borderWidth = 2 * BOX_BORDER_WIDTH;
      if ( richContents ) {
        rtb.setHtml( contents );
      } else {
        rtb.setPlainText( contents );
      }
      int boxHeight = allbox.height();
      if ( !sameLine ) {
        boxHeight -= captionBox.height();
      }
      rtb.setPageSize( QSize( textBox.width(), boxHeight ) );
      rtb.setDefaultFont( textFont );
      p.save();
      p.translate( textBox.x() - borderWidth, textBox.y() );
      QRect clipBox( 0, 0, box.width(), boxHeight );
      rtb.drawContents( &p, clipBox );
      p.restore();
      textBox.setBottom( textBox.y() +
                         rtb.documentLayout()->documentSize().height() );
    }
  }
  p.setFont( oldFont );

  if ( expand ) {
    return box.bottom();
  } else {
    return textBox.bottom();
  }
}

int CalPrintPluginBase::drawHeader( QPainter &p, const QString &title,
    const QDate &month1, const QDate &month2, const QRect &allbox,
    bool expand, QColor backColor )
{
  // 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() );
  QFont newFont( QLatin1String("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 );
    }
  }

  if ( !backColor.isValid() ) {
    backColor = QColor( 232, 232, 232 );
  }

  drawShadedBox( p, BOX_BORDER_WIDTH, backColor, box );

#if 0
  // current month title left justified, prev month, next month right justified
  QRect monthbox2( box.right()-10-smallMonthWidth, box.top(),
                   smallMonthWidth, box.height() );
  if ( month2.isValid() ) {
    drawSmallMonth( p, QDate( month2.year(), month2.month(), 1 ), monthbox2 );
    textRect.setRight( monthbox2.left() );
  }
  QRect monthbox1( monthbox2.left()-10-smallMonthWidth, box.top(),
                   smallMonthWidth, box.height() );
  if ( month1.isValid() ) {
    drawSmallMonth( p, QDate( month1.year(), month1.month(), 1 ), monthbox1 );
    textRect.setRight( monthbox1.left() );
  }

  // Set the margins
  p.setFont( newFont );
  textRect.adjust( 5, 0, 0, 0 );
  p.drawText( textRect, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, title );
  p.setFont( oldFont );
#endif
  // prev month left, current month centered, next month right
  QRect monthbox2( box.right()-10-smallMonthWidth, box.top(),
                   smallMonthWidth, box.height() );
  if ( month2.isValid() ) {
    drawSmallMonth( p, QDate( month2.year(), month2.month(), 1 ), monthbox2 );
    textRect.setRight( monthbox2.left() );
  }
  QRect monthbox1( box.left()+10, box.top(), smallMonthWidth, box.height() );
  if ( month1.isValid() ) {
    drawSmallMonth( p, QDate( month1.year(), month1.month(), 1 ), monthbox1 );
    textRect.setLeft( monthbox1.right() );
  }

  // Set the margins
  p.setFont( newFont );
  p.drawText( textRect, Qt::AlignCenter | Qt::AlignVCenter | Qt::TextWordWrap, title );
  p.setFont( oldFont );

  return textRect.bottom();
}

int CalPrintPluginBase::drawFooter( QPainter &p, const QRect &footbox )
{
  QFont oldfont( p.font() );
  p.setFont( QFont( QLatin1String("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 CalPrintPluginBase::drawSmallMonth( QPainter &p, const QDate &qd, const QRect &box )
{
  int weekdayCol = weekdayColumn( qd.dayOfWeek() );
  int month = qd.month();
  QDate monthDate( QDate( qd.year(), qd.month(), 1 ) );
  // correct begin of week
  QDate monthDate2( monthDate.addDays( -weekdayCol ) );

  double cellWidth = double( box.width() ) / double( 7 );
  int rownr = 3 + ( qd.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() );
  p.setFont( QFont( QLatin1String("sans-serif"), int(cellHeight-2), QFont::Normal ) );

  // draw the title
  if ( mCalSys ) {
    QRect titleBox( box );
    titleBox.setHeight( int( cellHeight + 1 ) );
    p.drawText( titleBox, Qt::AlignTop | Qt::AlignHCenter, mCalSys->monthName( qd ) );
  }

  // draw days of week
  QRect wdayBox( box );
  wdayBox.setTop( int( box.top() + 3 + cellHeight ) );
  wdayBox.setHeight( int( 2 * cellHeight ) - int( cellHeight ) );

  if ( mCalSys ) {
    for ( int col = 0; col < 7; ++col ) {
      QString tmpStr = mCalSys->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 );
}

/*
 * This routine draws a header box over the main part of the calendar
 * containing the days of the week.
 */
void CalPrintPluginBase::drawDaysOfWeek( QPainter &p,
                                         const QDate &fromDate,
                                         const QDate &toDate, const QRect &box )
{
  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, cellDate, dateBox );
    cellDate = cellDate.addDays( 1 );
    ++i;
  }
}

void CalPrintPluginBase::drawDaysOfWeekBox( QPainter &p, const QDate &qd, const QRect &box )
{
  drawSubHeaderBox( p, ( mCalSys ) ? ( mCalSys->weekDayName( qd ) ) : QString(), box );
}

void CalPrintPluginBase::drawTimeLine( QPainter &p, const QTime &fromTime,
                                       const QTime &toTime, const QRect &box )
{
  drawBox( p, BOX_BORDER_WIDTH, box );

  int totalsecs = fromTime.secsTo( toTime );
  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( fromTime );
  QTime endTime( toTime );
  if ( fromTime.minute() > 30 ) {
    curTime = QTime( fromTime.hour()+1, 0, 0 );
  } else if ( fromTime.minute() > 0 ) {
    curTime = QTime( fromTime.hour(), 30, 0 );
    float yy = currY + minlen * (float)fromTime.secsTo( curTime ) / 60.;
    p.drawLine( xcenter, (int)yy, box.right(), (int)yy );
    curTime = QTime( fromTime.hour() + 1, 0, 0 );
  }
  currY += ( float( fromTime.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() );
        if ( cellHeight > 30 ) {
          p.setFont( QFont( QLatin1String("sans-serif"), 14, QFont::Bold ) );
        } else {
          p.setFont( QFont( QLatin1String("sans-serif"), 12, QFont::Bold ) );
        }
        p.drawText( box.left() + 4, (int)currY + 2, box.width() / 2 - 2, (int)cellHeight,
                  Qt::AlignTop | Qt::AlignRight, numStr );
        p.setFont( QFont( QLatin1String("helvetica"), 10, QFont::Normal ) );
        p.drawText( xcenter + 4, (int)currY+2, box.width() / 2 + 2, (int)( cellHeight / 2 ) - 3,
                  Qt::AlignTop | Qt::AlignLeft, QLatin1String("00") );
      } else {
        p.drawLine( box.left(), (int)newY, box.right(), (int)newY );
        QTime time( curTime.hour(), 0 );
        numStr = KGlobal::locale()->formatTime( time );
        if ( box.width() < 60 ) {
          p.setFont( QFont( QLatin1String("sans-serif"), 7, QFont::Bold ) ); // for weekprint
        } else {
          p.setFont( QFont( QLatin1String("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;
    }
  }
}

/**
  prints the all-day box for the agenda print view. if expandable is set,
  height is the cell height of a single cell, and the returned height will
  be the total height used for the all-day events. If !expandable, only one
  cell will be used, and multiple events are concatenated using ", ".
*/
int CalPrintPluginBase::drawAllDayBox( QPainter &p, const KCalCore::Event::List &eventList_,
                                       const QDate &qd, bool expandable,
                                       const QRect &box,
                                       bool excludeConfidential,
                                       bool excludePrivate )
{
  KCalCore::Event::List::Iterator it;
  int offset = box.top();
  QString multiDayStr;

  KCalCore::Event::List eventList = eventList_;
  KCalCore::Event::Ptr hd = holidayEvent( qd );
  if ( hd ) {
    eventList.prepend( hd );
  }

  it = eventList.begin();
  while ( it != eventList.end() ) {
    KCalCore::Event::Ptr currEvent = *it;
    if ( ( excludeConfidential && currEvent->secrecy() == KCalCore::Incidence::SecrecyConfidential ) ||
         ( excludePrivate      && currEvent->secrecy() == KCalCore::Incidence::SecrecyPrivate ) ) {
      continue;
    }
    if ( currEvent && currEvent->allDay() ) {
      // set the colors according to the categories
      if ( expandable ) {
        QRect eventBox( box );
        eventBox.setTop( offset );
        showEventBox( p, EVENT_BORDER_WIDTH, eventBox, currEvent, currEvent->summary() );
        offset += box.height();
      } else {
        if ( !multiDayStr.isEmpty() ) {
          multiDayStr += QLatin1String(", ");
        }
        multiDayStr += currEvent->summary();
      }
      it = eventList.erase( it );
    } else {
      ++it;
    }
  }

  int ret = box.height();
  QRect eventBox( box );
  if ( !expandable ) {
    if ( !multiDayStr.isEmpty() ) {
      drawShadedBox( p, BOX_BORDER_WIDTH, QColor( 180, 180, 180 ), eventBox );
      printEventString( p, eventBox, multiDayStr );
    } else {
      drawBox( p, BOX_BORDER_WIDTH, eventBox );
    }
  } else {
    ret = offset - box.top();
    eventBox.setBottom( ret );
    drawBox( p, BOX_BORDER_WIDTH, eventBox );
  }
  return ret;
}

void CalPrintPluginBase::drawAgendaDayBox( QPainter &p, const KCalCore::Event::List &events,
                                           const QDate &qd, bool expandable,
                                           const QTime &fromTime,
                                           const QTime &toTime,
                                           const QRect &oldbox,
                                           bool includeDescription,
                                           bool excludeTime,
                                           bool excludeConfidential,
                                           bool excludePrivate,
                                           const QList<QDate> &workDays )
{
  QTime myFromTime, myToTime;
  if ( fromTime.isValid() ) {
    myFromTime = fromTime;
  } else {
    myFromTime = QTime( 0, 0, 0 );
  }
  if ( toTime.isValid() ) {
    myToTime = toTime;
  } else {
    myToTime = QTime( 23, 59, 59 );
  }

  if ( !workDays.contains( qd ) ) {
    drawShadedBox( p, BOX_BORDER_WIDTH, QColor( 232, 232, 232 ), oldbox );
  } else {
    drawBox( p, BOX_BORDER_WIDTH, oldbox );
  }
  QRect box( oldbox );
  // Account for the border with and cut away that margin from the interior
//   box.setRight( box.right()-BOX_BORDER_WIDTH );

  if ( expandable ) {
    // Adapt start/end times to include complete events
    Q_FOREACH ( const KCalCore::Event::Ptr &event, events ) {
      Q_ASSERT( event );
      if ( ( excludeConfidential && event->secrecy() == KCalCore::Incidence::SecrecyConfidential ) ||
           ( excludePrivate      && 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 ) );
      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( qd, myFromTime );
  KDateTime endPrintDate = KDateTime( qd, myToTime );

  // Calculate horizontal positions and widths of events taking into account
  // overlapping events

  QList<CellItem *> cells;

  Akonadi::Item::List::ConstIterator itEvents;
  foreach ( const KCalCore::Event::Ptr &event, events ) {
    if ( event->allDay() ) {
      continue;
    }
    QList<KDateTime> times = event->startDateTimesForDate( qd );
    for ( QList<KDateTime>::ConstIterator 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,
                    includeDescription, excludeTime );
  }
}

void CalPrintPluginBase::drawAgendaItem( PrintCellItem *item, QPainter &p,
                                         const KDateTime &startPrintDate,
                                         const KDateTime &endPrintDate,
                                         float minlen, const QRect &box,
                                         bool includeDescription,
                                         bool excludeTime )
{
  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 ( excludeTime ) {
      if ( event->location().isEmpty() ) {
        str = cleanStr( event->summary() );
      } else {
        str = i18nc( "summary, location", "%1, %2",
                     cleanStr( event->summary() ), cleanStr( 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() ),
                     cleanStr( 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() ),
                     cleanStr( event->summary() ),
                     cleanStr( event->location() ) );
      }
    }
    if ( includeDescription && !event->description().isEmpty() ) {
      str += QLatin1Char('\n');
      if ( event->descriptionIsRich() ) {
        str += toPlainText( event->description() );
      } else {
        str += event->description();
      }
    }
    QFont oldFont( p.font() );
    if ( eventBox.height() < 24 ) {
      if ( eventBox.height() < 12 ) {
        if ( eventBox.height() < 8 ) {
          p.setFont( QFont( QLatin1String("sans-serif"), 4 ) );
        } else {
          p.setFont( QFont( QLatin1String("sans-serif"), 5 ) );
        }
      } else {
        p.setFont( QFont( QLatin1String("sans-serif"), 6 ) );
      }
    } else {
       p.setFont( QFont( QLatin1String("sans-serif"), 8 ) );
    }
    showEventBox( p, EVENT_BORDER_WIDTH, eventBox, event, str );
    p.setFont( oldFont );
  }
}

void CalPrintPluginBase::drawDayBox( QPainter &p, const QDate &qd,
                                     const QTime &fromTime, const QTime &toTime,
                                     const QRect &box, bool fullDate,
                                     bool printRecurDaily, bool printRecurWeekly,
                                     bool singleLineLimit, bool showNoteLines,
                                     bool includeDescription,
                                     bool excludeConfidential,
                                     bool excludePrivate )
{
  QString dayNumStr;
  const KLocale *local = KGlobal::locale();

  QTime myFromTime, myToTime;
  if ( fromTime.isValid() ) {
    myFromTime = fromTime;
  } else {
    myFromTime = QTime( 0, 0, 0 );
  }
  if ( toTime.isValid() ) {
    myToTime = toTime;
  } else {
    myToTime = QTime( 23, 59, 59 );
  }

  if ( fullDate && mCalSys ) {
    dayNumStr = i18nc( "weekday, shortmonthname daynumber",
                       "%1, %2 <numid>%3</numid>",
                       mCalSys->weekDayName( qd ),
                       mCalSys->monthName( qd, KCalendarSystem::ShortName ),
                       qd.day() );
  } else {
    dayNumStr = QString::number( qd.day() );
  }

  QRect subHeaderBox( box );
  subHeaderBox.setHeight( mSubHeaderHeight );
  drawShadedBox( p, BOX_BORDER_WIDTH, p.background(), box );
  drawShadedBox( p, 0, QColor( 232, 232, 232 ), subHeaderBox );
  drawBox( p, BOX_BORDER_WIDTH, box );
  QString hstring( holidayString( qd ) );
  const QFont oldFont( p.font() );

  QRect headerTextBox( subHeaderBox );
  headerTextBox.setLeft( subHeaderBox.left() + 5 );
  headerTextBox.setRight( subHeaderBox.right() - 5 );
  if ( !hstring.isEmpty() ) {
    p.setFont( QFont( QLatin1String("sans-serif"), 8, QFont::Bold, true ) );
    p.drawText( headerTextBox, Qt::AlignLeft | Qt::AlignVCenter, hstring );
  }
  p.setFont( QFont( QLatin1String("sans-serif"), 10, QFont::Bold ) );
  p.drawText( headerTextBox, Qt::AlignRight | Qt::AlignVCenter, dayNumStr );

  const KCalCore::Event::List eventList =
    mCalendar->events( qd, KSystemTimeZones::local(),
                       KCalCore::EventSortStartDate,
                       KCalCore::SortDirectionAscending );

  QString timeText;
  p.setFont( QFont( QLatin1String("sans-serif"), 7 ) );

  int textY = mSubHeaderHeight; // 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() <= myFromTime ||
           currEvent->dtStart().toLocalZone().time() > myToTime ) {
        continue;
      }
    }
    if ( ( !printRecurDaily  && currEvent->recurrenceType() == KCalCore::Recurrence::rDaily ) ||
         ( !printRecurWeekly && currEvent->recurrenceType() == KCalCore::Recurrence::rWeekly ) ) {
      continue;
    }
    if ( ( excludeConfidential && currEvent->secrecy() == KCalCore::Incidence::SecrecyConfidential ) ||
         ( excludePrivate      && currEvent->secrecy() == KCalCore::Incidence::SecrecyPrivate ) ) {
      continue;
    }
    if ( currEvent->allDay() || currEvent->isMultiDay() ) {
      timeText.clear();
    } else {
      timeText = local->formatTime( currEvent->dtStart().toLocalZone().time() ) + QLatin1Char(' ');
    }
    p.save();
    setColorsByIncidenceCategory( p, currEvent );
    QString summaryStr = currEvent->summary();
    if ( !currEvent->location().isEmpty() ) {
      summaryStr = i18nc( "summary, location",
                          "%1, %2", summaryStr, currEvent->location() );
    }
    drawIncidence( p, box, timeText,
                   summaryStr, currEvent->description(),
                   textY, singleLineLimit, includeDescription,
                   currEvent->descriptionIsRich() );
    p.restore();
    visibleEventsCounter++;

    if ( textY >= box.height() ) {
      const QChar downArrow( 0x21e3 );

      const unsigned int invisibleIncidences =
        ( eventList.count() - visibleEventsCounter ) + mCalendar->todos( qd ).count();
      if ( invisibleIncidences > 0 ) {
        const QString warningMsg = QString::fromLatin1( "%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 todos = mCalendar->todos( qd );
    foreach ( const KCalCore::Todo::Ptr &todo, todos ) {
      if ( !todo->allDay() ) {
        if ( ( todo->hasDueDate() && todo->dtDue().toLocalZone().time() <= myFromTime ) ||
             ( todo->hasStartDate() && todo->dtStart().toLocalZone().time() > myToTime ) ) {
          continue;
        }
      }
      if ( ( !printRecurDaily  && todo->recurrenceType() == KCalCore::Recurrence::rDaily ) ||
           ( !printRecurWeekly && todo->recurrenceType() == KCalCore::Recurrence::rWeekly ) ) {
        continue;
      }
      if ( ( excludeConfidential && todo->secrecy() == KCalCore::Incidence::SecrecyConfidential ) ||
           ( excludePrivate      && todo->secrecy() == KCalCore::Incidence::SecrecyPrivate ) ) {
        continue;
      }
      if ( todo->hasStartDate() && !todo->allDay() ) {
        timeText = KGlobal::locale()->formatTime( todo->dtStart().toLocalZone().time() ) + QLatin1Char(' ');
      } 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;
      }
      drawIncidence( p, box, timeText,
                     i18n( "To-do: %1", str ), todo->description(),
                     textY, singleLineLimit, includeDescription,
                     todo->descriptionIsRich() );
      p.restore();
    }
  }
  if ( showNoteLines ) {
    drawNoteLines( p, box, box.y() + textY );
  }

  p.setFont( oldFont );
}

void CalPrintPluginBase::drawIncidence( QPainter &p, const QRect &dayBox,
                                        const QString &time,
                                        const QString &summary,
                                        const QString &description,
                                        int &textY,  bool singleLineLimit,
                                        bool includeDescription,
                                        bool richDescription )
{
  kDebug() << "summary =" << summary << ", singleLineLimit=" << singleLineLimit;

  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;

  if ( singleLineLimit ) {
    if ( includeDescription && !descText.isEmpty() ) {
      summaryText += QLatin1String(", ") + 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( QLatin1String("\n") );
        textCursor.insertHtml( description );
      }
    } else {
      textCursor.insertText( summaryText );
      if ( includeDescription && !descText.isEmpty() ) {
        textCursor.insertText( QLatin1String("\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 CalPrintPluginBase::drawWeek( QPainter &p, const QDate &qd,
                                   const QTime &fromTime, const QTime &toTime,
                                   const QRect &box,
                                   bool singleLineLimit, bool showNoteLines,
                                   bool includeDescription,
                                   bool excludeConfidential,
                                   bool excludePrivate )
{
  QDate weekDate = qd;
  const bool portrait = ( box.height() > box.width() );
  int cellWidth;
  int vcells;
  if ( portrait ) {
    cellWidth = box.width() / 2;
    vcells=3;
  } else {
    cellWidth = box.width() / 6;
    vcells=1;
  }
  const int cellHeight = box.height() / vcells;

  // correct begin of week
  int weekdayCol = weekdayColumn( qd.dayOfWeek() );
  weekDate = qd.addDays( -weekdayCol );

  for ( int i = 0; i < 7; ++i, weekDate = weekDate.addDays(1) ) {
    // Saturday and sunday share a cell, so we have to special-case sunday
    int hpos = ( ( i < 6 ) ? i : ( i - 1 ) ) / vcells;
    int vpos = ( ( i < 6 ) ? i : ( i - 1 ) ) % vcells;
    QRect dayBox(
      box.left() + cellWidth * hpos,
      box.top() + cellHeight * vpos + ( ( i == 6 ) ? ( cellHeight / 2 ) : 0 ),
      cellWidth, ( i < 5 ) ? ( cellHeight ) : ( cellHeight / 2 ) );
    drawDayBox( p, weekDate, fromTime, toTime, dayBox, true, true, true,
                singleLineLimit, showNoteLines, includeDescription,
                excludeConfidential, excludePrivate );
  } // for i through all weekdays
}

void CalPrintPluginBase::drawDays( QPainter &p,
                                   const QDate &start, const QDate &end,
                                   const QTime &fromTime, const QTime &toTime,
                                   const QRect &box,
                                   bool singleLineLimit, bool showNoteLines,
                                   bool includeDescription,
                                   bool excludeConfidential, bool excludePrivate )
{
  const int numberOfDays = start.daysTo( end ) + 1;
  int vcells;
  const bool portrait = ( box.height() > box.width() );
  int cellWidth;
  if ( portrait ) {
    // 2 columns
    vcells = qCeil( static_cast<double>( numberOfDays ) / 2.0 );
    if ( numberOfDays > 1 ) {
      cellWidth = box.width() / 2;
    } else {
      cellWidth = box.width();
    }
  } else {
    // landscape: N columns
    vcells = 1;
    cellWidth = box.width() / numberOfDays;
  }
  const int cellHeight = box.height() / vcells;
  QDate weekDate = start;
  for ( int i = 0; i < numberOfDays; ++i, weekDate = weekDate.addDays(1) ) {
    const int hpos = i / vcells;
    const int vpos = i % vcells;
    const QRect dayBox(
      box.left() + cellWidth * hpos,
      box.top() + cellHeight * vpos,
      cellWidth, cellHeight );
    drawDayBox( p, weekDate, fromTime, toTime, dayBox,
                true, true, true, singleLineLimit,
                showNoteLines, includeDescription, excludeConfidential,
                excludePrivate );
  } // for i through all selected days
}

void CalPrintPluginBase::drawTimeTable( QPainter &p,
                                        const QDate &fromDate,
                                        const QDate &toDate,
                                        bool expandable,
                                        const QTime &fromTime,
                                        const QTime &toTime,
                                        const QRect &box,
                                        bool includeDescription,
                                        bool excludeTime,
                                        bool excludeConfidential,
                                        bool excludePrivate )
{
  QTime myFromTime = fromTime;
  QTime myToTime = toTime;
  if ( expandable ) {
    QDate curDate( fromDate );
    KDateTime::Spec timeSpec = KSystemTimeZones::local();
    while ( curDate <= toDate ) {
      KCalCore::Event::List eventList = mCalendar->events( curDate, timeSpec );
      Q_FOREACH ( const KCalCore::Event::Ptr &event, eventList ) {
        Q_ASSERT( event );
        if ( event->allDay() ) {
          continue;
        }
        if ( event->dtStart().time() < myFromTime ) {
          myFromTime = event->dtStart().time();
        }
        if ( event->dtEnd().time() > myToTime ) {
          myToTime = event->dtEnd().time();
        }
      }
      curDate = curDate.addDays(1);
    }
  }

  // timeline is 1 hour:
  int alldayHeight = (int)( 3600. * box.height() / ( myFromTime.secsTo( myToTime ) + 3600. ) );
  int timelineWidth = TIMELINE_WIDTH;

  QRect dowBox( box );
  dowBox.setLeft( box.left() + timelineWidth );
  dowBox.setHeight( mSubHeaderHeight );
  drawDaysOfWeek( p, fromDate, toDate, dowBox );

  QRect tlBox( box );
  tlBox.setWidth( timelineWidth );
  tlBox.setTop( dowBox.bottom() + BOX_BORDER_WIDTH + alldayHeight );
  drawTimeLine( p, myFromTime, myToTime, tlBox );

  // draw each day
  QDate curDate( fromDate );
  KDateTime::Spec timeSpec = KSystemTimeZones::local();
  int i=0;
  double cellWidth = double( dowBox.width() ) / double( fromDate.daysTo( toDate ) + 1 );
  const QList<QDate> workDays = CalendarSupport::workDays( fromDate, toDate );
  while ( curDate <= toDate ) {
    QRect allDayBox( dowBox.left()+int( i * cellWidth ), dowBox.bottom() + BOX_BORDER_WIDTH,
                     int( ( i + 1 ) * cellWidth ) - int( i * cellWidth ), alldayHeight );
    QRect dayBox( allDayBox );
    dayBox.setTop( tlBox.top() );
    dayBox.setBottom( box.bottom() );
    KCalCore::Event::List eventList = mCalendar->events( curDate, timeSpec,
                                                         KCalCore::EventSortStartDate,
                                                         KCalCore::SortDirectionAscending );

    alldayHeight = drawAllDayBox( p, eventList, curDate, false, allDayBox,
                                  excludeConfidential, excludePrivate );
    drawAgendaDayBox( p, eventList, curDate, false, myFromTime, myToTime,
                      dayBox, includeDescription, excludeTime,
                      excludeConfidential, excludePrivate, workDays );

    ++i;
    curDate = curDate.addDays(1);
  }
}

class MonthEventStruct
{
  public:
    MonthEventStruct() : event(0) {}
    MonthEventStruct( const KDateTime &s, const KDateTime &e, const KCalCore::Event::Ptr &ev )
    {
      event = ev;
      start = s;
      end = e;
      if ( event->allDay() ) {
        start = KDateTime( start.date(), QTime( 0, 0, 0 ) );
        end = KDateTime( end.date().addDays(1), QTime( 0, 0, 0 ) ).addSecs(-1);
      }
    }
    bool operator < ( const MonthEventStruct &mes ) { return start < mes.start; }
    KDateTime start;
    KDateTime end;
    KCalCore::Event::Ptr event;
};

void CalPrintPluginBase::drawMonth( QPainter &p, const QDate &dt,
                                    const QRect &box, int maxdays,
                                    int subDailyFlags, int holidaysFlags,
                                    bool excludeConfidential, bool excludePrivate )
{
  p.save();
  const KCalendarSystem *calsys = calendarSystem();
  QRect subheaderBox( box );
  subheaderBox.setHeight( subHeaderHeight() );
  QRect borderBox( box );
  borderBox.setTop( subheaderBox.bottom() + 1 );
  drawSubHeaderBox( p, calsys->monthName( dt ), subheaderBox );
  // correct for half the border width
  int correction = ( BOX_BORDER_WIDTH/*-1*/ ) / 2;
  QRect daysBox( borderBox );
  daysBox.adjust( correction, correction, -correction, -correction );

  int daysinmonth = calsys->daysInMonth( dt );
  if ( maxdays <= 0 ) {
    maxdays = daysinmonth;
  }

  int d;
  float dayheight = float( daysBox.height() ) / float( maxdays );

  QColor holidayColor( 240, 240, 240 );
  QColor workdayColor( 255, 255, 255 );
  int dayNrWidth = p.fontMetrics().width( QLatin1String("99") );

  // Fill the remaining space (if a month has less days than others) with a crossed-out pattern
  if ( daysinmonth<maxdays ) {
    QRect dayBox( box.left(), daysBox.top() + qRound( dayheight * daysinmonth ), box.width(), 0 );
    dayBox.setBottom( daysBox.bottom() );
    p.fillRect( dayBox, Qt::DiagCrossPattern );
  }
  // Backgrounded boxes for each day, plus day numbers
  QBrush oldbrush( p.brush() );

  QList<QDate> workDays;

  {
    QDate startDate;
    QDate endDate;
    calsys->setDate( startDate, dt.year(), dt.month(), 1 );
    calsys->setDate( endDate, dt.year(), dt.month(), daysinmonth );

    workDays = CalendarSupport::workDays( startDate, endDate );
  }

  for ( d = 0; d < daysinmonth; ++d ) {
    QDate day;
    calsys->setDate( day, dt.year(), dt.month(), d+1 );
    QRect dayBox(
      daysBox.left()/*+rand()%50*/,
      daysBox.top() + qRound( dayheight * d ), daysBox.width()/*-rand()%50*/, 0 );
    // FIXME: When using a border width of 0 for event boxes,
    // don't let the rectangles overlap, i.e. subtract 1 from the top or bottom!
    dayBox.setBottom( daysBox.top() + qRound( dayheight * ( d + 1 ) ) - 1 );

    p.setBrush( workDays.contains( day ) ? workdayColor : holidayColor );
    p.drawRect( dayBox );
    QRect dateBox( dayBox );
    dateBox.setWidth( dayNrWidth + 3 );
    p.drawText( dateBox,
                Qt::AlignRight | Qt::AlignVCenter | Qt::TextSingleLine,
                QString::number( d + 1 ) );
  }
  p.setBrush( oldbrush );
  int xstartcont = box.left() + dayNrWidth + 5;

  QDate start, end;
  calsys->setDate( start, dt.year(), dt.month(), 1 );
  end = calsys->addMonths( start, 1 );
  end = calsys->addDays( end, -1 );

  const KCalCore::Event::List events = mCalendar->events( start, end );
  QMap<int, QStringList> textEvents;
  QList<CellItem *> timeboxItems;

  // 1) For multi-day events, show boxes spanning several cells, use CellItem
  //    print the summary vertically
  // 2) For sub-day events, print the concated summaries into the remaining
  //    space of the box (optional, depending on the given flags)
  // 3) Draw some kind of timeline showing free and busy times

  // Holidays
  QList<KCalCore::Event::Ptr> holidays;
  for ( QDate d( start ); d <= end; d = d.addDays(1) ) {
    KCalCore::Event::Ptr e = holidayEvent( d );
    if ( e ) {
      holidays.append( e );
      if ( holidaysFlags & TimeBoxes ) {
        timeboxItems.append( new PrintCellItem( e, KDateTime( d, QTime( 0, 0, 0 ) ),
                                                KDateTime( d.addDays(1), QTime( 0, 0, 0 ) ) ) );
      }
      if ( holidaysFlags & Text ) {
        textEvents[d.day()] << e->summary();
      }
    }
  }

  QList<MonthEventStruct> monthentries;

  KDateTime::Spec timeSpec = KSystemTimeZones::local();
  Q_FOREACH ( const KCalCore::Event::Ptr &e, events ) {
    if ( !e ) {
      continue;
    }
    if ( ( excludeConfidential && e->secrecy() == KCalCore::Incidence::SecrecyConfidential ) ||
         ( excludePrivate      && e->secrecy() == KCalCore::Incidence::SecrecyPrivate ) ) {
      continue;
    }
    if ( e->recurs() ) {
      if ( e->recursOn( start, timeSpec ) ) {
        // This occurrence has possibly started before the beginning of the
        // month, so obtain the start date before the beginning of the month
        QList<KDateTime> starttimes = e->startDateTimesForDate( start );
        QList<KDateTime>::ConstIterator it = starttimes.constBegin();
        for ( ; it != starttimes.constEnd(); ++it ) {
          monthentries.append( MonthEventStruct( *it, e->endDateForStart( *it ), e ) );
        }
      }
      // Loop through all remaining days of the month and check if the event
      // begins on that day (don't use Event::recursOn, as that will
      // also return events that have started earlier. These start dates
      // however, have already been treated!
      KCalCore::Recurrence *recur = e->recurrence();
      QDate d1( start.addDays(1) );
      while ( d1 <= end ) {
        if ( recur->recursOn( d1, timeSpec ) ) {
          KCalCore::TimeList times( recur->recurTimesOn( d1, timeSpec ) );
          for ( KCalCore::TimeList::ConstIterator it = times.constBegin();
                it != times.constEnd(); ++it ) {
            KDateTime d1start( d1, *it, timeSpec );
            monthentries.append( MonthEventStruct( d1start, e->endDateForStart( d1start ), e ) );
          }
        }
        d1 = d1.addDays(1);
      }
    } else {
      monthentries.append( MonthEventStruct( e->dtStart(), e->dtEnd(), e ) );
    }
  }

// TODO: to port the month entries sorting

//  qSort( monthentries.begin(), monthentries.end() );

  QList<MonthEventStruct>::ConstIterator mit = monthentries.constBegin();
  KDateTime endofmonth( end, QTime( 0, 0, 0 ) );
  endofmonth = endofmonth.addDays(1);
  for ( ; mit != monthentries.constEnd(); ++mit ) {
    if ( (*mit).start.date() == (*mit).end.date() ) {
      // Show also single-day events as time line boxes
      if ( subDailyFlags & TimeBoxes ) {
        timeboxItems.append(
          new PrintCellItem( (*mit).event, (*mit).start, (*mit).end ) );
      }
      // Show as text in the box
      if ( subDailyFlags & Text ) {
        textEvents[(*mit).start.date().day()] << (*mit).event->summary();
      }
    } else {
      // Multi-day events are always shown as time line boxes
      KDateTime thisstart( (*mit).start );
      KDateTime thisend( (*mit).end );
      if ( thisstart.date() < start ) {
        thisstart.setDate( start );
      }
      if ( thisend > endofmonth ) {
        thisend = endofmonth;
      }
      timeboxItems.append(
        new PrintCellItem( (*mit).event, thisstart, thisend ) );
    }
  }

  // For Multi-day events, line them up nicely so that the boxes don't overlap
  QListIterator<CellItem *> it1( timeboxItems );
  while ( it1.hasNext() ) {
    CellItem *placeItem = it1.next();
    CellItem::placeItem( timeboxItems, placeItem );
  }
  KDateTime starttime( start, QTime( 0, 0, 0 ) );
  int newxstartcont = xstartcont;

  QFont oldfont( p.font() );
  p.setFont( QFont( QLatin1String("sans-serif"), 7 ) );
  while ( it1.hasNext() ) {
    PrintCellItem *placeItem = static_cast<PrintCellItem *>( it1.next() );
    int minsToStart = starttime.secsTo( placeItem->start() ) / 60;
    int minsToEnd = starttime.secsTo( placeItem->end() ) / 60;

    QRect eventBox(
      xstartcont + placeItem->subCell() * 17,
      daysBox.top() + qRound(
        double( minsToStart * daysBox.height() ) / double( maxdays * 24 * 60 ) ), 14, 0 );
    eventBox.setBottom(
      daysBox.top() + qRound(
        double( minsToEnd * daysBox.height() ) / double( maxdays * 24 * 60 ) ) );
    drawVerticalBox( p, 0, eventBox, placeItem->event()->summary() );
    newxstartcont = qMax( newxstartcont, eventBox.right() );
  }
  xstartcont = newxstartcont;

  // For Single-day events, simply print their summaries into the remaining
  // space of the day's cell
  for ( int d=0; d<daysinmonth; ++d ) {
    QStringList dayEvents( textEvents[d+1] );
    QString txt = dayEvents.join( QLatin1String(", ") );
    QRect dayBox( xstartcont, daysBox.top() + qRound( dayheight * d ), 0, 0 );
    dayBox.setRight( box.right() );
    dayBox.setBottom( daysBox.top() + qRound( dayheight * ( d + 1 ) ) );
    printEventString( p, dayBox, txt, Qt::AlignTop | Qt::AlignLeft | Qt::TextWrapAnywhere );
  }
  p.setFont( oldfont );
  drawBox( p, BOX_BORDER_WIDTH, borderBox );
  p.restore();
}

void CalPrintPluginBase::drawMonthTable( QPainter &p, const QDate &qd,
                                         const QTime &fromTime, const QTime &toTime,
                                         bool weeknumbers, bool recurDaily,
                                         bool recurWeekly, bool singleLineLimit,
                                         bool showNoteLines,
                                         bool includeDescription,
                                         bool excludeConfidential,
                                         bool excludePrivate,
                                         const QRect &box )
{
  int yoffset = mSubHeaderHeight;
  int xoffset = 0;
  QDate monthDate( QDate( qd.year(), qd.month(), 1 ) );
  QDate monthFirst( monthDate );
  QDate monthLast( monthDate.addMonths(1).addDays(-1) );

  int weekdayCol = weekdayColumn( monthDate.dayOfWeek() );
  monthDate = monthDate.addDays(-weekdayCol);

  if (weeknumbers) {
    xoffset += 14;
  }

  int rows = ( weekdayCol + qd.daysInMonth() - 1 ) / 7 + 1;
  double cellHeight = ( box.height() - yoffset ) / ( 1. * rows );
  double cellWidth = ( box.width() - xoffset ) / 7.;

  // Precalculate the grid...
  // rows is at most 6, so using 8 entries in the array is fine, too!
  int coledges[8], rowedges[8];
  for ( int i = 0; i <= 7; ++i ) {
    rowedges[i] = int( box.top() + yoffset + i * cellHeight );
    coledges[i] = int( box.left() + xoffset + i * cellWidth );
  }

  if ( weeknumbers ) {
    QFont oldFont( p.font() );
    QFont newFont( p.font() );
    newFont.setPointSize( 6 );
    p.setFont( newFont );
    QDate weekDate( monthDate );
    for ( int row = 0; row<rows; ++row ) {
      int calWeek = weekDate.weekNumber();
      QRect rc( box.left(), rowedges[row],
                coledges[0] - 3 - box.left(), rowedges[row + 1] - rowedges[row] );
      p.drawText( rc, Qt::AlignRight | Qt::AlignVCenter, QString::number( calWeek ) );
      weekDate = weekDate.addDays( 7 );
    }
    p.setFont( oldFont );
  }

  QRect daysOfWeekBox( box );
  daysOfWeekBox.setHeight( mSubHeaderHeight );
  daysOfWeekBox.setLeft( box.left() + xoffset );
  drawDaysOfWeek( p, monthDate, monthDate.addDays( 6 ), daysOfWeekBox );

  QColor back = p.background().color();
  bool darkbg = false;
  for ( int row = 0; row < rows; ++row ) {
    for ( int col = 0; col < 7; ++col ) {
      // show days from previous/next month with a grayed background
      if ( ( monthDate < monthFirst ) || ( monthDate > monthLast ) ) {
        p.setBackground( back.darker( 120 ) );
        darkbg = true;
      }
      QRect dayBox( coledges[col], rowedges[row],
          coledges[col + 1] - coledges[col], rowedges[row + 1] - rowedges[row] );
      drawDayBox( p, monthDate, fromTime, toTime, dayBox, false,
                  recurDaily, recurWeekly, singleLineLimit, showNoteLines,
                  includeDescription, excludeConfidential, excludePrivate );
      if ( darkbg ) {
        p.setBackground( back );
        darkbg = false;
      }
      monthDate = monthDate.addDays(1);
    }
  }
}

void CalPrintPluginBase::drawTodoLines( QPainter &p,
                                        const QString &entry,
                                        int x, int &y, int width,
                                        int pageHeight, bool richTextEntry,
                                        QList<TodoParentStart *> &startPoints,
                                        bool connectSubTodos )
{
  QString plainEntry = ( richTextEntry ) ? toPlainText( entry ) : entry;

  QRect textrect( 0, 0, width, -1 );
  int flags = Qt::AlignLeft;
  QFontMetrics fm = p.fontMetrics();

  QStringList lines = plainEntry.split( QLatin1Char( '\n' ) );
  for ( int currentLine = 0; currentLine < lines.count(); currentLine++ ) {
    // split paragraphs into lines
    KWordWrap *ww = KWordWrap::formatText( fm, textrect, flags, lines[currentLine] );
    QStringList textLine = ww->wrappedString().split( QLatin1Char( '\n' ) );
    delete ww;

    // print each individual line
    for ( int lineCount = 0; lineCount < textLine.count(); lineCount++ ) {
      if ( y >= pageHeight ) {
        if ( connectSubTodos ) {
          for ( int i = 0; i < startPoints.size(); ++i ) {
            TodoParentStart *rct;
            rct = startPoints.at( i );
            int start = rct->mRect.bottom() + 1;
            int center = rct->mRect.left() + ( rct->mRect.width() / 2 );
            int to = y;
            if ( !rct->mSamePage ) {
              start = 0;
            }
            if ( rct->mHasLine ) {
              p.drawLine( center, start, center, to );
            }
            rct->mSamePage = false;
          }
        }
        y = 0;
        mPrinter->newPage();
      }
      y += fm.height();
      p.drawText( x, y, textLine[lineCount] );
    }
  }
}

void CalPrintPluginBase::drawTodo( int &count, const KCalCore::Todo::Ptr &todo, QPainter &p,
                                   KCalCore::TodoSortField sortField,
                                   KCalCore::SortDirection sortDir,
                                   bool connectSubTodos, bool strikeoutCompleted,
                                   bool desc, int posPriority, int posSummary,
                                   int posDueDt, int posPercentComplete,
                                   int level, int x, int &y, int width,
                                   int pageHeight, const KCalCore::Todo::List &todoList,
                                   TodoParentStart *r, bool excludeConfidential,
                                   bool excludePrivate )
{
  QString outStr;
  const KLocale *local = KGlobal::locale();
  QRect rect;
  TodoParentStart startpt;
  // This list keeps all starting points of the parent to-dos so the connection
  // lines of the tree can easily be drawn (needed if a new page is started)
  static QList<TodoParentStart *> startPoints;
  if ( level < 1 ) {
    startPoints.clear();
  }

  y += 10;

  // Compute the right hand side of the to-do box
  int rhs = posPercentComplete;
  if ( rhs < 0 ) {
    rhs = posDueDt; //not printing percent completed
  }
  if ( rhs < 0 ) {
    rhs = x + width;  //not printing due dates either
  }

  int left = posSummary + ( level * 10 );

  // If this is a sub-to-do, r will not be 0, and we want the LH side
  // of the priority line up to the RH side of the parent to-do's priority
  bool showPriority = posPriority >= 0;
  int lhs = posPriority;
  if ( r ) {
    lhs = r->mRect.right() + 1;
  }

  outStr.setNum( todo->priority() );
  rect = p.boundingRect( lhs, y + 10, 5, -1, Qt::AlignCenter, outStr );
  // Make it a more reasonable size
  rect.setWidth( 18 );
  rect.setHeight( 18 );

  // Draw a checkbox
  p.setBrush( QBrush( Qt::NoBrush ) );
  p.drawRect( rect );
  if ( todo->isCompleted() ) {
    // cross out the rectangle for completed to-dos
    p.drawLine( rect.topLeft(), rect.bottomRight() );
    p.drawLine( rect.topRight(), rect.bottomLeft() );
  }
  lhs = rect.right() + 3;

  // Priority
  if ( todo->priority() > 0 && showPriority ) {
    p.drawText( rect, Qt::AlignCenter, outStr );
  }
  startpt.mRect = rect; //save for later

  // Connect the dots
  if ( r && level > 0 && connectSubTodos ) {
    int bottom;
    int center( r->mRect.left() + ( r->mRect.width() / 2 ) );
    int to( rect.top() + ( rect.height() / 2 ) );
    int endx( rect.left() );
    p.drawLine( center, to, endx, to );  // side connector
    if ( r->mSamePage ) {
      bottom = r->mRect.bottom() + 1;
    } else {
      bottom = 0;
    }
    p.drawLine( center, bottom, center, to );
  }

  // summary
  outStr = todo->summary();
  rect = p.boundingRect( lhs, rect.top(), ( rhs - ( left + rect.width() + 5 ) ),
                         -1, Qt::TextWordWrap, outStr );

  QRect newrect;
  QFont newFont( p.font() );
  QFont oldFont( p.font() );
  if ( todo->isCompleted() && strikeoutCompleted ) {
    newFont.setStrikeOut( true );
    p.setFont( newFont );
  }
  p.drawText( rect, Qt::TextWordWrap, outStr, &newrect );
  p.setFont( oldFont );
  // due date
  if ( todo->hasDueDate() && posDueDt >= 0 ) {
    outStr = local->formatDate( todo->dtDue().toLocalZone().date(), KLocale::ShortDate );
    rect = p.boundingRect( posDueDt, y, x + width, -1,
                           Qt::AlignTop | Qt::AlignLeft, outStr );
    p.drawText( rect, Qt::AlignTop | Qt::AlignLeft, outStr );
  }

  // percentage completed
  bool showPercentComplete = posPercentComplete >= 0;
  if ( showPercentComplete ) {
    int lwidth = 24;
    int lheight = 12;
    //first, draw the progress bar
    int progress = (int)( ( lwidth * todo->percentComplete() ) / 100.0 + 0.5 );

    p.setBrush( QBrush( Qt::NoBrush ) );
    p.drawRect( posPercentComplete, y+3, lwidth, lheight );
    if ( progress > 0 ) {
      p.setBrush( QColor( 128, 128, 128 ) );
      p.drawRect( posPercentComplete, y+3, progress, lheight );
    }

    //now, write the percentage
    outStr = i18n( "%1%", todo->percentComplete() );
    rect = p.boundingRect( posPercentComplete+lwidth+3, y, x + width, -1,
                           Qt::AlignTop | Qt::AlignLeft, outStr );
    p.drawText( rect, Qt::AlignTop | Qt::AlignLeft, outStr );
  }

  y += 10;

  // Make a list of all the sub-to-dos related to this to-do.
  KCalCore::Todo::List t;
  KCalCore::Incidence::List relations = mCalendar->childIncidences( todo->uid() );

  foreach ( const KCalCore::Incidence::Ptr &incidence, relations ) {
    // In the future, to-dos might also be related to events
    // Manually check if the sub-to-do is in the list of to-dos to print
    // The problem is that relations() does not apply filters, so
    // we need to compare manually with the complete filtered list!
    KCalCore::Todo::Ptr subtodo = incidence.dynamicCast<KCalCore::Todo>();
    if ( !subtodo ) continue;
#ifdef AKONADI_PORT_DISABLED
    if ( subtodo && todoList.contains( subtodo ) ) {
#else
    bool subtodoOk = false;
    if ( subtodo ) {
      foreach ( const KCalCore::Todo::Ptr &tt, todoList ) {
        if ( tt == subtodo ) {
          subtodoOk = true;
          break;
        }
      }
    }
    if ( subtodoOk ) {
#endif
      if ( ( excludeConfidential &&
          subtodo->secrecy() == KCalCore::Incidence::SecrecyConfidential ) ||
          ( excludePrivate      &&
          subtodo->secrecy() == KCalCore::Incidence::SecrecyPrivate ) ) {
        continue;
      }
      t.append( subtodo );
    }
  }

  // has sub-todos?
  startpt.mHasLine = ( relations.size() > 0 );
  startPoints.append( &startpt );

  // description
  if ( !todo->description().isEmpty() && desc ) {
    y = newrect.bottom() + 5;
    drawTodoLines( p, todo->description(), left, y,
                   width - ( left + 10 - x ), pageHeight,
                   todo->descriptionIsRich(),
                   startPoints, connectSubTodos );
  } else {
    y += 10;
  }

  // Sort the sub-to-dos and print them
#ifdef AKONADI_PORT_DISABLED
  KCalCore::Todo::List sl = mCalendar->sortTodos( &t, sortField, sortDir );
#else
  KCalCore::Todo::List tl;
  foreach ( const KCalCore::Todo::Ptr &todo, t ) {
    tl.append( todo );
  }
  KCalCore::Todo::List sl = mCalendar->sortTodos( tl, sortField, sortDir );
#endif

  int subcount = 0;
  foreach ( const KCalCore::Todo::Ptr &isl, sl ) {
    count++;
    if ( ++subcount == sl.size() ) {
      startpt.mHasLine = false;
    }
    drawTodo( count, isl, p, sortField, sortDir,
              connectSubTodos, strikeoutCompleted,
              desc, posPriority, posSummary, posDueDt, posPercentComplete,
              level+1, x, y, width, pageHeight, todoList, &startpt,
              excludeConfidential, excludePrivate );
  }
  startPoints.removeAll( &startpt );
}

int CalPrintPluginBase::weekdayColumn( int weekday )
{
  int w = weekday + 7 - KGlobal::locale()->weekStartDay();
  return w % 7;
}

void CalPrintPluginBase::drawTextLines( QPainter &p, const QString &entry,
                                        int x, int &y, int width,
                                        int pageHeight, bool richTextEntry )
{
  QString plainEntry = ( richTextEntry ) ? toPlainText( entry ) : entry;

  QRect textrect( 0, 0, width, -1 );
  int flags = Qt::AlignLeft;
  QFontMetrics fm = p.fontMetrics();

  QStringList lines = plainEntry.split( QLatin1Char( '\n' ) );
  for ( int currentLine = 0; currentLine < lines.count(); currentLine++ ) {
    // split paragraphs into lines
    KWordWrap *ww = KWordWrap::formatText( fm, textrect, flags, lines[currentLine] );
    QStringList textLine = ww->wrappedString().split( QLatin1Char( '\n' ) );
    delete ww;
    // print each individual line
    for ( int lineCount = 0; lineCount < textLine.count(); lineCount++ ) {
      if ( y >= pageHeight ) {
        y = 0;
        mPrinter->newPage();
      }
      y += fm.height();
      p.drawText( x, y, textLine[lineCount] );
    }
  }
}

void CalPrintPluginBase::drawJournal( const KCalCore::Journal::Ptr &journal, QPainter &p,
                                      int x, int &y, int width, int pageHeight )
{
  QFont oldFont( p.font() );
  p.setFont( QFont( QLatin1String("sans-serif"), 15 ) );
  QString headerText;
  QString dateText( KGlobal::locale()->formatDate( journal->dtStart().toLocalZone().date(),
                                                   KLocale::LongDate ) );

  if ( journal->summary().isEmpty() ) {
    headerText = dateText;
  } else {
    headerText = i18nc( "Description - date", "%1 - %2", journal->summary(), dateText );
  }

  QRect rect( p.boundingRect( x, y, width, -1, Qt::TextWordWrap, headerText ) );
  if ( rect.bottom() > pageHeight ) {
    // Start new page...
    y = 0;
    mPrinter->newPage();
    rect = p.boundingRect( x, y, width, -1, Qt::TextWordWrap, headerText );
  }
  QRect newrect;
  p.drawText( rect, Qt::TextWordWrap, headerText, &newrect );
  p.setFont( oldFont );

  y = newrect.bottom() + 4;

  p.drawLine( x + 3, y, x + width - 6, y );
  y += 5;
  if ( !( journal->organizer()->fullName().isEmpty() ) ) {
    drawTextLines( p, i18n( "Person: %1", journal->organizer()->fullName() ),
                      x, y, width, pageHeight, false );
    y += 7;
  }
  if ( !( journal->description().isEmpty() ) ) {
    drawTextLines( p, journal->description(), x, y, width, pageHeight,
                      journal->descriptionIsRich() );
    y += 7;
  }
  y += 10;
}

void CalPrintPluginBase::drawSplitHeaderRight( QPainter &p, const QDate &fd,
                                               const QDate &td, const QDate &,
                                               int width, int height )
{
  QFont oldFont( p.font() );

  QPen oldPen( p.pen() );
  QPen pen( Qt::black, 4 );

  QString title;
  if ( mCalSys ) {
    if ( fd.month() == td.month() ) {
      title = i18nc( "Date range: Month dayStart - dayEnd", "%1 %2 - %3",
                     mCalSys->monthName( fd.month(), KCalendarSystem::LongName ),
                     mCalSys->formatDate( fd, KLocale::Day, KLocale::LongNumber ),
                     mCalSys->formatDate( td, KLocale::Day, KLocale::LongNumber ) );
    } else {
      title = i18nc( "Date range: monthStart dayStart - monthEnd dayEnd", "%1 %2 - %3 %4",
                     mCalSys->monthName( fd.month(), KCalendarSystem::LongName ),
                     mCalSys->formatDate( fd, KLocale::Day, KLocale::LongNumber ),
                     mCalSys->monthName( td.month(), KCalendarSystem::LongName ),
                     mCalSys->formatDate( td, KLocale::Day, KLocale::LongNumber ) );
    }
  }

  if ( height < 60 ) {
    p.setFont( QFont( QLatin1String("Times"), 22 ) );
  } else {
    p.setFont( QFont( QLatin1String("Times"), 28 ) );
  }

  int lineSpacing = p.fontMetrics().lineSpacing();
  p.drawText( 0, 0, width, lineSpacing,
              Qt::AlignRight | Qt::AlignTop, title );

  title.truncate(0);

  p.setPen( pen );
  p.drawLine( 300, lineSpacing, width, lineSpacing );
  p.setPen( oldPen );

  if ( height < 60 ) {
    p.setFont( QFont( QLatin1String("Times"), 14, QFont::Bold, true ) );
  } else {
    p.setFont( QFont( QLatin1String("Times"), 18, QFont::Bold, true ) );
  }

  title += QString::number( fd.year() );
  p.drawText( 0, lineSpacing, width, lineSpacing,
              Qt::AlignRight | Qt::AlignTop, title );

  p.setFont( oldFont );
}

void CalPrintPluginBase::drawNoteLines( QPainter &p, const QRect &box, int startY )
{
  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 );
}

QString CalPrintPluginBase::toPlainText( const QString &htmlText )
{
  // this converts possible rich text to plain text
  return QTextDocumentFragment::fromHtml( htmlText ).toPlainText();
}
