<commit_before>/*
  This file is part of KOrganizer.

  Copyright (c) 2000,2001,2003 <NAME> <<EMAIL>>
  Copyright (C) 2003-2004 <NAME> <<EMAIL>>

  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 "koagendaitem.h"
#include "koprefs.h"
#include "koglobals.h"

#include <libkdepim/kvcarddrag.h>

#include <kcal/icaldrag.h>
#include <kcal/incidence.h>
#include <kcal/todo.h>
#include <kcal/incidenceformatter.h>
#include <kcal/vcaldrag.h>

#include <kpimutils/email.h>

#ifndef KORG_NOKABC
#include <kabc/addressee.h>
#include <kabc/vcardconverter.h>
#endif

#include <kiconloader.h>
#include <kdebug.h>
#include <klocale.h>
#include <kwordwrap.h>
#include <kmessagebox.h>

#include <QPainter>
#include <QPixmap>
#include <QPaintEvent>
#include <QList>
#include <QDropEvent>
#include <QDragEnterEvent>
#include <QPixmapCache>
#include <QToolTip>

#include "koagendaitem.moc"

//-----------------------------------------------------------------------------

QPixmap *KOAgendaItem::alarmPxmp = 0;
QPixmap *KOAgendaItem::recurPxmp = 0;
QPixmap *KOAgendaItem::readonlyPxmp = 0;
QPixmap *KOAgendaItem::replyPxmp = 0;
QPixmap *KOAgendaItem::groupPxmp = 0;
QPixmap *KOAgendaItem::groupPxmpTent = 0;
QPixmap *KOAgendaItem::organizerPxmp = 0;
QPixmap *KOAgendaItem::eventPxmp = 0;
QPixmap *KOAgendaItem::todoPxmp = 0;
QPixmap *KOAgendaItem::journalPxmp = 0;
QPixmap *KOAgendaItem::completedPxmp = 0;

//-----------------------------------------------------------------------------

KOAgendaItem::KOAgendaItem( Incidence *incidence, const QDate &qd, QWidget *parent )
  : QWidget( parent ), mIncidence( incidence ), mDate( qd ),
    mLabelText( mIncidence->summary() ), mIconAlarm( false ),
    mIconRecur( false ), mIconReadonly( false ), mIconReply( false ),
    mIconGroup( false ), mIconGroupTent( false ), mIconOrganizer( false ),
    mMultiItemInfo( 0 ), mStartMoveInfo( 0 )
{
  kDebug() << "Incidence Type is " << mIncidence->type();
  QPalette pal = palette();
  pal.setColor( QPalette::Window, Qt::transparent );
  setPalette( pal );

  setCellXY( 0, 0, 1 );
  setCellXRight( 0 );
  setMouseTracking( true );
  mResourceColor = QColor();
  updateIcons();

  // select() does nothing if the state hasn't changed, so preset mSelected.
  mSelected = true;
  select( false );

  setAcceptDrops( true );
}

void KOAgendaItem::updateIcons()
{
  if ( !mIncidence ) {
    return;
  }
  mIconReadonly = mIncidence->isReadOnly();
  mIconRecur = mIncidence->recurs();
  mIconAlarm = mIncidence->isAlarmEnabled();
  if ( mIncidence->attendeeCount() > 0 ) {
    if ( KOPrefs::instance()->thatIsMe( mIncidence->organizer().email() ) ) {
      mIconReply = false;
      mIconGroup = false;
      mIconGroupTent = false;
      mIconOrganizer = true;
    } else {
      Attendee *me = mIncidence->attendeeByMails( KOPrefs::instance()->allEmails() );
      if ( me ) {
        if ( me->status() == Attendee::NeedsAction && me->RSVP() ) {
          mIconReply = true;
          mIconGroup = false;
          mIconGroupTent = false;
          mIconOrganizer = false;
        } else if ( me->status() == Attendee::Tentative ) {
          mIconReply = false;
          mIconGroup = false;
          mIconGroupTent = true;
          mIconOrganizer = false;
        } else {
          mIconReply = false;
          mIconGroup = true;
          mIconGroupTent = false;
          mIconOrganizer = false;
        }
      } else {
        mIconReply = false;
        mIconGroup = true;
        mIconGroupTent = false;
        mIconOrganizer = false;
      }
    }
  }
  update();
}

void KOAgendaItem::select( bool selected )
{
  if ( mSelected == selected ) {
    return;
  }
  mSelected = selected;

  update();
}

bool KOAgendaItem::dissociateFromMultiItem()
{
  if ( !isMultiItem() ) {
    return false;
  }

  KOAgendaItem *firstItem = firstMultiItem();
  if ( firstItem == this ) {
    firstItem = nextMultiItem();
  }

  KOAgendaItem *lastItem = lastMultiItem();
  if ( lastItem == this ) {
    lastItem = prevMultiItem();
  }

  KOAgendaItem *prevItem = prevMultiItem();
  KOAgendaItem *nextItem = nextMultiItem();

  if ( prevItem ) {
    prevItem->setMultiItem( firstItem, prevItem->prevMultiItem(), nextItem, lastItem );
  }
  if ( nextItem ) {
    nextItem->setMultiItem( firstItem, prevItem, nextItem->prevMultiItem(), lastItem );
  }
  delete mMultiItemInfo;
  mMultiItemInfo = 0;
  return true;
}

bool KOAgendaItem::setIncidence( Incidence *i )
{
  mIncidence = i;
  updateIcons();
  return true;
}

/*
  Return height of item in units of agenda cells
*/
int KOAgendaItem::cellHeight() const
{
  return mCellYBottom - mCellYTop + 1;
}

/*
  Return height of item in units of agenda cells
*/
int KOAgendaItem::cellWidth() const
{
  return mCellXRight - mCellXLeft + 1;
}

void KOAgendaItem::setItemDate( const QDate &qd )
{
  mDate = qd;
}

void KOAgendaItem::setCellXY( int X, int YTop, int YBottom )
{
  mCellXLeft = X;
  mCellYTop = YTop;
  mCellYBottom = YBottom;
}

void KOAgendaItem::setCellXRight( int XRight )
{
  mCellXRight = XRight;
}

void KOAgendaItem::setCellX( int XLeft, int XRight )
{
  mCellXLeft = XLeft;
  mCellXRight = XRight;
}

void KOAgendaItem::setCellY( int YTop, int YBottom )
{
  mCellYTop = YTop;
  mCellYBottom = YBottom;
}

