File indexing completed on 2024-05-12 04:20:45

0001 /*
0002  * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
0003  *
0004  * This file is part of the KGantt library.
0005  *
0006  * SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "kganttsummaryhandlingproxymodel.h"
0010 #include "kganttsummaryhandlingproxymodel_p.h"
0011 
0012 #include <QDebug>
0013 
0014 #include <cassert>
0015 
0016 using namespace KGantt;
0017 
0018 
0019 
0020 typedef ForwardingProxyModel BASE;
0021 
0022 bool SummaryHandlingProxyModel::Private::cacheLookup( const QModelIndex& idx,
0023                                                       QPair<QDateTime,QDateTime>* result ) const
0024 {
0025     //qDebug() << "cacheLookup("<<idx<<"), cache has " << cached_summary_items.count() << "items";
0026     QHash<QModelIndex,QPair<QDateTime,QDateTime> >::const_iterator it =
0027         cached_summary_items.constFind( idx );
0028     if ( it != cached_summary_items.constEnd() ) {
0029         *result = *it;
0030         return true;
0031     } else {
0032         return false;
0033     }
0034 }
0035 
0036 void SummaryHandlingProxyModel::Private::insertInCache( const SummaryHandlingProxyModel* model,
0037                                                         const QModelIndex& sourceIdx ) const
0038 {
0039     QAbstractItemModel* sourceModel = model->sourceModel();
0040     const QModelIndex& mainIdx = sourceIdx;
0041     QDateTime st;
0042     QDateTime et;
0043 
0044     for ( int r = 0; r < sourceModel->rowCount( mainIdx ); ++r ) {
0045         QModelIndex pdIdx = model->mapFromSource( sourceModel->index( r, 0, mainIdx ) );
0046         /* The probably results in recursive calls here */
0047     QVariant tmpsv = model->data( pdIdx, StartTimeRole );
0048     QVariant tmpev = model->data( pdIdx, EndTimeRole );
0049     if ( !tmpsv.canConvert( QVariant::DateTime ) ||
0050         !tmpev.canConvert( QVariant::DateTime ) ) {
0051             qDebug() << "Skipping item " << sourceIdx << " because it doesn't contain QDateTime";
0052             continue;
0053         }
0054 
0055         // check for valid datetimes
0056         if ( tmpsv.type() == QVariant::DateTime && !tmpsv.value<QDateTime>().isValid()) continue;
0057         if ( tmpev.type() == QVariant::DateTime && !tmpev.value<QDateTime>().isValid()) continue;
0058 
0059     // We need to test for empty strings to
0060     // avoid a stupid Qt warning
0061     if ( tmpsv.type() == QVariant::String && tmpsv.value<QString>().isEmpty()) continue;
0062     if ( tmpev.type() == QVariant::String && tmpev.value<QString>().isEmpty()) continue;
0063         QDateTime tmpst = tmpsv.toDateTime();
0064         QDateTime tmpet = tmpev.toDateTime();
0065         if ( st.isNull() || st > tmpst ) st = tmpst;
0066         if ( et.isNull() || et < tmpet ) et = tmpet;
0067     }
0068     QVariant tmpssv = sourceModel->data( mainIdx, StartTimeRole );
0069     QVariant tmpsev = sourceModel->data( mainIdx, EndTimeRole );
0070     if ( tmpssv.canConvert( QVariant::DateTime )
0071          && !( tmpssv.canConvert( QVariant::String ) && tmpssv.toString().isEmpty() )
0072          && tmpssv.toDateTime() != st )
0073         sourceModel->setData( mainIdx, st, StartTimeRole );
0074     if ( tmpsev.canConvert( QVariant::DateTime )
0075          && !( tmpsev.canConvert( QVariant::String ) && tmpsev.toString().isEmpty() )
0076          && tmpsev.toDateTime() != et )
0077         sourceModel->setData( mainIdx, et, EndTimeRole );
0078     cached_summary_items[sourceIdx]=qMakePair( st, et );
0079 }
0080 
0081 void SummaryHandlingProxyModel::Private::removeFromCache( const QModelIndex& idx ) const
0082 {
0083     cached_summary_items.remove( idx );
0084 }
0085 
0086 void SummaryHandlingProxyModel::Private::clearCache() const
0087 {
0088     cached_summary_items.clear();
0089 }
0090 
0091 
0092 SummaryHandlingProxyModel::SummaryHandlingProxyModel( QObject* parent )
0093     : BASE( parent ), _d( new Private )
0094 {
0095     init();
0096 }
0097 
0098 #define d d_func()
0099 SummaryHandlingProxyModel::~SummaryHandlingProxyModel()
0100 {
0101     delete _d;
0102 }
0103 
0104 void SummaryHandlingProxyModel::init()
0105 {
0106 }
0107 
0108 
0109 void SummaryHandlingProxyModel::setSourceModel( QAbstractItemModel* model )
0110 {
0111     BASE::setSourceModel( model );
0112     d->clearCache();
0113 }
0114 
0115 void SummaryHandlingProxyModel::sourceModelReset()
0116 {
0117     d->clearCache();
0118     BASE::sourceModelReset();
0119 }
0120 
0121 void SummaryHandlingProxyModel::sourceLayoutChanged()
0122 {
0123     d->clearCache();
0124     BASE::sourceLayoutChanged();
0125 }
0126 
0127 void SummaryHandlingProxyModel::sourceDataChanged( const QModelIndex& from, const QModelIndex& to )
0128 {
0129     QAbstractItemModel* model = sourceModel();
0130     QModelIndex parentIdx = from;
0131     do {
0132         const QModelIndex& dataIdx = parentIdx;
0133         if ( model->data( dataIdx, ItemTypeRole )==TypeSummary ) {
0134             //qDebug() << "removing " << parentIdx << "from cache";
0135             d->removeFromCache( dataIdx );
0136             QModelIndex proxyDataIdx = mapFromSource( dataIdx );
0137             Q_EMIT dataChanged( proxyDataIdx, proxyDataIdx );
0138         }
0139     } while ( ( parentIdx=model->parent( parentIdx ) ) != QModelIndex() );
0140 
0141     BASE::sourceDataChanged( from, to );
0142 }
0143 
0144 void SummaryHandlingProxyModel::sourceColumnsAboutToBeInserted( const QModelIndex& parentIdx,
0145                                                                     int start,
0146                                                                     int end )
0147 {
0148     BASE::sourceColumnsAboutToBeInserted( parentIdx, start, end );
0149     d->clearCache();
0150 }
0151 
0152 void SummaryHandlingProxyModel::sourceColumnsAboutToBeRemoved( const QModelIndex& parentIdx,
0153                                                                     int start,
0154                                                                     int end )
0155 {
0156     BASE::sourceColumnsAboutToBeRemoved( parentIdx, start, end );
0157     d->clearCache();
0158 }
0159 
0160 void SummaryHandlingProxyModel::sourceRowsAboutToBeInserted( const QModelIndex & parentIdx, int start, int end )
0161 {
0162     BASE::sourceRowsAboutToBeInserted( parentIdx, start, end );
0163     d->clearCache();
0164 }
0165 
0166 void SummaryHandlingProxyModel::sourceRowsAboutToBeRemoved( const QModelIndex & parentIdx, int start, int end )
0167 {
0168     BASE::sourceRowsAboutToBeRemoved( parentIdx, start, end );
0169     d->clearCache();
0170 }
0171 
0172 
0173 Qt::ItemFlags SummaryHandlingProxyModel::flags( const QModelIndex& idx ) const
0174 {
0175     const QModelIndex sidx = mapToSource( idx );
0176     const QAbstractItemModel* model = sourceModel();
0177     Qt::ItemFlags f = model->flags( sidx );
0178     if ( d->isSummary(sidx) ) {
0179         f &= ~Qt::ItemIsEditable;
0180     }
0181     return f;
0182 }
0183 
0184 
0185 QVariant SummaryHandlingProxyModel::data( const QModelIndex& proxyIndex, int role) const
0186 {
0187   //qDebug() << "SummaryHandlingProxyModel::data("<<proxyIndex<<role<<")";
0188     const QModelIndex sidx = mapToSource( proxyIndex );
0189     const QAbstractItemModel* model = sourceModel();
0190     if ( d->isSummary(sidx) && ( role==StartTimeRole || role==EndTimeRole )) {
0191       //qDebug() << "requested summary";
0192         QPair<QDateTime,QDateTime> result;
0193         if ( d->cacheLookup( sidx, &result ) ) {
0194       //qDebug() << "SummaryHandlingProxyModel::data(): Looking up summary for " << proxyIndex << role;
0195             switch ( role ) {
0196             case StartTimeRole: return result.first;
0197             case EndTimeRole: return result.second;
0198             default: /* fall thru */;
0199             }
0200         } else {
0201             d->insertInCache( this, sidx );
0202             return data( proxyIndex, role ); /* TODO: Optimize */
0203         }
0204     }
0205     return model->data( sidx, role );
0206 }
0207 
0208 
0209 bool SummaryHandlingProxyModel::setData( const QModelIndex& index, const QVariant& value, int role )
0210 {
0211     QAbstractItemModel* model = sourceModel();
0212     if ( role==StartTimeRole || role==EndTimeRole ) {
0213         QModelIndex parentIdx = mapToSource( index );
0214         do {
0215             if ( d->isSummary(parentIdx) ) {
0216           //qDebug() << "removing " << parentIdx << "from cache";
0217                 d->removeFromCache( parentIdx );
0218                 QModelIndex proxyParentIdx = mapFromSource( parentIdx );
0219                 Q_EMIT dataChanged( proxyParentIdx, proxyParentIdx );
0220             }
0221         } while ( ( parentIdx=model->parent( parentIdx ) ) != QModelIndex() );
0222     }
0223     return BASE::setData( index, value, role );
0224 }
0225 
0226 #undef d
0227 
0228 #ifndef KDAB_NO_UNIT_TESTS
0229 
0230 #include "unittest/test.h"
0231 
0232 #include <QStandardItemModel>
0233 
0234 static std::ostream& operator<<( std::ostream& os, const QDateTime& dt )
0235 {
0236 #ifdef QT_NO_STL
0237     os << dt.toString().toLatin1().constData();
0238 #else
0239     os << dt.toString().toStdString();
0240 #endif
0241     return os;
0242 }
0243 
0244 KDAB_SCOPED_UNITTEST_SIMPLE( KGantt, SummaryHandlingProxyModel, "test" ) {
0245     SummaryHandlingProxyModel model;
0246     QStandardItemModel sourceModel;
0247 
0248     model.setSourceModel( &sourceModel );
0249 
0250     QStandardItem* topitem = new QStandardItem( QString::fromLatin1( "Summary" ) );
0251     topitem->setData( KGantt::TypeSummary,  KGantt::ItemTypeRole );
0252     sourceModel.appendRow( topitem );
0253 
0254     QStandardItem* task1 = new QStandardItem( QString::fromLatin1( "Task1" ) );
0255     task1->setData( KGantt::TypeTask, KGantt::ItemTypeRole );
0256     QStandardItem* task2 = new QStandardItem( QString::fromLatin1( "Task2" ) );
0257     task2->setData( KGantt::TypeTask, KGantt::ItemTypeRole );
0258     topitem->appendRow( task1 );
0259     topitem->appendRow( task2 );
0260 
0261 
0262     QDateTime startdt = QDateTime::currentDateTime();
0263     QDateTime enddt = startdt.addDays( 1 );
0264 
0265 
0266     task1->setData( startdt, KGantt::StartTimeRole );
0267     task1->setData( enddt, KGantt::EndTimeRole );
0268     task2->setData( startdt, KGantt::StartTimeRole );
0269     task2->setData( enddt, KGantt::EndTimeRole );
0270 
0271     const QModelIndex topidx = model.index( 0, 0, QModelIndex() );
0272 
0273     assertEqual( model.data( topidx, KGantt::ItemTypeRole ).toInt(), KGantt::TypeSummary );
0274     assertEqual( model.data( model.index( 0, 0, topidx ), KGantt::ItemTypeRole ).toInt(), KGantt::TypeTask );
0275 
0276     QDateTime task1startdt = model.data( model.index( 0, 0, topidx ), KGantt::StartTimeRole ).toDateTime();
0277     assertEqual( task1startdt, startdt );
0278 
0279     QDateTime summarystartdt = model.data( topidx, KGantt::StartTimeRole ).toDateTime();
0280     assertEqual( summarystartdt, startdt );
0281     assertTrue( model.flags( model.index( 0, 0, topidx ) ) & Qt::ItemIsEditable );
0282     assertFalse( model.flags( topidx ) & Qt::ItemIsEditable );
0283 }
0284 
0285 #endif /* KDAB_NO_UNIT_TESTS */
0286 
0287 #include "moc_kganttsummaryhandlingproxymodel.cpp"