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"