void KOAgendaItem::setMultiItem( KOAgendaItem *first, KOAgendaItem *prev,
                                 KOAgendaItem *next, KOAgendaItem *last )
{
  if ( !mMultiItemInfo ) {
    mMultiItemInfo = new MultiItemInfo;
  }
  mMultiItemInfo->mFirstMultiItem = first;
  mMultiItemInfo->mPrevMultiItem = prev;
  mMultiItemInfo->mNextMultiItem = next;
  mMultiItemInfo->mLastMultiItem = last;
}

bool KOAgendaItem::isMultiItem()
{
  return mMultiItemInfo;
}

KOAgendaItem *KOAgendaItem::prependMoveItem( KOAgendaItem *e )
{
  if ( !e ) {
    return e;
  }

  KOAgendaItem *first = 0, *last = 0;
  if ( isMultiItem() ) {
    first = mMultiItemInfo->mFirstMultiItem;
    last = mMultiItemInfo->mLastMultiItem;
  }
  if ( !first ) {
    first = this;
  }
  if ( !last ) {
    last = this;
  }

  e->setMultiItem( 0, 0, first, last );
  first->setMultiItem( e, e, first->nextMultiItem(), first->lastMultiItem() );

  KOAgendaItem *tmp = first->nextMultiItem();
  while ( tmp ) {
    tmp->setMultiItem( e, tmp->prevMultiItem(), tmp->nextMultiItem(), tmp->lastMultiItem() );
    tmp = tmp->nextMultiItem();
  }

  if ( mStartMoveInfo && !e->moveInfo() ) {
    e->mStartMoveInfo=new MultiItemInfo( *mStartMoveInfo );
//    e->moveInfo()->mFirstMultiItem = moveInfo()->mFirstMultiItem;
//    e->moveInfo()->mLastMultiItem = moveInfo()->mLastMultiItem;
    e->moveInfo()->mPrevMultiItem = 0;
    e->moveInfo()->mNextMultiItem = first;
  }

  if ( first && first->moveInfo() ) {
    first->moveInfo()->mPrevMultiItem = e;
  }
  return e;
}

KOAgendaItem *KOAgendaItem::appendMoveItem( KOAgendaItem *e )
{
  if ( !e ) {
    return e;
  }

  KOAgendaItem *first = 0, *last = 0;
  if ( isMultiItem() ) {
    first = mMultiItemInfo->mFirstMultiItem;
    last = mMultiItemInfo->mLastMultiItem;
  }
  if ( !first ) {
    first = this;
  }
  if ( !last ) {
    last = this;
  }

  e->setMultiItem( first, last, 0, 0 );
  KOAgendaItem *tmp = first;

  while ( tmp ) {
    tmp->setMultiItem( tmp->firstMultiItem(), tmp->prevMultiItem(), tmp->nextMultiItem(), e );
    tmp = tmp->nextMultiItem();
  }
  last->setMultiItem( last->firstMultiItem(), last->prevMultiItem(), e, e );

  if ( mStartMoveInfo && !e->moveInfo() ) {
    e->mStartMoveInfo=new MultiItemInfo( *mStartMoveInfo );
//    e->moveInfo()->mFirstMultiItem = moveInfo()->mFirstMultiItem;
//    e->moveInfo()->mLastMultiItem = moveInfo()->mLastMultiItem;
    e->moveInfo()->mPrevMultiItem = last;
    e->moveInfo()->mNextMultiItem = 0;
  }
  if ( last && last->moveInfo() ) {
    last->moveInfo()->mNextMultiItem = e;
  }
  return e;
}

KOAgendaItem *KOAgendaItem::removeMoveItem( KOAgendaItem *e )
{
  if ( isMultiItem() ) {
    KOAgendaItem *first = mMultiItemInfo->mFirstMultiItem;
    KOAgendaItem *next, *prev;
    KOAgendaItem *last = mMultiItemInfo->mLastMultiItem;
    if ( !first ) {
      first = this;
    }
    if ( !last ) {
      last = this;
    }
    if ( first == e ) {
      first = first->nextMultiItem();
      first->setMultiItem( 0, 0, first->nextMultiItem(), first->lastMultiItem() );
    }
    if ( last == e ) {
      last = last->prevMultiItem();
      last->setMultiItem( last->firstMultiItem(), last->prevMultiItem(), 0, 0 );
    }

    KOAgendaItem *tmp =  first;
    if ( first == last ) {
      delete mMultiItemInfo;
      tmp = 0;
      mMultiItemInfo = 0;
    }
    while ( tmp ) {
      next = tmp->nextMultiItem();
      prev = tmp->prevMultiItem();
      if ( e == next ) {
        next = next->nextMultiItem();
      }
      if ( e == prev ) {
        prev = prev->prevMultiItem();
      }
      tmp->setMultiItem( ( tmp == first ) ? 0 : first,
                         ( tmp == prev ) ? 0 : prev,
                         ( tmp == next ) ? 0 : next,
                         ( tmp == last ) ? 0 : last );
      tmp = tmp->nextMultiItem();
    }
  }

  return e;
}

void KOAgendaItem::startMove()
{
  KOAgendaItem *first = this;
  if ( isMultiItem() && mMultiItemInfo->mFirstMultiItem ) {
    first=mMultiItemInfo->mFirstMultiItem;
  }
  first->startMovePrivate();
}

void KOAgendaItem::startMovePrivate()
{
  mStartMoveInfo = new MultiItemInfo;
  mStartMoveInfo->mStartCellXLeft = mCellXLeft;
  mStartMoveInfo->mStartCellXRight = mCellXRight;
  mStartMoveInfo->mStartCellYTop = mCellYTop;
  mStartMoveInfo->mStartCellYBottom = mCellYBottom;
  if ( mMultiItemInfo ) {
    mStartMoveInfo->mFirstMultiItem = mMultiItemInfo->mFirstMultiItem;
    mStartMoveInfo->mLastMultiItem = mMultiItemInfo->mLastMultiItem;
    mStartMoveInfo->mPrevMultiItem = mMultiItemInfo->mPrevMultiItem;
    mStartMoveInfo->mNextMultiItem = mMultiItemInfo->mNextMultiItem;
  } else {
    mStartMoveInfo->mFirstMultiItem = 0;
    mStartMoveInfo->mLastMultiItem = 0;
    mStartMoveInfo->mPrevMultiItem = 0;
    mStartMoveInfo->mNextMultiItem = 0;
  }
  if ( isMultiItem() && mMultiItemInfo->mNextMultiItem ) {
    mMultiItemInfo->mNextMultiItem->startMovePrivate();
  }
}

