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

0001 /*
0002     SPDX-FileCopyrightText: 2005 Roberto Raggi <roberto@kdevelop.org>
0003     SPDX-FileCopyrightText: 2007 Andreas Pakulat <apaku@gmx.de>
0004     SPDX-FileCopyrightText: 2007 Aleix Pol <aleixpol@gmail.com>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "projectmodel.h"
0010 
0011 #include <QIcon>
0012 #include <QMimeDatabase>
0013 #include <QMimeType>
0014 #include <QMutex>
0015 #include <QMutexLocker>
0016 
0017 #include <KIO/StatJob>
0018 #include <KLocalizedString>
0019 
0020 #include <interfaces/iproject.h>
0021 #include <interfaces/iprojectcontroller.h>
0022 #include <interfaces/icore.h>
0023 #include "interfaces/iprojectfilemanager.h"
0024 #include <serialization/indexedstring.h>
0025 
0026 #include "debug.h"
0027 #include "path.h"
0028 
0029 namespace KDevelop
0030 {
0031 
0032 QStringList removeProjectBasePath( const QStringList& fullpath, KDevelop::ProjectBaseItem* item )
0033 {
0034     QStringList result = fullpath;
0035     if( item )
0036     {
0037         KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel();
0038         QStringList basePath = model->pathFromIndex( model->indexFromItem( item ) );
0039         if( basePath.count() >= fullpath.count() )
0040         {
0041             return QStringList();
0042         }
0043         return result.mid(basePath.count());
0044     }
0045     return result;
0046 }
0047 
0048 QStringList joinProjectBasePath( const QStringList& partialpath, KDevelop::ProjectBaseItem* item )
0049 {
0050     QStringList basePath;
0051     if( item )
0052     {
0053         KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel();
0054         basePath = model->pathFromIndex( model->indexFromItem( item ) );
0055     }
0056     return basePath + partialpath;
0057 }
0058 
0059 inline uint indexForPath( const Path& path )
0060 {
0061     return IndexedString::indexForString(path.pathOrUrl());
0062 }
0063 
0064 class ProjectModelPrivate
0065 {
0066 public:
0067     explicit ProjectModelPrivate( ProjectModel* model ): model( model )
0068     {
0069     }
0070     ProjectBaseItem* rootItem;
0071     ProjectModel* model;
0072     ProjectBaseItem* itemFromIndex(const QModelIndex& idx) const
0073     {
0074         if( !idx.isValid() ) {
0075             return rootItem;
0076         }
0077         if( idx.model() != model ) {
0078             return nullptr;
0079         }
0080         return model->itemFromIndex( idx );
0081     }
0082 
0083     // a hash of IndexedString::indexForString(path) <-> ProjectBaseItem for fast lookup
0084     QMultiHash<uint, ProjectBaseItem*> pathLookupTable;
0085 };
0086 
0087 class ProjectBaseItemPrivate
0088 {
0089 public:
0090     ProjectBaseItemPrivate() {}
0091     ProjectModel* model = nullptr;
0092     IProject* project = nullptr;
0093     ProjectBaseItem* parent = nullptr;
0094     QList<ProjectBaseItem*> children;
0095     QString text;
0096     Path m_path;
0097     QString iconName;
0098     int row = -1;
0099     uint m_pathIndex = 0;
0100     Qt::ItemFlags flags;
0101 
0102     ProjectBaseItem::RenameStatus renameBaseItem(ProjectBaseItem* item, const QString& newName)
0103     {
0104         if (item->parent()) {
0105             const auto siblings = item->parent()->children();
0106             for (ProjectBaseItem* sibling : siblings) {
0107                 if (sibling->text() == newName) {
0108                     return ProjectBaseItem::ExistingItemSameName;
0109                 }
0110             }
0111         }
0112         item->setText( newName );
0113         return ProjectBaseItem::RenameOk;
0114     }
0115 
0116     ProjectBaseItem::RenameStatus renameFileOrFolder(ProjectBaseItem* item, const QString& newName)
0117     {
0118         Q_ASSERT(item->file() || item->folder());
0119 
0120         if (newName.contains(QLatin1Char('/'))) {
0121             return ProjectBaseItem::InvalidNewName;
0122         }
0123 
0124         if (item->text() == newName) {
0125             return ProjectBaseItem::RenameOk;
0126         }
0127 
0128         Path newPath = item->path();
0129         newPath.setLastPathSegment(newName);
0130 
0131         auto job = KIO::statDetails(newPath.toUrl(), KIO::StatJob::SourceSide, KIO::StatNoDetails, KIO::HideProgressInfo);
0132         if (job->exec()) {
0133             // file/folder exists already
0134             return ProjectBaseItem::ExistingItemSameName;
0135         }
0136 
0137         if( !item->project() || !item->project()->projectFileManager() ) {
0138             return renameBaseItem(item, newName);
0139         } else if( item->folder() && item->project()->projectFileManager()->renameFolder(item->folder(), newPath) ) {
0140             return ProjectBaseItem::RenameOk;
0141         } else if ( item->file() && item->project()->projectFileManager()->renameFile(item->file(), newPath) ) {
0142             return ProjectBaseItem::RenameOk;
0143         } else {
0144             return ProjectBaseItem::ProjectManagerRenameFailed;
0145         }
0146     }
0147 };
0148 
0149 
0150 ProjectBaseItem::ProjectBaseItem( IProject* project, const QString &name, ProjectBaseItem *parent )
0151         : d_ptr(new ProjectBaseItemPrivate)
0152 {
0153     Q_ASSERT(!name.isEmpty() || !parent);
0154     Q_D(ProjectBaseItem);
0155     d->project = project;
0156     d->text = name;
0157     d->flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
0158     if( parent ) {
0159         parent->appendRow( this );
0160     }
0161 }
0162 
0163 ProjectBaseItem::~ProjectBaseItem()
0164 {
0165     Q_D(ProjectBaseItem);
0166 
0167     if (model() && d->m_pathIndex) {
0168         model()->d_func()->pathLookupTable.remove(d->m_pathIndex, this);
0169     }
0170 
0171     if( parent() ) {
0172         parent()->takeRow( d->row );
0173     } else if( model() ) {
0174         model()->takeRow( d->row );
0175     }
0176     removeRows(0, d->children.size());
0177 }
0178 
0179 ProjectBaseItem* ProjectBaseItem::child( int row ) const
0180 {
0181     Q_D(const ProjectBaseItem);
0182     if( row < 0 || row >= d->children.length() ) {
0183         return nullptr;
0184     }
0185     return d->children.at( row );
0186 }
0187 
0188 QList< ProjectBaseItem* > ProjectBaseItem::children() const
0189 {
0190     Q_D(const ProjectBaseItem);
0191     return d->children;
0192 }
0193 
0194 ProjectBaseItem* ProjectBaseItem::takeRow(int row)
0195 {
0196     Q_D(ProjectBaseItem);
0197     Q_ASSERT(row >= 0 && row < d->children.size());
0198 
0199     if( model() ) {
0200         model()->beginRemoveRows(index(), row, row);
0201     }
0202     ProjectBaseItem* olditem = d->children.takeAt( row );
0203     olditem->d_func()->parent = nullptr;
0204     olditem->d_func()->row = -1;
0205     olditem->setModel( nullptr );
0206 
0207     for (int i = row, count = d->children.size(); i < count; i++) {
0208         auto sibling = d->children.at(i);
0209         Q_ASSERT(sibling->d_func()->row == i + 1);
0210         sibling->d_func()->row = i;
0211     }
0212 
0213     if( model() ) {
0214         model()->endRemoveRows();
0215     }
0216     return olditem;
0217 }
0218 
0219 void ProjectBaseItem::removeRow( int row )
0220 {
0221     delete takeRow( row );
0222 }
0223 
0224 void ProjectBaseItem::removeRows(int row, int count)
0225 {
0226     if (!count) {
0227         return;
0228     }
0229 
0230     Q_D(ProjectBaseItem);
0231     Q_ASSERT(row >= 0 && row + count <= d->children.size());
0232 
0233     if( model() ) {
0234         model()->beginRemoveRows(index(), row, row + count - 1);
0235     }
0236 
0237     //NOTE: we unset parent, row and model manually to speed up the deletion
0238     if (row == 0 && count == d->children.size()) {
0239         // optimize if we want to delete all
0240         for (ProjectBaseItem* item : qAsConst(d->children)) {
0241             item->d_func()->parent = nullptr;
0242             item->d_func()->row = -1;
0243             item->setModel( nullptr );
0244             delete item;
0245         }
0246         d->children.clear();
0247     } else {
0248         for (int i = row; i < count; ++i) {
0249             ProjectBaseItem* item = d->children.at(i);
0250             item->d_func()->parent = nullptr;
0251             item->d_func()->row = -1;
0252             item->setModel( nullptr );
0253             delete d->children.takeAt( row );
0254         }
0255         for(int i = row; i < d->children.size(); ++i) {
0256             d->children.at(i)->d_func()->row--;
0257             Q_ASSERT(child(i)->d_func()->row==i);
0258         }
0259     }
0260 
0261     if( model() ) {
0262         model()->endRemoveRows();
0263     }
0264 }
0265 
0266 QModelIndex ProjectBaseItem::index() const
0267 {
0268     if( model() ) {
0269         return model()->indexFromItem( this );
0270     }
0271     return QModelIndex();
0272 }
0273 
0274 int ProjectBaseItem::rowCount() const
0275 {
0276     Q_D(const ProjectBaseItem);
0277     return d->children.count();
0278 }
0279 
0280 int ProjectBaseItem::type() const
0281 {
0282     return ProjectBaseItem::BaseItem;
0283 }
0284 
0285 ProjectModel* ProjectBaseItem::model() const
0286 {
0287     Q_D(const ProjectBaseItem);
0288     return d->model;
0289 }
0290 
0291 ProjectBaseItem* ProjectBaseItem::parent() const
0292 {
0293     Q_D(const ProjectBaseItem);
0294     if( model() && model()->d_func()->rootItem == d->parent ) {
0295         return nullptr;
0296     }
0297     return d->parent;
0298 }
0299 
0300 int ProjectBaseItem::row() const
0301 {
0302     Q_D(const ProjectBaseItem);
0303     return d->row;
0304 }
0305 
0306 QString ProjectBaseItem::text() const
0307 {
0308     Q_D(const ProjectBaseItem);
0309     if( project() && !parent() ) {
0310         return project()->name();
0311     } else {
0312         return d->text;
0313     }
0314 }
0315 
0316 void ProjectBaseItem::setModel( ProjectModel* model )
0317 {
0318     Q_D(ProjectBaseItem);
0319 
0320     if (model == d->model) {
0321         return;
0322     }
0323 
0324     if (d->model && d->m_pathIndex) {
0325         d->model->d_func()->pathLookupTable.remove(d->m_pathIndex, this);
0326     }
0327 
0328     d->model = model;
0329 
0330     if (model && d->m_pathIndex) {
0331         model->d_func()->pathLookupTable.insert(d->m_pathIndex, this);
0332     }
0333 
0334     for (ProjectBaseItem* item : qAsConst(d->children)) {
0335         item->setModel( model );
0336     }
0337 }
0338 
0339 void ProjectBaseItem::setRow( int row )
0340 {
0341     Q_D(ProjectBaseItem);
0342     d->row = row;
0343 }
0344 
0345 void ProjectBaseItem::setText( const QString& text )
0346 {
0347     Q_ASSERT(!text.isEmpty() || !parent());
0348     Q_D(ProjectBaseItem);
0349     d->text = text;
0350     if( d->model ) {
0351         QModelIndex idx = index();
0352         emit d->model->dataChanged(idx, idx);
0353     }
0354 }
0355 
0356 ProjectBaseItem::RenameStatus ProjectBaseItem::rename(const QString& newName)
0357 {
0358     Q_D(ProjectBaseItem);
0359     return d->renameBaseItem(this, newName);
0360 }
0361 
0362 KDevelop::ProjectBaseItem::ProjectItemType baseType( int type )
0363 {
0364     if( type == KDevelop::ProjectBaseItem::Folder || type == KDevelop::ProjectBaseItem::BuildFolder )
0365         return KDevelop::ProjectBaseItem::Folder;
0366     if( type == KDevelop::ProjectBaseItem::Target || type == KDevelop::ProjectBaseItem::ExecutableTarget
0367         || type == KDevelop::ProjectBaseItem::LibraryTarget)
0368         return KDevelop::ProjectBaseItem::Target;
0369 
0370     return static_cast<KDevelop::ProjectBaseItem::ProjectItemType>( type );
0371 }
0372 
0373 bool ProjectBaseItem::lessThan( const KDevelop::ProjectBaseItem* item ) const
0374 {
0375     if(item->type() >= KDevelop::ProjectBaseItem::CustomProjectItemType ) {
0376         // For custom types we want to make sure that if they override lessThan, then we
0377         // prefer their lessThan implementation
0378         return !item->lessThan( this );
0379     }
0380     KDevelop::ProjectBaseItem::ProjectItemType leftType=baseType(type()), rightType=baseType(item->type());
0381     if(leftType==rightType)
0382     {
0383         return text().compare(item->text(), Qt::CaseInsensitive) < 0;
0384     }
0385     else
0386     {
0387         return leftType<rightType;
0388     }
0389 
0390     return false;
0391 }
0392 
0393 bool ProjectBaseItem::pathLessThan(ProjectBaseItem* item1, ProjectBaseItem* item2)
0394 {
0395     return item1->path() < item2->path();
0396 }
0397 
0398 IProject* ProjectBaseItem::project() const
0399 {
0400     Q_D(const ProjectBaseItem);
0401     return d->project;
0402 }
0403 
0404 void ProjectBaseItem::appendRow( ProjectBaseItem* item )
0405 {
0406     Q_D(ProjectBaseItem);
0407     if( !item ) {
0408         return;
0409     }
0410     if( item->parent() ) {
0411         // Proper way is to first removeRow() on the original parent, then appendRow on this one
0412         qCWarning(PROJECT) << "Ignoring double insertion of item" << item;
0413         return;
0414     }
0415     // this is too slow... O(n) and thankfully not a problem anyways
0416 //     Q_ASSERT(!d->children.contains(item));
0417     int startrow,endrow;
0418     if( model() ) {
0419         startrow = endrow = d->children.count();
0420         model()->beginInsertRows(index(), startrow, endrow);
0421     }
0422     d->children.append( item );
0423     item->setRow( d->children.count() - 1 );
0424     item->d_func()->parent = this;
0425     item->setModel( model() );
0426     if( model() ) {
0427         model()->endInsertRows();
0428     }
0429 }
0430 
0431 Path ProjectBaseItem::path() const
0432 {
0433     Q_D(const ProjectBaseItem);
0434     return d->m_path;
0435 }
0436 
0437 IndexedString ProjectBaseItem::indexedPath() const
0438 {
0439     return IndexedString::fromIndex( d_ptr->m_pathIndex );
0440 }
0441 
0442 QString ProjectBaseItem::baseName() const
0443 {
0444     return text();
0445 }
0446 
0447 void ProjectBaseItem::setPath( const Path& path)
0448 {
0449     Q_D(ProjectBaseItem);
0450 
0451     if (model() && d->m_pathIndex) {
0452         model()->d_func()->pathLookupTable.remove(d->m_pathIndex, this);
0453     }
0454 
0455     d->m_path = path;
0456     d->m_pathIndex = indexForPath(path);
0457     setText( path.lastPathSegment() );
0458 
0459     if (model() && d->m_pathIndex) {
0460         model()->d_func()->pathLookupTable.insert(d->m_pathIndex, this);
0461     }
0462 }
0463 
0464 Qt::ItemFlags ProjectBaseItem::flags()
0465 {
0466     Q_D(ProjectBaseItem);
0467     return d->flags;
0468 }
0469 
0470 Qt::DropActions ProjectModel::supportedDropActions() const
0471 {
0472     return (Qt::DropActions)(Qt::MoveAction);
0473 }
0474 
0475 void ProjectBaseItem::setFlags(Qt::ItemFlags flags)
0476 {
0477     Q_D(ProjectBaseItem);
0478     d->flags = flags;
0479     if(d->model)
0480         emit d->model->dataChanged(index(), index());
0481 }
0482 
0483 QString ProjectBaseItem::iconName() const
0484 {
0485     return QString();
0486 }
0487 
0488 ProjectFolderItem *ProjectBaseItem::folder() const
0489 {
0490     return nullptr;
0491 }
0492 
0493 ProjectTargetItem *ProjectBaseItem::target() const
0494 {
0495     return nullptr;
0496 }
0497 
0498 ProjectExecutableTargetItem *ProjectBaseItem::executable() const
0499 {
0500     return nullptr;
0501 }
0502 
0503 ProjectFileItem *ProjectBaseItem::file() const
0504 {
0505     return nullptr;
0506 }
0507 
0508 QList<ProjectFolderItem*> ProjectBaseItem::folderList() const
0509 {
0510     QList<ProjectFolderItem*> lst;
0511     for ( int i = 0; i < rowCount(); ++i )
0512     {
0513         ProjectBaseItem* item = child( i );
0514         if ( item->type() == Folder || item->type() == BuildFolder )
0515         {
0516             auto *kdevitem = dynamic_cast<ProjectFolderItem*>( item );
0517             if ( kdevitem )
0518                 lst.append( kdevitem );
0519         }
0520     }
0521 
0522     return lst;
0523 }
0524 
0525 QList<ProjectTargetItem*> ProjectBaseItem::targetList() const
0526 {
0527     QList<ProjectTargetItem*> lst;
0528     for ( int i = 0; i < rowCount(); ++i )
0529     {
0530         ProjectBaseItem* item = child( i );
0531 
0532         if ( item->type() == Target || item->type() == LibraryTarget || item->type() == ExecutableTarget )
0533         {
0534             auto *kdevitem = dynamic_cast<ProjectTargetItem*>( item );
0535             if ( kdevitem )
0536                 lst.append( kdevitem );
0537         }
0538     }
0539 
0540     return lst;
0541 }
0542 
0543 QList<ProjectFileItem*> ProjectBaseItem::fileList() const
0544 {
0545     QList<ProjectFileItem*> lst;
0546     for ( int i = 0; i < rowCount(); ++i )
0547     {
0548         ProjectBaseItem* item = child( i );
0549         Q_ASSERT(item);
0550         if ( item && item->type() == File )
0551         {
0552             auto *kdevitem = dynamic_cast<ProjectFileItem*>( item );
0553             if ( kdevitem )
0554                 lst.append( kdevitem );
0555         }
0556 
0557     }
0558     return lst;
0559 }
0560 
0561 void ProjectModel::clear()
0562 {
0563     Q_D(ProjectModel);
0564 
0565     d->rootItem->removeRows(0, d->rootItem->rowCount());
0566 }
0567 
0568 
0569 ProjectFolderItem::ProjectFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent)
0570     : ProjectBaseItem( project, path.lastPathSegment(), parent )
0571 {
0572     setPath( path );
0573 
0574     setFlags(flags() | Qt::ItemIsDropEnabled);
0575     if (project && project->path() != path)
0576         setFlags(flags() | Qt::ItemIsDragEnabled);
0577 }
0578 
0579 ProjectFolderItem::ProjectFolderItem( const QString & name, ProjectBaseItem * parent )
0580         : ProjectBaseItem( parent->project(), name, parent )
0581 {
0582     setPath( Path(parent->path(), name) );
0583 
0584     setFlags(flags() | Qt::ItemIsDropEnabled);
0585     if (project() && project()->path() != path())
0586         setFlags(flags() | Qt::ItemIsDragEnabled);
0587 }
0588 
0589 ProjectFolderItem::~ProjectFolderItem()
0590 {
0591 }
0592 
0593 void ProjectFolderItem::setPath( const Path& path )
0594 {
0595     ProjectBaseItem::setPath(path);
0596 
0597     propagateRename(path);
0598 }
0599 
0600 ProjectFolderItem *ProjectFolderItem::folder() const
0601 {
0602     return const_cast<ProjectFolderItem*>(this);
0603 }
0604 
0605 int ProjectFolderItem::type() const
0606 {
0607     return ProjectBaseItem::Folder;
0608 }
0609 
0610 QString ProjectFolderItem::folderName() const
0611 {
0612     return baseName();
0613 }
0614 
0615 void ProjectFolderItem::propagateRename( const Path& newBase ) const
0616 {
0617     Path path = newBase;
0618     path.addPath(QStringLiteral("dummy"));
0619     const auto children = this->children();
0620     for (KDevelop::ProjectBaseItem* child : children) {
0621         path.setLastPathSegment( child->text() );
0622         child->setPath( path );
0623 
0624         const ProjectFolderItem* folder = child->folder();
0625         if ( folder ) {
0626             folder->propagateRename( path );
0627         }
0628     }
0629 }
0630 
0631 ProjectBaseItem::RenameStatus ProjectFolderItem::rename(const QString& newName)
0632 {
0633     return d_ptr->renameFileOrFolder(this, newName);
0634 }
0635 
0636 bool ProjectFolderItem::hasFileOrFolder(const QString& name) const
0637 {
0638     const auto children = this->children();
0639     return std::any_of(children.begin(), children.end(), [&](ProjectBaseItem* item) {
0640         return ((item->type() == Folder || item->type() == File || item->type() == BuildFolder)
0641                 && name == item->baseName());
0642     });
0643 }
0644 
0645 bool ProjectBaseItem::isProjectRoot() const
0646 {
0647     return parent()==nullptr;
0648 }
0649 
0650 ProjectBuildFolderItem::ProjectBuildFolderItem(IProject* project, const Path& path, ProjectBaseItem *parent)
0651     : ProjectFolderItem( project, path, parent )
0652 {
0653 }
0654 
0655 ProjectBuildFolderItem::ProjectBuildFolderItem( const QString& name, ProjectBaseItem* parent )
0656     : ProjectFolderItem( name, parent )
0657 {
0658 
0659 }
0660 
0661 QString ProjectFolderItem::iconName() const
0662 {
0663     return QStringLiteral("folder");
0664 }
0665 
0666 int ProjectBuildFolderItem::type() const
0667 {
0668     return ProjectBaseItem::BuildFolder;
0669 }
0670 
0671 QString ProjectBuildFolderItem::iconName() const
0672 {
0673     return QStringLiteral("folder-development");
0674 }
0675 
0676 ProjectFileItem::ProjectFileItem( IProject* project, const Path& path, ProjectBaseItem* parent )
0677     : ProjectBaseItem( project, path.lastPathSegment(), parent )
0678 {
0679     setFlags(flags() | Qt::ItemIsDragEnabled);
0680     setPath( path );
0681 }
0682 
0683 ProjectFileItem::ProjectFileItem( const QString& name, ProjectBaseItem* parent )
0684     : ProjectBaseItem( parent->project(), name, parent )
0685 {
0686     setFlags(flags() | Qt::ItemIsDragEnabled);
0687     setPath( Path(parent->path(), name) );
0688 }
0689 
0690 ProjectFileItem::~ProjectFileItem()
0691 {
0692     if( project() && d_ptr->m_pathIndex ) {
0693         project()->removeFromFileSet( this );
0694     }
0695 }
0696 
0697 ProjectBaseItem::RenameStatus ProjectFileItem::rename(const QString& newName)
0698 {
0699     return d_ptr->renameFileOrFolder(this, newName);
0700 }
0701 
0702 QString ProjectFileItem::fileName() const
0703 {
0704     return baseName();
0705 }
0706 
0707 // Maximum length of a string to still consider it as a file extension which we cache
0708 // This has to be a slow value, so that we don't fill our file extension cache with crap
0709 static const int maximumCacheExtensionLength = 3;
0710 
0711 bool isNumeric(const QStringRef& str)
0712 {
0713     if (str.isEmpty()) {
0714         return false;
0715     }
0716 
0717     return std::all_of(str.begin(), str.end(), [](const QChar c) {
0718         return c.isNumber();
0719     });
0720 }
0721 
0722 class IconNameCache
0723 {
0724 public:
0725     QString iconNameForPath(const Path& path, const QString& fileName)
0726     {
0727         // find icon name based on file extension, if possible
0728         QString extension;
0729         int extensionStart = fileName.lastIndexOf(QLatin1Char('.'));
0730         if( extensionStart != -1 && fileName.length() - extensionStart - 1 <= maximumCacheExtensionLength ) {
0731             QStringRef extRef = fileName.midRef(extensionStart + 1);
0732             if( isNumeric(extRef) ) {
0733                 // don't cache numeric extensions
0734                 extRef.clear();
0735             }
0736             if( !extRef.isEmpty() ) {
0737                 extension = extRef.toString();
0738                 QMutexLocker lock(&mutex);
0739                 QHash< QString, QString >::const_iterator it = fileExtensionToIcon.constFind( extension );
0740                 if( it != fileExtensionToIcon.constEnd() ) {
0741                     return *it;
0742                 }
0743             }
0744         }
0745 
0746         QMimeType mime = QMimeDatabase().mimeTypeForFile(path.lastPathSegment(), QMimeDatabase::MatchExtension); // no I/O
0747         QMutexLocker lock(&mutex);
0748         QHash< QString, QString >::const_iterator it = mimeToIcon.constFind(mime.name());
0749         QString iconName;
0750         if ( it == mimeToIcon.constEnd() ) {
0751             iconName = mime.iconName();
0752             if (iconName.isEmpty()) {
0753                 iconName = QStringLiteral("none");
0754             }
0755             mimeToIcon.insert(mime.name(), iconName);
0756         } else {
0757             iconName = *it;
0758         }
0759         if ( !extension.isEmpty() ) {
0760             fileExtensionToIcon.insert(extension, iconName);
0761         }
0762         return iconName;
0763     }
0764     QMutex mutex;
0765     QHash<QString, QString> mimeToIcon;
0766     QHash<QString, QString> fileExtensionToIcon;
0767 };
0768 
0769 Q_GLOBAL_STATIC(IconNameCache, s_cache)
0770 
0771 QString ProjectFileItem::iconName() const
0772 {
0773     // think of d_ptr->iconName as mutable, possible since d_ptr is not const
0774     if (d_ptr->iconName.isEmpty()) {
0775         // lazy load implementation of icon lookup
0776         d_ptr->iconName = s_cache->iconNameForPath( d_ptr->m_path, d_ptr->text );
0777         // we should always get *some* icon name back
0778         Q_ASSERT(!d_ptr->iconName.isEmpty());
0779     }
0780     return d_ptr->iconName;
0781 }
0782 
0783 void ProjectFileItem::setPath( const Path& path )
0784 {
0785     if (path == d_ptr->m_path) {
0786         return;
0787     }
0788 
0789     if( project() && d_ptr->m_pathIndex ) {
0790         // remove from fileset if we are in there
0791         project()->removeFromFileSet( this );
0792     }
0793 
0794     ProjectBaseItem::setPath( path );
0795 
0796     if( project() && d_ptr->m_pathIndex ) {
0797         // add to fileset with new path
0798         project()->addToFileSet( this );
0799     }
0800 
0801     // invalidate icon name for future lazy-loaded updated
0802     d_ptr->iconName.clear();
0803 }
0804 
0805 int ProjectFileItem::type() const
0806 {
0807     return ProjectBaseItem::File;
0808 }
0809 
0810 ProjectFileItem *ProjectFileItem::file() const
0811 {
0812     return const_cast<ProjectFileItem*>( this );
0813 }
0814 
0815 ProjectTargetItem::ProjectTargetItem( IProject* project, const QString &name, ProjectBaseItem *parent )
0816     : ProjectBaseItem( project, name, parent )
0817 {
0818     setFlags(flags() | Qt::ItemIsDropEnabled);
0819 }
0820 
0821 QString ProjectTargetItem::iconName() const
0822 {
0823     return QStringLiteral("system-run");
0824 }
0825 
0826 void ProjectTargetItem::setPath( const Path& path )
0827 {
0828     // don't call base class, it calls setText with the new path's filename
0829     // which we do not want for target items
0830     d_ptr->m_path = path;
0831 }
0832 
0833 int ProjectTargetItem::type() const
0834 {
0835     return ProjectBaseItem::Target;
0836 }
0837 
0838 ProjectTargetItem *ProjectTargetItem::target() const
0839 {
0840     return const_cast<ProjectTargetItem*>( this );
0841 }
0842 
0843 ProjectExecutableTargetItem::ProjectExecutableTargetItem( IProject* project, const QString &name, ProjectBaseItem *parent )
0844     : ProjectTargetItem(project, name, parent)
0845 {
0846 }
0847 
0848 ProjectExecutableTargetItem *ProjectExecutableTargetItem::executable() const
0849 {
0850     return const_cast<ProjectExecutableTargetItem*>( this );
0851 }
0852 
0853 int ProjectExecutableTargetItem::type() const
0854 {
0855     return ProjectBaseItem::ExecutableTarget;
0856 }
0857 
0858 ProjectLibraryTargetItem::ProjectLibraryTargetItem( IProject* project, const QString &name, ProjectBaseItem *parent )
0859     : ProjectTargetItem(project, name, parent)
0860 {}
0861 
0862 int ProjectLibraryTargetItem::type() const
0863 {
0864     return ProjectBaseItem::LibraryTarget;
0865 }
0866 
0867 QModelIndex ProjectModel::pathToIndex(const QStringList& tofetch_) const
0868 {
0869     if(tofetch_.isEmpty())
0870         return QModelIndex();
0871     QStringList tofetch(tofetch_);
0872     if(tofetch.last().isEmpty())
0873         tofetch.removeLast();
0874 
0875     QModelIndex current=index(0,0, QModelIndex());
0876 
0877     QModelIndex ret;
0878     for(int a = 0; a < tofetch.size(); ++a)
0879     {
0880         const QString& currentName = tofetch[a];
0881 
0882         bool matched = false;
0883         const QModelIndexList l = match(current, Qt::DisplayRole, currentName, -1, Qt::MatchExactly);
0884         for (const QModelIndex& idx : l) {
0885             //If this is not the last item, only match folders, as there may be targets and folders with the same name
0886             if(a == tofetch.size()-1 || itemFromIndex(idx)->folder()) {
0887                 ret = idx;
0888                 current = index(0,0, ret);
0889                 matched = true;
0890                 break;
0891             }
0892         }
0893         if(!matched) {
0894             ret = QModelIndex();
0895             break;
0896         }
0897     }
0898     Q_ASSERT(!ret.isValid() || data(ret).toString()==tofetch.last());
0899     return ret;
0900 }
0901 
0902 QStringList ProjectModel::pathFromIndex(const QModelIndex& index) const
0903 {
0904     if (!index.isValid())
0905         return QStringList();
0906 
0907     QModelIndex idx = index;
0908     QStringList list;
0909     do {
0910         QString t = data(idx, Qt::DisplayRole).toString();
0911         list.prepend(t);
0912         QModelIndex parent = idx.parent();
0913         idx = parent.sibling(parent.row(), index.column());
0914     } while (idx.isValid());
0915 
0916     return list;
0917 }
0918 
0919 int ProjectModel::columnCount( const QModelIndex& ) const
0920 {
0921     return 1;
0922 }
0923 
0924 int ProjectModel::rowCount( const QModelIndex& parent ) const
0925 {
0926     Q_D(const ProjectModel);
0927 
0928     ProjectBaseItem* item = d->itemFromIndex( parent );
0929     return item ? item->rowCount() : 0;
0930 }
0931 
0932 QModelIndex ProjectModel::parent( const QModelIndex& child ) const
0933 {
0934     if( child.isValid() ) {
0935         auto* item = static_cast<ProjectBaseItem*>( child.internalPointer() );
0936         return indexFromItem( item );
0937     }
0938     return QModelIndex();
0939 }
0940 
0941 QModelIndex ProjectModel::indexFromItem( const ProjectBaseItem* item ) const
0942 {
0943     if( item && item->d_func()->parent ) {
0944         return createIndex( item->row(), 0, item->d_func()->parent );
0945     }
0946     return QModelIndex();
0947 }
0948 
0949 ProjectBaseItem* ProjectModel::itemFromIndex( const QModelIndex& index ) const
0950 {
0951     if( index.row() >= 0 && index.column() == 0  && index.model() == this ) {
0952         auto* parent = static_cast<ProjectBaseItem*>( index.internalPointer() );
0953         if( parent ) {
0954             return parent->child( index.row() );
0955         }
0956     }
0957     return nullptr;
0958 }
0959 
0960 QVariant ProjectModel::data( const QModelIndex& index, int role ) const
0961 {
0962     static const QSet<int> allowedRoles = {
0963         Qt::DisplayRole,
0964         Qt::ToolTipRole,
0965         Qt::DecorationRole,
0966         ProjectItemRole,
0967         ProjectRole,
0968         UrlRole
0969     };
0970     if( allowedRoles.contains(role) && index.isValid() ) {
0971         ProjectBaseItem* item = itemFromIndex( index );
0972         if( item ) {
0973             switch(role) {
0974                 case Qt::DecorationRole:
0975                     return QIcon::fromTheme(item->iconName());
0976                 case Qt::ToolTipRole:
0977                     return item->path().pathOrUrl();
0978                 case Qt::DisplayRole:
0979                     return item->text();
0980                 case ProjectItemRole:
0981                     return QVariant::fromValue<ProjectBaseItem*>(item);
0982                 case UrlRole:
0983                     return item->path().toUrl();
0984                 case ProjectRole:
0985                     return QVariant::fromValue<QObject*>(item->project());
0986             }
0987         }
0988     }
0989     return QVariant();
0990 }
0991 
0992 ProjectModel::ProjectModel( QObject *parent )
0993     : QAbstractItemModel(parent)
0994     , d_ptr(new ProjectModelPrivate(this))
0995 {
0996     Q_D(ProjectModel);
0997 
0998     d->rootItem = new ProjectBaseItem( nullptr, QString(), nullptr );
0999     d->rootItem->setModel( this );
1000 }
1001 
1002 ProjectModel::~ProjectModel()
1003 {
1004     Q_D(ProjectModel);
1005 
1006     d->rootItem->setModel(nullptr);
1007     delete d->rootItem;
1008 }
1009 
1010 
1011 ProjectVisitor::ProjectVisitor()
1012 {
1013 }
1014 
1015 QModelIndex ProjectModel::index( int row, int column, const QModelIndex& parent ) const
1016 {
1017     Q_D(const ProjectModel);
1018 
1019     ProjectBaseItem* parentItem = d->itemFromIndex( parent );
1020     if( parentItem && row >= 0 && row < parentItem->rowCount() && column == 0 ) {
1021         return createIndex( row, column, parentItem );
1022     }
1023     return QModelIndex();
1024 }
1025 
1026 void ProjectModel::appendRow( ProjectBaseItem* item )
1027 {
1028     Q_D(ProjectModel);
1029 
1030     d->rootItem->appendRow( item );
1031 }
1032 
1033 void ProjectModel::removeRow( int row )
1034 {
1035     Q_D(ProjectModel);
1036 
1037     d->rootItem->removeRow( row );
1038 }
1039 
1040 ProjectBaseItem* ProjectModel::takeRow( int row )
1041 {
1042     Q_D(ProjectModel);
1043 
1044     return d->rootItem->takeRow( row );
1045 }
1046 
1047 ProjectBaseItem* ProjectModel::itemAt(int row) const
1048 {
1049     Q_D(const ProjectModel);
1050 
1051     return d->rootItem->child(row);
1052 }
1053 
1054 QList< ProjectBaseItem* > ProjectModel::topItems() const
1055 {
1056     Q_D(const ProjectModel);
1057 
1058     return d->rootItem->children();
1059 }
1060 
1061 Qt::ItemFlags ProjectModel::flags(const QModelIndex& index) const
1062 {
1063     ProjectBaseItem* item = itemFromIndex( index );
1064     if(item)
1065         return item->flags();
1066 
1067     return Qt::NoItemFlags;
1068 }
1069 
1070 bool ProjectModel::insertColumns(int, int, const QModelIndex&)
1071 {
1072     // Not supported
1073     return false;
1074 }
1075 
1076 bool ProjectModel::insertRows(int, int, const QModelIndex&)
1077 {
1078     // Not supported
1079     return false;
1080 }
1081 
1082 bool ProjectModel::setData(const QModelIndex&, const QVariant&, int)
1083 {
1084     // Not supported
1085     return false;
1086 }
1087 
1088 QList<ProjectBaseItem*> ProjectModel::itemsForPath(const IndexedString& path) const
1089 {
1090     Q_D(const ProjectModel);
1091 
1092     return d->pathLookupTable.values(path.index());
1093 }
1094 
1095 ProjectBaseItem* ProjectModel::itemForPath(const IndexedString& path) const
1096 {
1097     Q_D(const ProjectModel);
1098 
1099     return d->pathLookupTable.value(path.index());
1100 }
1101 
1102 void ProjectVisitor::visit( ProjectModel* model )
1103 {
1104     const auto topItems = model->topItems();
1105     for (ProjectBaseItem* item : topItems) {
1106         visit( item->project() );
1107     }
1108 }
1109 
1110 void ProjectVisitor::visit ( IProject* prj )
1111 {
1112     visit( prj->projectItem() );
1113 }
1114 
1115 void ProjectVisitor::visit ( ProjectBuildFolderItem* folder )
1116 {
1117     visit(static_cast<ProjectFolderItem*>(folder));
1118 }
1119 
1120 void ProjectVisitor::visit ( ProjectExecutableTargetItem* exec )
1121 {
1122     const auto fileItems = exec->fileList();
1123     for (ProjectFileItem* item : fileItems) {
1124         visit( item );
1125     }
1126 }
1127 
1128 void ProjectVisitor::visit ( ProjectFolderItem* folder )
1129 {
1130     const auto fileItems = folder->fileList();
1131     for (ProjectFileItem* item : fileItems) {
1132         visit( item );
1133     }
1134     const auto targetItems = folder->targetList();
1135     for (ProjectTargetItem* item : targetItems) {
1136         if( item->type() == ProjectBaseItem::LibraryTarget )
1137         {
1138             visit( dynamic_cast<ProjectLibraryTargetItem*>( item ) );
1139         } else if( item->type() == ProjectBaseItem::ExecutableTarget )
1140         {
1141             visit( dynamic_cast<ProjectExecutableTargetItem*>( item ) );
1142         }
1143     }
1144     const auto folderItems = folder->folderList();
1145     for (ProjectFolderItem* item : folderItems) {
1146         if( item->type() == ProjectBaseItem::BuildFolder )
1147         {
1148             visit( dynamic_cast<ProjectBuildFolderItem*>( item ) );
1149         } else if( item->type() == ProjectBaseItem::Folder )
1150         {
1151             visit( dynamic_cast<ProjectFolderItem*>( item ) );
1152         }
1153     }
1154 }
1155 
1156 void ProjectVisitor::visit ( ProjectFileItem* )
1157 {
1158 }
1159 
1160 void ProjectVisitor::visit ( ProjectLibraryTargetItem* lib )
1161 {
1162     const auto fileItems = lib->fileList();
1163     for (ProjectFileItem* item : fileItems) {
1164         visit( item );
1165     }
1166 }
1167 
1168 ProjectVisitor::~ProjectVisitor()
1169 {
1170 }
1171 
1172 
1173 }
1174 
1175 #include "moc_projectmodel.cpp"