/* Copyright (c) 2008 Thomas Thrainer Copyright (c) 2012 Sérgio Martins 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 "todomodel_p.h" #include "incidencetreemodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct SourceModelIndex { SourceModelIndex( int _r, int _c, void *_p, QAbstractItemModel *_m ) : r(_r), c(_c), p(_p), m(_m) { } operator QModelIndex() { return reinterpret_cast( *this ); } int r, c; void *p; const QAbstractItemModel *m; }; static bool isDueToday( const KCalCore::Todo::Ptr &todo ) { return !todo->isCompleted() && todo->dtDue().date() == QDate::currentDate(); } TodoModel::Private::Private( const EventViews::PrefsPtr &preferences, TodoModel *qq ) : QObject(), m_changer(0) , m_preferences( preferences ) , q( qq ) { } Akonadi::Item TodoModel::Private::findItemByUid( const QString &uid, const QModelIndex &parent ) const { Q_ASSERT( !uid.isEmpty() ); IncidenceTreeModel *treeModel = qobject_cast( q->sourceModel() ); if ( treeModel ) { // O(1) Shortcut return treeModel->item( uid ); } Akonadi::Item item; const int count = q->rowCount( parent ); for ( int i=0; iindex( i, 0, parent ); Q_ASSERT( currentIndex.isValid() ); item = q->data( currentIndex, Akonadi::EntityTreeModel::ItemRole ).value(); if ( item.isValid() ) { return item; } else { item = findItemByUid( uid, currentIndex ); if ( item.isValid() ) { return item; } } } return item; } void TodoModel::Private::onDataChanged( const QModelIndex &begin, const QModelIndex &end ) { Q_ASSERT( begin.isValid() ); Q_ASSERT( end.isValid() ); const QModelIndex proxyBegin = q->mapFromSource( begin ); Q_ASSERT( proxyBegin.column() == 0 ); const QModelIndex proxyEnd = q->mapFromSource( end ); emit q->dataChanged( proxyBegin, proxyEnd.sibling( proxyEnd.row(), TodoModel::ColumnCount-1 ) ); } void TodoModel::Private::onHeaderDataChanged( Qt::Orientation orientation, int first, int last ) { emit q->headerDataChanged( orientation, first, last ); } void TodoModel::Private::onRowsAboutToBeInserted( const QModelIndex &parent, int begin, int end ) { const QModelIndex index = q->mapFromSource( parent ); Q_ASSERT( !( parent.isValid() ^ index.isValid() ) ); // Both must be valid, or both invalid Q_ASSERT( !( index.isValid() && index.model() != q ) ); q->beginInsertRows( index, begin, end ); } void TodoModel::Private::onRowsInserted( const QModelIndex &, int, int ) { q->endInsertRows(); } void TodoModel::Private::onRowsAboutToBeRemoved( const QModelIndex &parent, int begin, int end ) { const QModelIndex index = q->mapFromSource( parent ); Q_ASSERT( !( parent.isValid() ^ index.isValid() ) ); // Both must be valid, or both invalid Q_ASSERT( !( index.isValid() && index.model() != q ) ); q->beginRemoveRows( index, begin, end ); } void TodoModel::Private::onRowsRemoved( const QModelIndex &, int, int ) { q->endRemoveRows(); } void TodoModel::Private::onRowsAboutToBeMoved( const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow ) { Q_UNUSED( sourceParent ); Q_UNUSED( sourceStart ); Q_UNUSED( sourceEnd ); Q_UNUSED( destinationParent ); Q_UNUSED( destinationRow ); /* Disabled for now, layoutAboutToBeChanged() is emitted q->beginMoveRows( q->mapFromSource( sourceParent ), sourceStart, sourceEnd, q->mapFromSource( destinationParent ), destinationRow ); */ } void TodoModel::Private::onRowsMoved( const QModelIndex &, int, int, const QModelIndex &, int ) { /*q->endMoveRows();*/ } void TodoModel::Private::onModelAboutToBeReset() { q->beginResetModel(); } void TodoModel::Private::onModelReset() { q->endResetModel(); } void TodoModel::Private::onLayoutAboutToBeChanged() { Q_ASSERT( m_persistentIndexes.isEmpty() ); Q_ASSERT( m_layoutChangePersistentIndexes.isEmpty() ); Q_ASSERT( m_columns.isEmpty() ); QModelIndexList persistentIndexes = q->persistentIndexList(); foreach ( const QPersistentModelIndex &persistentIndex, persistentIndexes ) { m_persistentIndexes << persistentIndex; // Stuff we have to update onLayoutChanged Q_ASSERT( persistentIndex.isValid() ); QModelIndex index_col0 = q->createIndex( persistentIndex.row(), 0, persistentIndex.internalPointer() ); const QPersistentModelIndex srcPersistentIndex = q->mapToSource( index_col0 ); Q_ASSERT( srcPersistentIndex.isValid() ); m_layoutChangePersistentIndexes << srcPersistentIndex; m_columns << persistentIndex.column(); } emit q->layoutAboutToBeChanged(); } void TodoModel::Private::onLayoutChanged() { for ( int i = 0; i < m_persistentIndexes.size(); ++i ) { QModelIndex newIndex_col0 = q->mapFromSource( m_layoutChangePersistentIndexes.at( i ) ); Q_ASSERT( newIndex_col0.isValid() ); const int column = m_columns.at( i ); QModelIndex newIndex = column == 0 ? newIndex_col0 : q->createIndex( newIndex_col0.row(), column, newIndex_col0.internalPointer() ); q->changePersistentIndex( m_persistentIndexes.at( i ), newIndex ); } m_layoutChangePersistentIndexes.clear(); m_persistentIndexes.clear(); m_columns.clear(); emit q->layoutChanged(); } TodoModel::TodoModel( const EventViews::PrefsPtr &preferences, QObject *parent ) : QAbstractProxyModel( parent ), d( new Private( preferences, this ) ) { setObjectName( QLatin1String("TodoModel") ); } TodoModel::~TodoModel() { delete d; } QVariant TodoModel::data( const QModelIndex &index, int role ) const { Q_ASSERT( index.isValid() ); if ( !index.isValid() || !d->m_calendar ) { return QVariant(); } const QModelIndex sourceIndex = mapToSource( index.sibling( index.row(), 0 ) ); if ( !sourceIndex.isValid() ) { return QVariant(); } Q_ASSERT( sourceIndex.isValid() ); const Akonadi::Item item = sourceIndex.data( Akonadi::EntityTreeModel::ItemRole ).value(); if ( !item.isValid() ) { kWarning() << "Invalid index: " << sourceIndex; //Q_ASSERT( false ); return QVariant(); } const KCalCore::Todo::Ptr todo = CalendarSupport::todo( item ); if ( !todo ) { kError() << "item.hasPayload()" << item.hasPayload(); if ( item.hasPayload() ) { KCalCore::Incidence::Ptr incidence = item.payload(); if ( incidence ) kError() << "It's actually " << incidence->type(); } Q_ASSERT(!"There's no to-do."); return QVariant(); } if ( role == Qt::DisplayRole ) { switch ( index.column() ) { case SummaryColumn: return QVariant( todo->summary() ); case RecurColumn: if (todo->recurs()) { if (todo->hasRecurrenceId()) { return i18nc( "yes, an exception to a recurring to-do", "Exception" ); } else { return i18nc( "yes, recurring to-do", "Yes" ); } } else { return i18nc( "no, not a recurring to-do", "No" ); } case PriorityColumn: if ( todo->priority() == 0 ) { return QVariant( QString::fromLatin1( "--" ) ); } return QVariant( todo->priority() ); case PercentColumn: return QVariant( todo->percentComplete() ); case StartDateColumn: return todo->hasStartDate() ? QVariant( KCalUtils::IncidenceFormatter::dateToString( todo->dtStart() ) ) : QVariant( QString() ); case DueDateColumn: return todo->hasDueDate() ? QVariant( KCalUtils::IncidenceFormatter::dateToString( todo->dtDue() ) ) : QVariant( QString() ); case CategoriesColumn: { QString categories = todo->categories().join( i18nc( "delimiter for joining category names", "," ) ); return QVariant( categories ); } case DescriptionColumn: return QVariant( todo->description() ); case CalendarColumn: return QVariant( CalendarSupport::displayName( d->m_calendar.data(), item.parentCollection() ) ); } return QVariant(); } if ( role == Qt::EditRole ) { switch ( index.column() ) { case SummaryColumn: return QVariant( todo->summary() ); case RecurColumn: return QVariant( todo->recurs() ); case PriorityColumn: return QVariant( todo->priority() ); case PercentColumn: return QVariant( todo->percentComplete() ); case StartDateColumn: return QVariant( todo->dtStart().date() ); case DueDateColumn: return QVariant( todo->dtDue().date() ); case CategoriesColumn: return QVariant( todo->categories() ); case DescriptionColumn: return QVariant( todo->description() ); case CalendarColumn: return QVariant( CalendarSupport::displayName( d->m_calendar.data(), item.parentCollection() ) ); } return QVariant(); } // set the tooltip for every item if ( role == Qt::ToolTipRole ) { if ( d->m_preferences->enableToolTips() ) { return QVariant( KCalUtils::IncidenceFormatter::toolTipStr( CalendarSupport::displayName( d->m_calendar.data(), item.parentCollection() ), todo, QDate(), true, CalendarSupport::KCalPrefs::instance()->timeSpec() ) ); } else { return QVariant(); } } // background colour for todos due today or overdue todos if ( role == Qt::BackgroundRole ) { if ( todo->isOverdue() ) { return QVariant( QBrush( d->m_preferences->todoOverdueColor() ) ); } else if ( isDueToday( todo ) ) { return QVariant( QBrush( d->m_preferences->todoDueTodayColor() ) ); } } // indicate if a row is checked (=completed) only in the first column if ( role == Qt::CheckStateRole && index.column() == 0 ) { if (hasChildren(index) && !index.parent().isValid()) { return QVariant(); } if ( todo->isCompleted() ) { return QVariant( Qt::Checked ); } else { return QVariant( Qt::Unchecked ); } } // icon for recurring todos // It's in the summary column so you don't accidentally click // the checkbox ( which increments the next occurrence date ). if ( role == Qt::DecorationRole && index.column() == SummaryColumn ) { if ( todo->recurs() ) { return QVariant( QIcon( SmallIcon( QLatin1String("task-recurring") ) ) ); } } // category colour if ( role == Qt::DecorationRole && index.column() == SummaryColumn ) { QStringList categories = todo->categories(); return categories.isEmpty() ? QVariant() : QVariant( CalendarSupport::KCalPrefs::instance()->categoryColor( categories.first() ) ); } else if ( role == Qt::DecorationRole ) { return QVariant(); } if ( role == TodoRole ) { QVariant ret( QMetaType::VoidStar ); ret.setValue( item ); return ret; } if ( role == IsRichTextRole ) { if ( index.column() == SummaryColumn ) { return QVariant( todo->summaryIsRich() ); } else if ( index.column() == DescriptionColumn ) { return QVariant( todo->descriptionIsRich() ); } else { return QVariant(); } } if ( role == Qt::TextAlignmentRole ) { switch ( index.column() ) { // If you change this, change headerData() too. case RecurColumn: case PriorityColumn: case PercentColumn: case StartDateColumn: case DueDateColumn: case CategoriesColumn: case CalendarColumn: return QVariant( Qt::AlignHCenter | Qt::AlignVCenter ); } return QVariant( Qt::AlignLeft | Qt::AlignVCenter ); } if ( sourceModel() ) { return sourceModel()->data( mapToSource( index.sibling( index.row(), 0 ) ), role ); } return QVariant(); } bool TodoModel::setData( const QModelIndex &index, const QVariant &value, int role ) { Q_ASSERT( index.isValid() ); if ( !d->m_changer ) { return false; } const QVariant oldValue = data( index, role ); if ( oldValue == value ) { // Nothing changed, the user used one of the QStyledDelegate's editors but seted the old value // Lets just skip this then and avoid a roundtrip to akonadi, and avoid sending invitations return true; } const Akonadi::Item item = data( index, Akonadi::EntityTreeModel::ItemRole ).value(); const KCalCore::Todo::Ptr todo = CalendarSupport::todo( item ); if ( !item.isValid() || !todo ) { kWarning() << "TodoModel::setData() called, bug item is invalid or doesn't have payload"; Q_ASSERT( false ); return false; } if ( d->m_calendar->hasRight( item, Akonadi::Collection::CanChangeItem ) ) { KCalCore::Todo::Ptr oldTodo( todo->clone() ); if ( role == Qt::CheckStateRole && index.column() == 0 ) { const bool checked = static_cast( value.toInt() ) == Qt::Checked; if ( checked ) todo->setCompleted( KDateTime::currentLocalDateTime() ); // Because it calls Todo::recurTodo() else todo->setCompleted( false ); } if ( role == Qt::EditRole ) { switch ( index.column() ) { case SummaryColumn: if ( !value.toString().isEmpty() ) { todo->setSummary( value.toString() ); } break; case PriorityColumn: todo->setPriority( value.toInt() ); break; case PercentColumn: todo->setPercentComplete( value.toInt() ); break; case StartDateColumn: { KDateTime tmp = todo->dtStart(); tmp.setDate( value.toDate() ); todo->setDtStart( tmp ); } break; case DueDateColumn: { KDateTime tmp = todo->dtDue(); tmp.setDate( value.toDate() ); todo->setDtDue( tmp ); } break; case CategoriesColumn: todo->setCategories( value.toStringList() ); break; case DescriptionColumn: todo->setDescription( value.toString() ); break; } } if ( !todo->dirtyFields().isEmpty() ) { d->m_changer->modifyIncidence( item, oldTodo ); // modifyIncidence will eventually call the view's // changeIncidenceDisplay method, which in turn // will call processChange. processChange will then emit // dataChanged to the view, so we don't have to // do it here } return true; } else { if ( !( role == Qt::CheckStateRole && index.column() == 0 ) ) { //KOHelper::showSaveIncidenceErrorMsg( 0, todo ); //TODO pass parent kError() << "Unable to modify incidence"; } return false; } } int TodoModel::rowCount( const QModelIndex &parent ) const { if ( sourceModel() ) { if ( parent.isValid() ) { QModelIndex parent_col0 = createIndex( parent.row(), 0, parent.internalPointer() ); return sourceModel()->rowCount( mapToSource( parent_col0 ) ); } else { return sourceModel()->rowCount(); } } return 0; } int TodoModel::columnCount( const QModelIndex & ) const { return ColumnCount; } void TodoModel::setSourceModel( QAbstractItemModel *model ) { if ( model == sourceModel() ) { return; } beginResetModel(); if ( sourceModel() ) { disconnect( sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), d, SLOT(onDataChanged(QModelIndex,QModelIndex)) ); disconnect( sourceModel(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)), d, SLOT(onHeaderDataChanged(Qt::Orientation,int,int)) ); disconnect( sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), d, SLOT(onRowsInserted(QModelIndex,int,int)) ); disconnect( sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), d, SLOT(onRowsRemoved(QModelIndex,int,int)) ); disconnect( sourceModel(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), d, SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int)) ); disconnect( sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeInserted(QModelIndex,int,int)) ); disconnect( sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeRemoved(QModelIndex,int,int)) ); disconnect( sourceModel(), SIGNAL(modelAboutToBeReset()), d, SLOT(onModelAboutToBeReset()) ); disconnect( sourceModel(), SIGNAL(modelReset()), d, SLOT(onModelReset()) ); disconnect( sourceModel(), SIGNAL(layoutAboutToBeChanged()), d, SLOT(onLayoutAboutToBeChanged()) ); disconnect( sourceModel(), SIGNAL(layoutChanged()), d, SLOT(onLayoutChanged()) ); } QAbstractProxyModel::setSourceModel( model ); if ( sourceModel() ) { connect( sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), d, SLOT(onDataChanged(QModelIndex,QModelIndex)) ); connect( sourceModel(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)), d, SLOT(onHeaderDataChanged(Qt::Orientation,int,int)) ); connect( sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeInserted(QModelIndex,int,int)) ); connect( sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), d, SLOT(onRowsInserted(QModelIndex,int,int)) ); connect( sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeRemoved(QModelIndex,int,int)) ); connect( sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), d, SLOT(onRowsRemoved(QModelIndex,int,int)) ); connect( sourceModel(), SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), d, SLOT(onRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)) ); connect( sourceModel(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), d, SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int)) ); connect( sourceModel(), SIGNAL(modelAboutToBeReset()), d, SLOT(onModelAboutToBeReset()) ); connect( sourceModel(), SIGNAL(modelReset()), d, SLOT(onModelReset()) ); connect( sourceModel(), SIGNAL(layoutAboutToBeChanged()), d, SLOT(onLayoutAboutToBeChanged()) ); connect( sourceModel(), SIGNAL(layoutChanged()), d, SLOT(onLayoutChanged()) ); } endResetModel(); } void TodoModel::setIncidenceChanger( Akonadi::IncidenceChanger *changer ) { d->m_changer = changer; } QVariant TodoModel::headerData( int column, Qt::Orientation orientation, int role ) const { if ( orientation != Qt::Horizontal ) { return QVariant(); } if ( role == Qt::DisplayRole ) { switch ( column ) { case SummaryColumn: return QVariant( i18n( "Summary" ) ); case RecurColumn: return QVariant( i18n( "Recurs" ) ); case PriorityColumn: return QVariant( i18n( "Priority" ) ); case PercentColumn: return QVariant( i18nc( "@title:column percent complete", "Complete" ) ); case StartDateColumn: return QVariant( i18n( "Start Date" ) ); case DueDateColumn: return QVariant( i18n( "Due Date" ) ); case CategoriesColumn: return QVariant( i18n( "Categories" ) ); case DescriptionColumn: return QVariant( i18n( "Description" ) ); case CalendarColumn: return QVariant( i18n( "Calendar" ) ); } } if ( role == Qt::TextAlignmentRole ) { switch ( column ) { // If you change this, change data() too. case RecurColumn: case PriorityColumn: case PercentColumn: case StartDateColumn: case DueDateColumn: case CategoriesColumn: case CalendarColumn: return QVariant( Qt::AlignHCenter ); } return QVariant(); } return QVariant(); } void TodoModel::setCalendar( const Akonadi::ETMCalendar::Ptr &calendar ) { d->m_calendar = calendar; } Qt::DropActions TodoModel::supportedDropActions() const { // Qt::CopyAction not supported yet return Qt::MoveAction; } QStringList TodoModel::mimeTypes() const { static QStringList list; if ( list.isEmpty() ) { list << KCalUtils::ICalDrag::mimeType() << KCalUtils::VCalDrag::mimeType(); } return list; } QMimeData *TodoModel::mimeData( const QModelIndexList &indexes ) const { Akonadi::Item::List items; Q_FOREACH ( const QModelIndex &index, indexes ) { const Akonadi::Item item = this->data( index, Akonadi::EntityTreeModel::ItemRole ).value(); if ( item.isValid() && !items.contains( item ) ) { items.push_back( item ); } } return CalendarSupport::createMimeData( items, d->m_calendar->timeSpec() ); } bool TodoModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent ) { Q_UNUSED( row ); Q_UNUSED( column ); if ( action != Qt::MoveAction ) { kWarning() << "No action other than MoveAction currently supported!"; //TODO return false; } if ( d->m_calendar && d->m_changer && ( KCalUtils::ICalDrag::canDecode( data ) || KCalUtils::VCalDrag::canDecode( data ) ) ) { KCalUtils::DndFactory dndFactory( d->m_calendar ); KCalCore::Todo::Ptr t = dndFactory.createDropTodo( data ); KCalCore::Event::Ptr e = dndFactory.createDropEvent( data ); if ( t ) { // we don't want to change the created todo, but the one which is already // stored in our calendar / tree const Akonadi::Item item = d->findItemByUid( t->uid(), QModelIndex() ); KCalCore::Todo::Ptr todo = CalendarSupport::todo( item ); KCalCore::Todo::Ptr destTodo; if ( parent.isValid() ) { const Akonadi::Item parentItem = this->data( parent, Akonadi::EntityTreeModel::ItemRole ).value(); if ( parentItem.isValid() ) { destTodo = CalendarSupport::todo( parentItem ); } } KCalCore::Incidence::Ptr tmp = destTodo; while ( tmp ) { if ( tmp->uid() == todo->uid() ) { //correct, don't use instanceIdentifier() here KMessageBox::information( 0, i18n( "Cannot move to-do to itself or a child of itself." ), i18n( "Drop To-do" ), QLatin1String("NoDropTodoOntoItself") ); return false; } const QString parentUid = tmp->relatedTo(); tmp = CalendarSupport::incidence( d->m_calendar->item( parentUid ) ); } if (!destTodo || !destTodo->hasRecurrenceId()) { KCalCore::Todo::Ptr oldTodo = KCalCore::Todo::Ptr( todo->clone() ); // destTodo is empty when we drag a to-do out of a relationship todo->setRelatedTo( destTodo ? destTodo->uid() : QString() ); d->m_changer->modifyIncidence( item, oldTodo ); // again, no need to emit dataChanged, that's done by processChange return true; } else { kDebug() << "Todo's with recurring id can't have child todos yet."; return false; } } else if ( e ) { // TODO: Implement dropping an event onto a to-do: Generate a relationship to the event! } else { if ( !parent.isValid() ) { // TODO we should create a new todo with the data in the drop object kDebug() << "TODO: Create a new todo with the given data"; return false; } const Akonadi::Item parentItem = this->data( parent, Akonadi::EntityTreeModel::ItemRole ).value(); KCalCore::Todo::Ptr destTodo = CalendarSupport::todo( parentItem ); if ( data->hasText() ) { QString text = data->text(); KCalCore::Todo::Ptr oldTodo = KCalCore::Todo::Ptr( destTodo->clone() ); if ( text.startsWith( QLatin1String( "file:" ) ) ) { destTodo->addAttachment( KCalCore::Attachment::Ptr( new KCalCore::Attachment( text ) ) ); } else { QStringList emails = KPIMUtils::splitAddressList( text ); for ( QStringList::ConstIterator it = emails.constBegin(); it != emails.constEnd(); ++it ) { QString name, email, comment; if ( KPIMUtils::splitAddress( *it, name, email, comment ) == KPIMUtils::AddressOk ) { destTodo->addAttendee( KCalCore::Attendee::Ptr( new KCalCore::Attendee( name, email ) ) ); } } } d->m_changer->modifyIncidence( parentItem, oldTodo ); return true; } } } return false; } Qt::ItemFlags TodoModel::flags( const QModelIndex &index ) const { if ( !index.isValid() ) { return 0; } Qt::ItemFlags ret = QAbstractItemModel::flags( index ); const Akonadi::Item item = data( index, Akonadi::EntityTreeModel::ItemRole ).value(); if ( !item.isValid() ) { Q_ASSERT( mapToSource( index ).isValid() ); kWarning() << "Item is invalid " << index; Q_ASSERT( false ); return 0; } ret |= Qt::ItemIsDragEnabled; const KCalCore::Todo::Ptr todo = CalendarSupport::todo( item ); if ( d->m_calendar->hasRight( item, Akonadi::Collection::CanChangeItem ) ) { // the following columns are editable: switch ( index.column() ) { case SummaryColumn: case PriorityColumn: case PercentColumn: case StartDateColumn: case DueDateColumn: case CategoriesColumn: ret |= Qt::ItemIsEditable; break; case DescriptionColumn: if ( !todo->descriptionIsRich() ) { ret |= Qt::ItemIsEditable; } break; } } if ( index.column() == 0 ) { // whole rows should have checkboxes, so append the flag for the // first item of every row only. Also, only the first item of every // row should be used as a target for a drag and drop operation. ret |= Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled; } return ret; } QModelIndex TodoModel::mapFromSource( const QModelIndex &sourceIndex ) const { if ( !sourceModel() || !sourceIndex.isValid() ) { return QModelIndex(); } Q_ASSERT( sourceIndex.internalPointer() ); return createIndex( sourceIndex.row(), 0, sourceIndex.internalPointer() ); } QModelIndex TodoModel::mapToSource( const QModelIndex &proxyIndex ) const { if ( !sourceModel() || !proxyIndex.isValid() ) { return QModelIndex(); } if ( proxyIndex.column() != 0 ) { kError() << "Map to source called with column>0, but source model only has 1 column"; Q_ASSERT( false ); } Q_ASSERT( proxyIndex.internalPointer() ); // we convert to column 0 const QModelIndex sourceIndex = SourceModelIndex( proxyIndex.row(), 0, proxyIndex.internalPointer(), sourceModel() ); return sourceIndex; } QModelIndex TodoModel::index( int row, int column, const QModelIndex &parent ) const { if ( !sourceModel() ) { return QModelIndex(); } Q_ASSERT( !parent.isValid() || parent.internalPointer() ); QModelIndex parent_col0 = parent.isValid() ? createIndex( parent.row(), 0, parent.internalPointer() ) : QModelIndex(); // Lets preserve the original internalPointer const QModelIndex index = mapFromSource( sourceModel()->index( row, 0, mapToSource( parent_col0 ) ) ); Q_ASSERT( !index.isValid() || index.internalPointer() ); if ( index.isValid() ) { return createIndex( row, column, index.internalPointer() ); } return QModelIndex(); } QModelIndex TodoModel::parent( const QModelIndex &child ) const { if ( !sourceModel() || !child.isValid() ) { return QModelIndex(); } Q_ASSERT( child.internalPointer() ); const QModelIndex child_col0 = createIndex( child.row(), 0, child.internalPointer() ); QModelIndex parentIndex = mapFromSource( sourceModel()->parent( mapToSource( child_col0 ) ) ); Q_ASSERT( !parentIndex.isValid() || parentIndex.internalPointer() ); if ( parentIndex.isValid() ) { // preserve original column return createIndex( parentIndex.row(), child.column(), parentIndex.internalPointer() ); } return QModelIndex(); } QModelIndex TodoModel::buddy( const QModelIndex &index ) const { // We reimplement because the default implementation calls mapToSource() and // source model doesn't have the same number of columns. return index; }