File indexing completed on 2024-05-19 15:27:38

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