void KOAgendaItem::resetMove()
{
  if ( mStartMoveInfo ) {
    if ( mStartMoveInfo->mFirstMultiItem ) {
      mStartMoveInfo->mFirstMultiItem->resetMovePrivate();
    } else {
      resetMovePrivate();
    }
  }
}

void KOAgendaItem::resetMovePrivate()
{
  if ( mStartMoveInfo ) {
    mCellXLeft = mStartMoveInfo->mStartCellXLeft;
    mCellXRight = mStartMoveInfo->mStartCellXRight;
    mCellYTop = mStartMoveInfo->mStartCellYTop;
    mCellYBottom = mStartMoveInfo->mStartCellYBottom;

    // if we don't have mMultiItemInfo, the item didn't span two days before,
    // and wasn't moved over midnight, either, so we don't have to reset
    // anything. Otherwise, restore from mMoveItemInfo
    if ( mMultiItemInfo ) {
      // It was already a multi-day info
      mMultiItemInfo->mFirstMultiItem = mStartMoveInfo->mFirstMultiItem;
      mMultiItemInfo->mPrevMultiItem = mStartMoveInfo->mPrevMultiItem;
      mMultiItemInfo->mNextMultiItem = mStartMoveInfo->mNextMultiItem;
      mMultiItemInfo->mLastMultiItem = mStartMoveInfo->mLastMultiItem;

      if ( !mStartMoveInfo->mFirstMultiItem ) {
        // This was the first multi-item when the move started, delete all previous
        KOAgendaItem *toDel = mStartMoveInfo->mPrevMultiItem;
        KOAgendaItem *nowDel = 0;
        while ( toDel ) {
          nowDel = toDel;
          if ( nowDel->moveInfo() ) {
            toDel = nowDel->moveInfo()->mPrevMultiItem;
          }
          emit removeAgendaItem( nowDel );
        }
        mMultiItemInfo->mFirstMultiItem = 0;
        mMultiItemInfo->mPrevMultiItem = 0;
      }
      if ( !mStartMoveInfo->mLastMultiItem ) {
        // This was the last multi-item when the move started, delete all next
        KOAgendaItem *toDel = mStartMoveInfo->mNextMultiItem;
        KOAgendaItem *nowDel = 0;
        while ( toDel ) {
          nowDel = toDel;
          if ( nowDel->moveInfo() ) {
            toDel=nowDel->moveInfo()->mNextMultiItem;
          }
          emit removeAgendaItem( nowDel );
        }
        mMultiItemInfo->mLastMultiItem = 0;
        mMultiItemInfo->mNextMultiItem = 0;
      }

      if ( mStartMoveInfo->mFirstMultiItem == 0 && mStartMoveInfo->mLastMultiItem == 0 ) {
        // it was a single-day event before we started the move.
        delete mMultiItemInfo;
        mMultiItemInfo = 0;
      }
    }
    delete mStartMoveInfo;
    mStartMoveInfo = 0;
  }
  emit showAgendaItem( this );
  if ( nextMultiItem() ) {
    nextMultiItem()->resetMovePrivate();
  }
}

void KOAgendaItem::endMove()
{
  KOAgendaItem *first = firstMultiItem();
  if ( !first ) {
    first = this;
  }
  first->endMovePrivate();
}

void KOAgendaItem::endMovePrivate()
{
  if ( mStartMoveInfo ) {
    // if first, delete all previous
    if ( !firstMultiItem() || firstMultiItem() == this ) {
      KOAgendaItem *toDel = mStartMoveInfo->mPrevMultiItem;
      KOAgendaItem *nowDel = 0;
      while ( toDel ) {
        nowDel = toDel;
        if ( nowDel->moveInfo() ) {
          toDel=nowDel->moveInfo()->mPrevMultiItem;
        }
        emit removeAgendaItem( nowDel );
      }
    }
    // if last, delete all next
    if ( !lastMultiItem() || lastMultiItem() == this ) {
      KOAgendaItem *toDel=mStartMoveInfo->mNextMultiItem;
      KOAgendaItem *nowDel = 0;
      while ( toDel ) {
        nowDel = toDel;
        if ( nowDel->moveInfo() ) {
          toDel=nowDel->moveInfo()->mNextMultiItem;
        }
        emit removeAgendaItem( nowDel );
      }
    }
    // also delete the moving info
    delete mStartMoveInfo;
    mStartMoveInfo = 0;
    if ( nextMultiItem() ) {
      nextMultiItem()->endMovePrivate();
    }
  }
}

void KOAgendaItem::moveRelative( int dx, int dy )
{
  int newXLeft = cellXLeft() + dx;
  int newXRight = cellXRight() + dx;
  int newYTop = cellYTop() + dy;
  int newYBottom = cellYBottom() + dy;
  setCellXY( newXLeft, newYTop, newYBottom );
  setCellXRight( newXRight );
}

void KOAgendaItem::expandTop( int dy )
{
  int newYTop = cellYTop() + dy;
  int newYBottom = cellYBottom();
  if ( newYTop > newYBottom ) {
    newYTop = newYBottom;
  }
  setCellY( newYTop, newYBottom );
}

void KOAgendaItem::expandBottom( int dy )
{
  int newYTop = cellYTop();
  int newYBottom = cellYBottom() + dy;
  if ( newYBottom < newYTop ) {
    newYBottom = newYTop;
  }
  setCellY( newYTop, newYBottom );
}

void KOAgendaItem::expandLeft( int dx )
{
  int newXLeft = cellXLeft() + dx;
  int newXRight = cellXRight();
  if ( newXLeft > newXRight ) {
    newXLeft = newXRight;
  }
  setCellX( newXLeft, newXRight );
}

void KOAgendaItem::expandRight( int dx )
{
  int newXLeft = cellXLeft();
  int newXRight = cellXRight() + dx;
  if ( newXRight < newXLeft ) {
    newXRight = newXLeft;
  }
  setCellX( newXLeft, newXRight );
}

void KOAgendaItem::dragEnterEvent( QDragEnterEvent *e )
{
#ifndef KORG_NODND
  const QMimeData *md = e->mimeData();
  if ( ICalDrag::canDecode( md ) || VCalDrag::canDecode( md ) ) {
    // TODO: Allow dragging events/todos onto other events to create a relation
    e->ignore();
    return;
  }
  if ( KPIM::KVCardDrag::canDecode( md ) || md->hasText() ) {
    e->accept();
  } else {
    e->ignore();
  }
#endif
}

