File indexing completed on 2024-04-28 04:37:04

0001 /*
0002     SPDX-FileCopyrightText: 2007 Andreas Pakulat <apaku@gmx.de>
0003     SPDX-FileCopyrightText: 2009 Aleix Pol <aleixpol@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "projectbuildsetmodel.h"
0009 
0010 #include <QVariant>
0011 
0012 #include <KLocalizedString>
0013 #include <KConfigGroup>
0014 
0015 #include <interfaces/icore.h>
0016 #include <interfaces/iproject.h>
0017 #include <interfaces/iprojectcontroller.h>
0018 #include <interfaces/isession.h>
0019 
0020 #include "projectmodel.h"
0021 #include <util/kdevstringhandler.h>
0022 #include <QIcon>
0023 
0024 namespace KDevelop
0025 {
0026 
0027 BuildItem::BuildItem()
0028 {
0029 }
0030 
0031 BuildItem::BuildItem( const QStringList & itemPath )
0032     : m_itemPath( itemPath )
0033 {
0034 }
0035 
0036 BuildItem::BuildItem( KDevelop::ProjectBaseItem* item )
0037 {
0038     initializeFromItem( item );
0039 }
0040 
0041 BuildItem::BuildItem( const BuildItem& rhs )
0042     : m_itemPath(rhs.itemPath())
0043 {
0044 }
0045 
0046 void BuildItem::initializeFromItem( KDevelop::ProjectBaseItem* item )
0047 {
0048     Q_ASSERT(item);
0049     KDevelop::ProjectModel* model=KDevelop::ICore::self()->projectController()->projectModel();
0050 
0051     m_itemPath = model->pathFromIndex(item->index());
0052 }
0053 
0054 QString BuildItem::itemName() const
0055 {
0056     return m_itemPath.last();
0057 }
0058 
0059 QString BuildItem::itemProject() const
0060 {
0061     return m_itemPath.first();
0062 }
0063 
0064 KDevelop::ProjectBaseItem* BuildItem::findItem() const
0065 {
0066     KDevelop::ProjectModel* model=KDevelop::ICore::self()->projectController()->projectModel();
0067     QModelIndex idx = model->pathToIndex(m_itemPath);
0068     return model->itemFromIndex(idx);
0069 }
0070 
0071 bool operator==( const BuildItem& rhs, const BuildItem& lhs  )
0072 {
0073     return( rhs.itemPath() == lhs.itemPath() );
0074 }
0075 
0076 BuildItem& BuildItem::operator=( const BuildItem& rhs )
0077 {
0078     if( this == &rhs )
0079         return *this;
0080     m_itemPath = rhs.itemPath();
0081     return *this;
0082 }
0083 
0084 
0085 class ProjectBuildSetModelPrivate
0086 {
0087 public:
0088     QList<BuildItem> items;
0089     QList<QStringList> orderingCache;
0090 };
0091 
0092 
0093 ProjectBuildSetModel::ProjectBuildSetModel( QObject* parent )
0094     : QAbstractTableModel( parent )
0095     , d_ptr(new ProjectBuildSetModelPrivate)
0096 {
0097 }
0098 
0099 ProjectBuildSetModel::~ProjectBuildSetModel() = default;
0100 
0101 void ProjectBuildSetModel::loadFromSession( ISession* session )
0102 {
0103     Q_D(ProjectBuildSetModel);
0104 
0105     if (!session) {
0106         return;
0107     }
0108 
0109     // Load the item ordering cache
0110     KConfigGroup sessionBuildSetConfig = session->config()->group( "Buildset" );
0111     const QVariantList sessionBuildItems = KDevelop::stringToQVariant(sessionBuildSetConfig.readEntry("BuildItems", QString())).toList();
0112     d->orderingCache.reserve(d->orderingCache.size() + sessionBuildItems.size());
0113     for (const QVariant& item : sessionBuildItems) {
0114         d->orderingCache.append(item.toStringList());
0115     }
0116 }
0117 
0118 void ProjectBuildSetModel::storeToSession( ISession* session )
0119 {
0120     Q_D(ProjectBuildSetModel);
0121 
0122     if (!session) {
0123         return;
0124     }
0125 
0126     // Store the item ordering cache
0127     QVariantList sessionBuildItems;
0128     sessionBuildItems.reserve(d->orderingCache.size());
0129     for (const QStringList& item : qAsConst(d->orderingCache)) {
0130         sessionBuildItems.append( item );
0131     }
0132     KConfigGroup sessionBuildSetConfig = session->config()->group( "Buildset" );
0133     sessionBuildSetConfig.writeEntry("BuildItems", KDevelop::qvariantToString( QVariant( sessionBuildItems ) ));
0134     sessionBuildSetConfig.sync();
0135 }
0136 
0137 
0138 int ProjectBuildSetModel::findInsertionPlace( const QStringList& itemPath )
0139 {
0140     /*
0141      * The ordering cache list is a superset of the build set, and must be ordered in the same way.
0142      * Example:
0143      * (items)         A - B ----- D --------- G
0144      * (orderingCache) A - B - C - D - E - F - G
0145      *
0146      * We scan orderingCache until we find the required item (absent in items: say, F).
0147      * In process of scanning we synchronize position in orderingCache with position in items;
0148      * so, when we reach F, we have D as last synchronization point and hence return it
0149      * as the insertion place (actually, we return the next item's index - here, index of G).
0150      *
0151      * If an item cannot be found in the ordering list, we append it to the list.
0152      */
0153 
0154     Q_D(ProjectBuildSetModel);
0155 
0156     int insertionIndex = 0;
0157     bool found = false;
0158     // Points to the item which is next to last synchronization point.
0159     QList<BuildItem>::iterator nextItemIterator = d->items.begin();
0160 
0161     for (auto& orderedItemPath : qAsConst(d->orderingCache)) {
0162         if (itemPath == orderedItemPath) {
0163             found = true;
0164             break;
0165         }
0166         if (nextItemIterator != d->items.end() &&
0167             nextItemIterator->itemPath() == orderedItemPath) {
0168             ++insertionIndex;
0169             ++nextItemIterator;
0170         }
0171     }
0172 
0173     if( !found ) {
0174         d->orderingCache.append(itemPath);
0175     }
0176     Q_ASSERT( insertionIndex >= 0 && insertionIndex <= d->items.size() );
0177     return insertionIndex;
0178 }
0179 
0180 void ProjectBuildSetModel::removeItemsWithCache( const QList<int>& itemIndices )
0181 {
0182     /*
0183      * Removes the items with given indices from both the build set and the ordering cache.
0184      * List is given since removing many items together is more efficient than by one.
0185      * 
0186      * Indices in the list shall be sorted.
0187      */
0188 
0189     Q_D(ProjectBuildSetModel);
0190 
0191     QList<int> itemIndicesCopy = itemIndices;
0192 
0193     beginRemoveRows( QModelIndex(), itemIndices.first(), itemIndices.last() );
0194     for (QList<QStringList>::iterator cacheIterator = d->orderingCache.end() - 1;
0195          cacheIterator >= d->orderingCache.begin() && !itemIndicesCopy.isEmpty();) {
0196 
0197         int index = itemIndicesCopy.back();
0198         Q_ASSERT( index >= 0 && index < d->items.size() );
0199         if (*cacheIterator == d->items.at(index).itemPath()) {
0200             cacheIterator = d->orderingCache.erase(cacheIterator);
0201             d->items.removeAt(index);
0202             itemIndicesCopy.removeLast();
0203         }
0204         --cacheIterator;
0205 
0206     } // for
0207     endRemoveRows();
0208 
0209     Q_ASSERT( itemIndicesCopy.isEmpty() );
0210 }
0211 
0212 void ProjectBuildSetModel::insertItemWithCache( const BuildItem& item )
0213 {
0214     Q_D(ProjectBuildSetModel);
0215 
0216     int insertionPlace = findInsertionPlace( item.itemPath() );
0217     beginInsertRows( QModelIndex(), insertionPlace, insertionPlace );
0218     d->items.insert(insertionPlace, item);
0219     endInsertRows();
0220 }
0221 
0222 void ProjectBuildSetModel::insertItemsOverrideCache( int index, const QList< BuildItem >& items )
0223 {
0224     Q_D(ProjectBuildSetModel);
0225 
0226     Q_ASSERT( index >= 0 && index <= d->items.size() );
0227 
0228     if (index == d->items.size()) {
0229         beginInsertRows( QModelIndex(), index, index + items.size() - 1 );
0230         d->items.append(items);
0231         d->orderingCache.reserve(d->orderingCache.size() + items.size());
0232         for (const BuildItem& item : items) {
0233             d->orderingCache.append(item.itemPath());
0234         }
0235         endInsertRows();
0236     } else {
0237         int indexInCache = d->orderingCache.indexOf(d->items.at(index).itemPath());
0238         Q_ASSERT( indexInCache >= 0 );
0239 
0240         beginInsertRows( QModelIndex(), index, index + items.size() - 1 );
0241         for( int i = 0; i < items.size(); ++i ) {
0242             const BuildItem& item = items.at( i );
0243             d->items.insert(index + i, item);
0244             d->orderingCache.insert(indexInCache + i, item.itemPath());
0245         }
0246         endInsertRows();
0247     }
0248 }
0249 
0250 QVariant ProjectBuildSetModel::data( const QModelIndex& idx, int role ) const
0251 {
0252     Q_D(const ProjectBuildSetModel);
0253 
0254     if( !idx.isValid() || idx.row() < 0 || idx.column() < 0
0255          || idx.row() >= rowCount() || idx.column() >= columnCount())
0256     {
0257         return QVariant();
0258     }
0259     
0260     if(role == Qt::DisplayRole) {
0261         switch( idx.column() )
0262         {
0263             case 0:
0264                 return d->items.at(idx.row()).itemName();
0265             case 1:
0266                 return KDevelop::joinWithEscaping(d->items.at(idx.row()).itemPath(), QLatin1Char('/'), QLatin1Char('\\'));
0267         }
0268     } else if(role == Qt::DecorationRole && idx.column()==0) {
0269         KDevelop::ProjectBaseItem* item = d->items.at(idx.row()).findItem();
0270         if( item ) {
0271             return QIcon::fromTheme( item->iconName() );
0272         }
0273     }
0274     return QVariant();
0275 }
0276 
0277 QVariant ProjectBuildSetModel::headerData( int section, Qt::Orientation orientation, int role ) const
0278 {
0279     if( section < 0 || section >= columnCount()
0280         || orientation != Qt::Horizontal || role != Qt::DisplayRole )
0281         return QVariant();
0282 
0283     switch( section )
0284     {
0285         case 0:
0286             return i18nc("@title:column buildset item name", "Name");
0287         case 1:
0288             return i18nc("@title:column buildset item path", "Path");
0289     }
0290     return QVariant();
0291 }
0292 
0293 int ProjectBuildSetModel::rowCount( const QModelIndex& parent ) const
0294 {
0295     Q_D(const ProjectBuildSetModel);
0296 
0297     if( parent.isValid() )
0298         return 0;
0299     return d->items.count();
0300 }
0301 
0302 int ProjectBuildSetModel::columnCount( const QModelIndex& parent ) const
0303 {
0304     if( parent.isValid() )
0305         return 0;
0306     return 2;
0307 }
0308 
0309 void ProjectBuildSetModel::addProjectItem( KDevelop::ProjectBaseItem* item )
0310 {
0311     Q_D(ProjectBuildSetModel);
0312 
0313     BuildItem buildItem( item );
0314     if (d->items.contains(buildItem))
0315         return;
0316 
0317     insertItemWithCache( buildItem );
0318 }
0319 
0320 bool ProjectBuildSetModel::removeRows( int row, int count, const QModelIndex& parent )
0321 {
0322     if( parent.isValid() || row > rowCount() || row < 0 || (row+count) > rowCount() || count <= 0 )
0323         return false;
0324 
0325     QList<int> itemsToRemove;
0326     itemsToRemove.reserve(count);
0327     for( int i = row; i < row+count; i++ )
0328     {
0329         itemsToRemove.append( i );
0330     }
0331     removeItemsWithCache( itemsToRemove );
0332     return true;
0333 }
0334 
0335 QList<BuildItem> ProjectBuildSetModel::items() const
0336 {
0337     Q_D(const ProjectBuildSetModel);
0338 
0339     return d->items;
0340 }
0341 
0342 void ProjectBuildSetModel::projectClosed( KDevelop::IProject* project )
0343 {
0344     Q_D(ProjectBuildSetModel);
0345 
0346     for (int i = d->items.count() - 1; i >= 0; --i) {
0347         if (d->items.at(i).itemProject() == project->name()) {
0348             beginRemoveRows( QModelIndex(), i, i );
0349             d->items.removeAt(i);
0350             endRemoveRows();
0351         }
0352     }  
0353 }
0354 
0355 void ProjectBuildSetModel::saveToProject( KDevelop::IProject* project ) const
0356 {
0357     Q_D(const ProjectBuildSetModel);
0358 
0359     QVariantList paths;
0360     for (const BuildItem& item : qAsConst(d->items)) {
0361         if( item.itemProject() == project->name() )
0362             paths.append(item.itemPath());
0363     }
0364     KConfigGroup base = project->projectConfiguration()->group("Buildset");
0365     base.writeEntry("BuildItems", KDevelop::qvariantToString( QVariant( paths ) ));
0366     base.sync();
0367 }
0368 
0369 void ProjectBuildSetModel::loadFromProject( KDevelop::IProject* project )
0370 {
0371     KConfigGroup base = project->projectConfiguration()->group("Buildset");
0372     if (base.hasKey("BuildItems")) {
0373         const QVariantList items = KDevelop::stringToQVariant(base.readEntry("BuildItems", QString())).toList();
0374 
0375         for (const QVariant& path : items) {
0376             insertItemWithCache( BuildItem( path.toStringList() ) );
0377         }
0378     } else {
0379         // Add project to buildset, but only if there is no configuration for this project yet.
0380         addProjectItem( project->projectItem() );
0381     }
0382 }
0383 
0384 void ProjectBuildSetModel::moveRowsDown(int row, int count)
0385 {
0386     Q_D(ProjectBuildSetModel);
0387 
0388     QList<BuildItem> items = d->items.mid(row, count);
0389     removeRows( row, count );
0390     insertItemsOverrideCache( row + 1, items );
0391 }
0392 
0393 void ProjectBuildSetModel::moveRowsToBottom(int row, int count)
0394 {
0395     Q_D(ProjectBuildSetModel);
0396 
0397     QList<BuildItem> items = d->items.mid(row, count);
0398     removeRows( row, count );
0399     insertItemsOverrideCache( rowCount(), items );
0400 }
0401 
0402 void ProjectBuildSetModel::moveRowsUp(int row, int count)
0403 {
0404     Q_D(ProjectBuildSetModel);
0405 
0406     QList<BuildItem> items = d->items.mid(row, count);
0407     removeRows( row, count );
0408     insertItemsOverrideCache( row - 1, items );
0409 }
0410 
0411 void ProjectBuildSetModel::moveRowsToTop(int row, int count)
0412 {
0413     Q_D(ProjectBuildSetModel);
0414 
0415     QList<BuildItem> items = d->items.mid(row, count);
0416     removeRows( row, count );
0417     insertItemsOverrideCache( 0, items );
0418 }
0419 
0420 }
0421 
0422 #include "moc_projectbuildsetmodel.cpp"