void KOAgendaItem::addAttendee( const QString &newAttendee )
{
  QString name, email;
  KPIMUtils::extractEmailAddressAndName( newAttendee, email, name );
  if ( !( name.isEmpty() && email.isEmpty() ) ) {
    mIncidence->addAttendee( new Attendee( name, email ) );
    KMessageBox::information(
      this,
      i18n( "Attendee \"%1\" added to the calendar item \"%2\"",
            KPIMUtils::normalizedAddress( name, email, QString() ), text() ),
      i18n( "Attendee added" ), "AttendeeDroppedAdded" );
  }
}

void KOAgendaItem::dropEvent( QDropEvent *e )
{
  // TODO: Organize this better: First check for attachment
  // (not only file, also any other url!), then if it's a vcard,
  // otherwise check for attendees, then if the data is binary,
  // add a binary attachment.
#ifndef KORG_NODND
  const QMimeData *md = e->mimeData();

  bool decoded = md->hasText();
  QString text = md->text();
  if ( decoded && text.startsWith( "file:" ) ) {
    mIncidence->addAttachment( new Attachment( text ) );
    return;
  }

#ifndef KORG_NOKABC
  KABC::Addressee::List list;

  if ( KPIM::KVCardDrag::fromMimeData( md, list ) ) {
    KABC::Addressee::List::Iterator it;
    for ( it = list.begin(); it != list.end(); ++it ) {
      QString em( (*it).fullEmail() );
      if ( em.isEmpty() ) {
        em = (*it).realName();
      }
      addAttendee( em );
    }
  }
#else
  if( decoded ) {
    QStringList emails = text.split( ",", QString::SkipEmptyParts );
    for ( QStringList::ConstIterator it = emails.begin(); it != emails.end(); ++it ) {
      addAttendee( *it );
    }
  }
#endif // KORG_NOKABC

#endif // KORG_NODND
}

QList<KOAgendaItem*> &KOAgendaItem::conflictItems()
{
  return mConflictItems;
}

void KOAgendaItem::setConflictItems( QList<KOAgendaItem*> ci )
{
  mConflictItems = ci;
  QList<KOAgendaItem*>::iterator it;
  for ( it = mConflictItems.begin(); it != mConflictItems.end(); ++it ) {
    (*it)->addConflictItem( this );
  }
}

void KOAgendaItem::addConflictItem( KOAgendaItem *ci )
{
  if ( mConflictItems.contains( ci ) ) {
    mConflictItems.append( ci );
  }
}

QString KOAgendaItem::label() const
{
  return mLabelText;
}

bool KOAgendaItem::overlaps( KOrg::CellItem *o ) const
{
  KOAgendaItem *other = static_cast<KOAgendaItem *>( o );

  if ( cellXLeft() <= other->cellXRight() && cellXRight() >= other->cellXLeft() ) {
    if ( ( cellYTop() <= other->cellYBottom() ) && ( cellYBottom() >= other->cellYTop() ) ) {
      return true;
    }
  }

  return false;
}

static void conditionalPaint( QPainter *p, bool condition, int &x, int y,
                              int ft, const QPixmap &pxmp )
{
  if ( !condition ) {
    return;
  }

  p->drawPixmap( x, y, pxmp );
  x += pxmp.width() + ft;
}

void KOAgendaItem::paintEventIcon( QPainter *p, int &x, int y, int ft )
{
  if ( !mIncidence ) {
    return;
  }
  conditionalPaint( p, mIncidence->type() == "Event", x, y, ft, *eventPxmp );
}

void KOAgendaItem::paintTodoIcon( QPainter *p, int &x, int y, int ft )
{
  if ( !mIncidence || mIncidence->type() != "Todo" ) {
    return;
  }

  bool b = ( static_cast<Todo *>( mIncidence ) )->isCompleted();
  conditionalPaint( p, !b, x, y, ft, *todoPxmp );
  conditionalPaint( p, b, x, y, ft, *completedPxmp );
}

void KOAgendaItem::paintJournalIcon( QPainter *p, int &x, int y, int ft )
{
  if ( !mIncidence ) {
    return;
  }
  conditionalPaint( p, mIncidence->type() == "Journal", x, y, ft, *journalPxmp );
}

void KOAgendaItem::paintIcons( QPainter *p, int &x, int y, int ft )
{
  paintEventIcon( p, x, y, ft );
  paintTodoIcon( p, x, y, ft );
  paintJournalIcon( p, x, y, ft );
  conditionalPaint( p, mIconAlarm, x, y, ft, *alarmPxmp );
  conditionalPaint( p, mIconRecur, x, y, ft, *recurPxmp );
  conditionalPaint( p, mIconReadonly, x, y, ft, *readonlyPxmp );
  conditionalPaint( p, mIconReply, x, y, ft, *replyPxmp );
  conditionalPaint( p, mIconGroup, x, y, ft, *groupPxmp );
  conditionalPaint( p, mIconGroupTent, x, y, ft, *groupPxmpTent );
  conditionalPaint( p, mIconOrganizer, x, y, ft, *organizerPxmp );
}

void KOAgendaItem::paintEvent( QPaintEvent *ev )
{
  if ( !mIncidence ) {
    return;
  }

  QRect visRect = visibleRegion().boundingRect();
  // when scrolling horizontally in the side-by-side view, the repainted area is clipped
  // to the newly visible area, which is a problem since the content changes when visRect
  // changes, so repaint the full item in that case
  if ( ev->rect() != visRect && visRect.isValid() && ev->rect().isValid() ) {
    repaint( visRect );
    return;
  }

  QPainter p( this );
  p.setRenderHint( QPainter::Antialiasing );
  const int fmargin = 0; // frame margin
  const int ft = 1; // frame thickness for layout, see drawRoundedRect(),
                    // keep multiple of 2
  const int margin = 5 + ft + fmargin ; // frame + space between frame and content

  // General idea is to always show the icons (even in the all-day events).
  // This creates a consistent feeling for the user when the view mode<commit_msg>Backport SVN commit 862635 by thiago from trunk:<commit_after>/*
  This file is part of KOrganizer.

  Copyright (c) 2000,2001,2003 <NAME> <<EMAIL>>
  Copyright (C) 2003-2004 <NAME> <<EMAIL>>

  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 "koagendaitem.h"
#include "koprefs.h"
#include "koglobals.h"

#include <libkdepim/kvcarddrag.h>

#include <kcal/icaldrag.h>
#include <kcal/incidence.h>
#include <kcal/todo.h>
#include <kcal/incidenceformatter.h>
#include <kcal/vcaldrag.h>

#include <kpimutils/email.h>

#ifndef KORG_NOKABC
#include <kabc/addressee.h>
#include <kabc/vcardconverter.h>
#endif

#include <kiconloader.h>
#include <kdebug.h>
#include <klocale.h>
#include <kwordwrap.h>
#include <kmessagebox.h>

#include <QPainter>
#include <QPixmap>
#include <QPaintEvent>
#include <QList>
#include <QDropEvent>
#include <QDragEnterEvent>
#include <QPixmapCache>
#include <QToolTip>

#include "koagendaitem.moc"

//-----------------------------------------------------------------------------

QPixmap *KOAgendaItem::alarmPxmp = 0;
QPixmap *KOAgendaItem::recurPxmp = 0;
QPixmap *KOAgendaItem::readonlyPxmp = 0;
QPixmap *KOAgendaItem::replyPxmp = 0;
QPixmap *KOAgendaItem::groupPxmp = 0;
QPixmap *KOAgendaItem::groupPxmpTent = 0;
QPixmap *KOAgendaItem::organizerPxmp = 0;
QPixmap *KOAgendaItem::eventPxmp = 0;
QPixmap *KOAgendaItem::todoPxmp = 0;
QPixmap *KOAgendaItem::journalPxmp = 0;
QPixmap *KOAgendaItem::completedPxmp = 0;

//-----------------------------------------------------------------------------

KOAgendaItem::KOAgendaItem( Incidence *incidence, const QDate &qd, QWidget *parent )
  : QWidget( parent ), mIncidence( incidence ), mDate( qd ),
    mLabelText( mIncidence->summary() ), mIconAlarm( false ),
    mIconRecur( false ), mIconReadonly( false ), mIconReply( false ),
    mIconGroup( false ), mIconGroupTent( false ), mIconOrganizer( false ),
    mMultiItemInfo( 0 ), mStartMoveInfo( 0 )
{
  kDebug() << "Incidence Type is " << mIncidence->type();
  QPalette pal = palette();
  pal.setColor( QPalette::Window, Qt::transparent );
  setPalette( pal );

  setCellXY( 0, 0, 1 );
  setCellXRight( 0 );
  setMouseTracking( true );
  mResourceColor = QColor();
  updateIcons();

  // select() does nothing if the state hasn't changed, so preset mSelected.
  mSelected = true;
  select( false );

  setAcceptDrops( true );
}

void KOAgendaItem::updateIcons()
{
  if ( !mIncidence ) {
    return;
  }
  mIconReadonly = mIncidence->isReadOnly();
  mIconRecur = mIncidence->recurs();
  mIconAlarm = mIncidence->isAlarmEnabled();
  if ( mIncidence->attendeeCount() > 0 ) {
    if ( KOPrefs::instance()->thatIsMe( mIncidence->organizer().email() ) ) {
      mIconReply = false;
      mIconGroup = false;
      mIconGroupTent = false;
      mIconOrganizer = true;
    } else {
      Attendee *me = mIncidence->attendeeByMails( KOPrefs::instance()->allEmails() );
      if ( me ) {
        if ( me->status() == Attendee::NeedsAction && me->RSVP() ) {
          mIconReply = true;
          mIconGroup = false;
          mIconGroupTent = false;
          mIconOrganizer = false;
        } else if ( me->status() == Attendee::Tentative ) {
          mIconReply = false;
          mIconGroup = false;
          mIconGroupTent = true;
          mIconOrganizer = false;
        } else {
          mIconReply = false;
          mIconGroup = true;
          mIconGroupTent = false;
          mIconOrganizer = false;
        }
      } else {
        mIconReply = false;
        mIconGroup = true;
        mIconGroupTent = false;
        mIconOrganizer = false;
      }
    }
  }
  update();
}

void KOAgendaItem::select( bool selected )
{
  if ( mSelected == selected ) {
    return;
  }
  mSelected = selected;

  update();
}

bool KOAgendaItem::dissociateFromMultiItem()
{
  if ( !isMultiItem() ) {
    return false;
  }

  KOAgendaItem *firstItem = firstMultiItem();
  if ( firstItem == this ) {
    firstItem = nextMultiItem();
  }

  KOAgendaItem *lastItem = lastMultiItem();
  if ( lastItem == this ) {
    lastItem = prevMultiItem();
  }

  KOAgendaItem *prevItem = prevMultiItem();
  KOAgendaItem *nextItem = nextMultiItem();

  if ( prevItem ) {
    prevItem->setMultiItem( firstItem, prevItem->prevMultiItem(), nextItem, lastItem );
  }
  if ( nextItem ) {
    nextItem->setMultiItem( firstItem, prevItem, nextItem->prevMultiItem(), lastItem );
  }
  delete mMultiItemInfo;
  mMultiItemInfo = 0;
  return true;
}

bool KOAgendaItem::setIncidence( Incidence *i )
{
  mIncidence = i;
  updateIcons();
  return true;
}

/*
  Return height of item in units of agenda cells
*/
int KOAgendaItem::cellHeight() const
{
  return mCellYBottom - mCellYTop + 1;
}

/*
  Return height of item in units of agenda cells
*/
int KOAgendaItem::cellWidth() const
{
  return mCellXRight - mCellXLeft + 1;
}

void KOAgendaItem::setItemDate( const QDate &qd )
{
  mDate = qd;
}

void KOAgendaItem::setCellXY( int X, int YTop, int YBottom )
{
  mCellXLeft = X;
  mCellYTop = YTop;
  mCellYBottom = YBottom;
}

void KOAgendaItem::setCellXRight( int XRight )
{
  mCellXRight = XRight;
}

void KOAgendaItem::setCellX( int XLeft, int XRight )
{
  mCellXLeft = XLeft;
  mCellXRight = XRight;
}

void KOAgendaItem::setCellY( int YTop, int YBottom )
{
  mCellYTop = YTop;
  mCellYBottom = YBottom;
}

void KOAgendaItem::setMultiItem( KOAgendaItem *first, KOAgendaItem *prev,
                                 KOAgendaItem *next, KOAgendaItem *last )
{
  if ( !mMultiItemInfo ) {
    mMultiItemInfo = new MultiItemInfo;
  }
  mMultiItemInfo->mFirstMultiItem = first;
  mMultiItemInfo->mPrevMultiItem = prev;
  mMultiItemInfo->mNextMultiItem = next;
  mMultiItemInfo->mLastMultiItem = last;
}

bool KOAgendaItem::isMultiItem()
{
  return mMultiItemInfo;
}

KOAgendaItem *KOAgendaItem::prependMoveItem( KOAgendaItem *e )
{
  if ( !e ) {
    return e;
  }

  KOAgendaItem *first = 0, *last = 0;
  if ( isMultiItem() ) {
    first = mMultiItemInfo->mFirstMultiItem;
    last = mMultiItemInfo->mLastMultiItem;
  }
  if ( !first ) {
    first = this;
  }
  if ( !last ) {
    last = this;
  }

  e->setMultiItem( 0, 0, first, last );
  first->setMultiItem( e, e, first->nextMultiItem(), first->lastMultiItem() );

  KOAgendaItem *tmp = first->nextMultiItem();
  while ( tmp ) {
    tmp->setMultiItem( e, tmp->prevMultiItem(), tmp->nextMultiItem(), tmp->lastMultiItem() );
    tmp = tmp->nextMultiItem();
  }

  if ( mStartMoveInfo && !e->moveInfo() ) {
    e->mStartMoveInfo=new MultiItemInfo( *mStartMoveInfo );
//    e->moveInfo()->mFirstMultiItem = moveInfo()->mFirstMultiItem;
//    e->moveInfo()->mLastMultiItem = moveInfo()->mLastMultiItem;
    e->moveInfo()->mPrevMultiItem = 0;
    e->moveInfo()->mNextMultiItem = first;
  }

  if ( first && first->moveInfo() ) {
    first->moveInfo()->mPrevMultiItem = e;
  }
  return e;
}

KOAgendaItem *KOAgendaItem::appendMoveItem( KOAgendaItem *e )
{
  if ( !e ) {
    return e;
  }

  KOAgendaItem *first = 0, *last = 0;
  if ( isMultiItem() ) {
    first = mMultiItemInfo->mFirstMultiItem;
    last = mMultiItemInfo->mLastMultiItem;
  }
  if ( !first ) {
    first = this;
  }
  if ( !last ) {
    last = this;
  }

  e->setMultiItem( first, last, 0, 0 );
  KOAgendaItem *tmp = first;

  while ( tmp ) {
    tmp->setMultiItem( tmp->firstMultiItem(), tmp->prevMultiItem(), tmp->nextMultiItem(), e );
    tmp = tmp->nextMultiItem();
  }
  last->setMultiItem( last->firstMultiItem(), last->prevMultiItem(), e, e );

  if ( mStartMoveInfo && !e->moveInfo() ) {
    e->mStartMoveInfo=new MultiItemInfo( *mStartMoveInfo );
//    e->moveInfo()->mFirstMultiItem = moveInfo()->mFirstMultiItem;
//    e->moveInfo()->mLastMultiItem = moveInfo()->mLastMultiItem;
    e->moveInfo()->mPrevMultiItem = last;
    e->moveInfo()->mNextMultiItem = 0;
  }
  if ( last && last->moveInfo() ) {
    last->moveInfo()->mNextMultiItem = e;
  }
  return e;
}

KOAgendaItem *KOAgendaItem::removeMoveItem( KOAgendaItem *e )
{
  if ( isMultiItem() ) {
    KOAgendaItem *first = mMultiItemInfo->mFirstMultiItem;
    KOAgendaItem *next, *prev;
    KOAgendaItem *last = mMultiItemInfo->mLastMultiItem;
    if ( !first ) {
      first = this;
    }
    if ( !last ) {
      last = this;
    }
    if ( first == e ) {
      first = first->nextMultiItem();
      first->setMultiItem( 0, 0, first->nextMultiItem(), first->lastMultiItem() );
    }
    if ( last == e ) {
      last = last->prevMultiItem();
      last->setMultiItem( last->firstMultiItem(), last->prevMultiItem(), 0, 0 );
    }

    KOAgendaItem *tmp =  first;
    if ( first == last ) {
      delete mMultiItemInfo;
      tmp = 0;
      mMultiItemInfo = 0;
    }
    while ( tmp ) {
      next = tmp->nextMultiItem();
      prev = tmp->prevMultiItem();
      if ( e == next ) {
        next = next->nextMultiItem();
      }
      if ( e == prev ) {
        prev = prev->prevMultiItem();
      }
      tmp->setMultiItem( ( tmp == first ) ? 0 : first,
                         ( tmp == prev ) ? 0 : prev,
                         ( tmp == next ) ? 0 : next,
                         ( tmp == last ) ? 0 : last );
      tmp = tmp->nextMultiItem();
    }
  }

  return e;
}

void KOAgendaItem::startMove()
{
  KOAgendaItem *first = this;
  if ( isMultiItem() && mMultiItemInfo->mFirstMultiItem ) {
    first=mMultiItemInfo->mFirstMultiItem;
  }
  first->startMovePrivate();
}

void KOAgendaItem::startMovePrivate()
{
  mStartMoveInfo = new MultiItemInfo;
  mStartMoveInfo->mStartCellXLeft = mCellXLeft;
  mStartMoveInfo->mStartCellXRight = mCellXRight;
  mStartMoveInfo->mStartCellYTop = mCellYTop;
  mStartMoveInfo->mStartCellYBottom = mCellYBottom;
  if ( mMultiItemInfo ) {
    mStartMoveInfo->mFirstMultiItem = mMultiItemInfo->mFirstMultiItem;
    mStartMoveInfo->mLastMultiItem = mMultiItemInfo->mLastMultiItem;
    mStartMoveInfo->mPrevMultiItem = mMultiItemInfo->mPrevMultiItem;
    mStartMoveInfo->mNextMultiItem = mMultiItemInfo->mNextMultiItem;
  } else {
    mStartMoveInfo->mFirstMultiItem = 0;
    mStartMoveInfo->mLastMultiItem = 0;
    mStartMoveInfo->mPrevMultiItem = 0;
    mStartMoveInfo->mNextMultiItem = 0;
  }
  if ( isMultiItem() && mMultiItemInfo->mNextMultiItem ) {
    mMultiItemInfo->mNextMultiItem->startMovePrivate();
  }
}

void KOAgendaItem::resetMove()
{
  if ( mStartMoveInfo ) {
    if ( mStartMoveInfo->mFirstMultiItem ) {
      mStartMoveInfo->mFirstMultiItem->resetMovePrivate();
    } else {
      resetMovePrivate();
    }
  }
}

void KOAgendaItem::resetMovePrivate()
{
  if ( mStartMoveInfo ) {
    mCellXLeft = mStartMoveInfo->mStartCellXLeft;
    mCellXRight = mStartMoveInfo->mStartCellXRight;
    mCellYTop = mStartMoveInfo->mStartCellYTop;
    mCellYBottom = mStartMoveInfo->mStartCellYBottom;

    // if we don't have mMultiItemInfo, the item didn't span two days before,
    // and wasn't moved over midnight, either, so we don't have to reset
    // anything. Otherwise, restore from mMoveItemInfo
    if ( mMultiItemInfo ) {
      // It was already a multi-day info
      mMultiItemInfo->mFirstMultiItem = mStartMoveInfo->mFirstMultiItem;
      mMultiItemInfo->mPrevMultiItem = mStartMoveInfo->mPrevMultiItem;
      mMultiItemInfo->mNextMultiItem = mStartMoveInfo->mNextMultiItem;
      mMultiItemInfo->mLastMultiItem = mStartMoveInfo->mLastMultiItem;

      if ( !mStartMoveInfo->mFirstMultiItem ) {
        // This was the first multi-item when the move started, delete all previous
        KOAgendaItem *toDel = mStartMoveInfo->mPrevMultiItem;
        KOAgendaItem *nowDel = 0;
        while ( toDel ) {
          nowDel = toDel;
          if ( nowDel->moveInfo() ) {
            toDel = nowDel->moveInfo()->mPrevMultiItem;
          }
          emit removeAgendaItem( nowDel );
        }
        mMultiItemInfo->mFirstMultiItem = 0;
        mMultiItemInfo->mPrevMultiItem = 0;
      }
      if ( !mStartMoveInfo->mLastMultiItem ) {
        // This was the last multi-item when the move started, delete all next
        KOAgendaItem *toDel = mStartMoveInfo->mNextMultiItem;
        KOAgendaItem *nowDel = 0;
        while ( toDel ) {
          nowDel = toDel;
          if ( nowDel->moveInfo() ) {
            toDel=nowDel->moveInfo()->mNextMultiItem;
          }
          emit removeAgendaItem( nowDel );
        }
        mMultiItemInfo->mLastMultiItem = 0;
        mMultiItemInfo->mNextMultiItem = 0;
      }

      if ( mStartMoveInfo->mFirstMultiItem == 0 && mStartMoveInfo->mLastMultiItem == 0 ) {
        // it was a single-day event before we started the move.
        delete mMultiItemInfo;
        mMultiItemInfo = 0;
      }
    }
    delete mStartMoveInfo;
    mStartMoveInfo = 0;
  }
  emit showAgendaItem( this );
  if ( nextMultiItem() ) {
    nextMultiItem()->resetMovePrivate();
  }
}

void KOAgendaItem::endMove()
{
  KOAgendaItem *first = firstMultiItem();
  if ( !first ) {
    first = this;
  }
  first->endMovePrivate();
}

void KOAgendaItem::endMovePrivate()
{
  if ( mStartMoveInfo ) {
    // if first, delete all previous
    if ( !firstMultiItem() || firstMultiItem() == this ) {
      KOAgendaItem *toDel = mStartMoveInfo->mPrevMultiItem;
      KOAgendaItem *nowDel = 0;
      while ( toDel ) {
        nowDel = toDel;
        if ( nowDel->moveInfo() ) {
          toDel=nowDel->moveInfo()->mPrevMultiItem;
        }
        emit removeAgendaItem( nowDel );
      }
    }
    // if last, delete all next
    if ( !lastMultiItem() || lastMultiItem() == this ) {
      KOAgendaItem *toDel=mStartMoveInfo->mNextMultiItem;
      KOAgendaItem *nowDel = 0;
      while ( toDel ) {
        nowDel = toDel;
        if ( nowDel->moveInfo() ) {
          toDel=nowDel->moveInfo()->mNextMultiItem;
        }
        emit removeAgendaItem( nowDel );
      }
    }
    // also delete the moving info
    delete mStartMoveInfo;
    mStartMoveInfo = 0;
    if ( nextMultiItem() ) {
      nextMultiItem()->endMovePrivate();
    }
  }
}

void KOAgendaItem::moveRelative( int dx, int dy )
{
  int newXLeft = cellXLeft() + dx;
  int newXRight = cellXRight() + dx;
  int newYTop = cellYTop() + dy;
  int newYBottom = cellYBottom() + dy;
  setCellXY( newXLeft, newYTop, newYBottom );
  setCellXRight( newXRight );
}

void KOAgendaItem::expandTop( int dy )
{
  int newYTop = cellYTop() + dy;
  int newYBottom = cellYBottom();
  if ( newYTop > newYBottom ) {
    newYTop = newYBottom;
  }
  setCellY( newYTop, newYBottom );
}

void KOAgendaItem::expandBottom( int dy )
{
  int newYTop = cellYTop();
  int newYBottom = cellYBottom() + dy;
  if ( newYBottom < newYTop ) {
    newYBottom = newYTop;
  }
  setCellY( newYTop, newYBottom );
}

void KOAgendaItem::expandLeft( int dx )
{
  int newXLeft = cellXLeft() + dx;
  int newXRight = cellXRight();
  if ( newXLeft > newXRight ) {
    newXLeft = newXRight;
  }
  setCellX( newXLeft, newXRight );
}

void KOAgendaItem::expandRight( int dx )
{
  int newXLeft = cellXLeft();
  int newXRight = cellXRight() + dx;
  if ( newXRight < newXLeft ) {
    newXRight = newXLeft;
  }
  setCellX( newXLeft, newXRight );
}

void KOAgendaItem::dragEnterEvent( QDragEnterEvent *e )
{
#ifndef KORG_NODND
  const QMimeData *md = e->mimeData();
  if ( ICalDrag::canDecode( md ) || VCalDrag::canDecode( md ) ) {
    // TODO: Allow dragging events/todos onto other events to create a relation
    e->ignore();
    return;
  }
  if ( KPIM::KVCardDrag::canDecode( md ) || md->hasText() ) {
    e->accept();
  } else {
    e->ignore();
  }
#endif
}

void KOAgendaItem::addAttendee( const QString &newAttendee )
{
  QString name, email;
  KPIMUtils::extractEmailAddressAndName( newAttendee, email, name );
  if ( !( name.isEmpty() && email.isEmpty() ) ) {
    mIncidence->addAttendee( new Attendee( name, email ) );
    KMessageBox::information(
      this,
      i18n( "Attendee \"%1\" added to the calendar item \"%2\"",
            KPIMUtils::normalizedAddress( name, email, QString() ), text() ),
      i18n( "Attendee added" ), "AttendeeDroppedAdded" );
  }
}

void KOAgendaItem::dropEvent( QDropEvent *e )
{
  // TODO: Organize this better: First check for attachment
  // (not only file, also any other url!), then if it's a vcard,
  // otherwise check for attendees, then if the data is binary,
  // add a binary attachment.
#ifndef KORG_NODND
  const QMimeData *md = e->mimeData();

  bool decoded = md->hasText();
  QString text = md->text();
  if ( decoded && text.startsWith( "file:" ) ) {
    mIncidence->addAttachment( new Attachment( text ) );
    return;
  }

#ifndef KORG_NOKABC
  KABC::Addressee::List list;

  if ( KPIM::KVCardDrag::fromMimeData( md, list ) ) {
    KABC::Addressee::List::Iterator it;
    for ( it = list.begin(); it != list.end(); ++it ) {
      QString em( (*it).fullEmail() );
      if ( em.isEmpty() ) {
        em = (*it).realName();
      }
      addAttendee( em );
    }
  }
#else
  if( decoded ) {
    QStringList emails = text.split( ",", QString::SkipEmptyParts );
    for ( QStringList::ConstIterator it = emails.begin(); it != emails.end(); ++it ) {
      addAttendee( *it );
    }
  }
#endif // KORG_NOKABC

#endif // KORG_NODND
}

QList<KOAgendaItem*> &KOAgendaItem::conflictItems()
{
  return mConflictItems;
}

void KOAgendaItem::setConflictItems( QList<KOAgendaItem*> ci )
{
  mConflictItems = ci;
  QList<KOAgendaItem*>::iterator it;
  for ( it = mConflictItems.begin(); it != mConflictItems.end(); ++it ) {
    (*it)->addConflictItem( this );
  }
}

void KOAgendaItem::addConflictItem( KOAgendaItem *ci )
{
  if ( mConflictItems.contains( ci ) ) {
    mConflictItems.append( ci );
  }
}

QString KOAgendaItem::label() const
{
  return mLabelText;
}

bool KOAgendaItem::overlaps( KOrg::CellItem *o ) const
{
  KOAgendaItem *other = static_cast<KOAgendaItem *>( o );

  if ( cellXLeft() <= other->cellXRight() && cellXRight() >= other->cellXLeft() ) {
    if ( ( cellYTop() <= other->cellYBottom() ) && ( cellYBottom() >= other->cellYTop() ) ) {
      return true;
    }
  }

  return false;
}

static void conditionalPaint( QPainter *p, bool condition, int &x, int y,
                              int ft, const QPixmap &pxmp )
{
  if ( !condition ) {
    return;
  }

  p->drawPixmap( x, y, pxmp );
  x += pxmp.width() + ft;
}

void KOAgendaItem::paintEventIcon( QPainter *p, int &x, int y, int ft )
{
  if ( !mIncidence ) {
    return;
  }
  conditionalPaint( p, mIncidence->type() == "Event", x, y, ft, *eventPxmp );
}

void KOAgendaItem::paintTodoIcon( QPainter *p, int &x, int y, int ft )
{
  if ( !mIncidence || mIncidence->type() != "Todo" ) {
    return;
  }

  bool b = ( static_cast<Todo *>( mIncidence ) )->isCompleted();
  conditionalPaint( p, !b, x, y, ft, *todoPxmp );
  conditionalPaint( p, b, x, y, ft, *completedPxmp );
}

void KOAgendaItem::paintJournalIcon( QPainter *p, int &x, int y, int ft )
{
  if ( !mIncidence ) {
    return;
  }
  conditionalPaint( p, mIncidence->type() == "Journal", x, y, ft, *journalPxmp );
}

void KOAgendaItem::paintIcons( QPainter *p, int &x, int y, int ft )
{
  paintEventIcon( p, x, y, ft );
  paintTodoIcon( p, x, y, ft );
  paintJournalIcon( p, x, y, ft );
  conditionalPaint( p, mIconAlarm, x, y, ft, *alarmPxmp );
  conditionalPaint( p, mIconRecur, x, y, ft, *recurPxmp );
  conditionalPaint( p, mIconReadonly, x, y, ft, *readonlyPxmp );
  conditionalPaint( p, mIconReply, x, y, ft, *replyPxmp );
  conditionalPaint( p, mIconGroup, x, y, ft, *groupPxmp );
  conditionalPaint( p, mIconGroupTent, x, y, ft, *groupPxmpTent );
  conditionalPaint( p, mIconOrganizer, x, y, ft, *organizerPxmp );
}

void KOAgendaItem::paintEvent( QPaintEvent *ev )
{
  if ( !mIncidence ) {
    return;
  }

  QRect visRect = visibleRegion().boundingRect();
  // when scrolling horizontally in the side-by-side view, the repainted area is clipped
  // to the newly visible area, which is a problem since the content changes when visRect
  // changes, so repaint the full item in that case
  if ( ev->rect() != visRect && visRect.isValid() && ev->rect().isValid() ) {
    update( visRect );
    return;
  }

  QPainter p( this );
  p.setRenderHint( QPainter::Antialiasing );
  const int fmargin = 0; // frame margin
  const int ft = 1; // frame thickness for layout, see drawRoundedRect(),
                    // keep multiple of 2
  const int margin = 5 + ft + fmargin ; // frame + space between frame and content

  // General idea is to always show the icons (even in the all-day events).
  // This creates a consistent feeling for the user when the view mode
{'fraction_non_alphanumeric': 0.1376784284166392, 'fraction_numerical': 0.004852310736326377, 'mean_word_length': 3.0885015408320493, 'pattern_counts': {'":': 0, '<': 85, '<?xml version=': 0, '>': 431, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '212804', 'n_tokens_mistral': 15173, 'n_tokens_neox': 14572, 'n_words': 3454}