File indexing completed on 2024-05-12 04:39:40

0001 /*
0002     SPDX-FileCopyrightText: 2010 Andreas Pakulat <apaku@gmx.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-or-later
0005 */
0006 
0007 #include "projectpathsmodel.h"
0008 
0009 #include <KLocalizedString>
0010 #include <interfaces/iproject.h>
0011 #include <serialization/indexedstring.h>
0012 #include <util/path.h>
0013 
0014 using namespace KDevelop;
0015 
0016 ProjectPathsModel::ProjectPathsModel( QObject* parent )
0017     : QAbstractListModel( parent )
0018 {
0019 }
0020 
0021 void ProjectPathsModel::setProject(IProject* w_project)
0022 {
0023     project = w_project;
0024 }
0025 
0026 QVariant ProjectPathsModel::data( const QModelIndex& index, int role ) const
0027 {
0028     if( !index.isValid() || index.row() < 0 || index.row() >= rowCount() || index.column() != 0 ) {
0029         return QVariant();
0030     }
0031 
0032     const ConfigEntry& pathConfig = projectPaths.at( index.row() );
0033     switch( role ) {
0034     case IncludesDataRole:
0035         return pathConfig.includes;
0036     case DefinesDataRole:
0037         return QVariant::fromValue(pathConfig.defines);
0038     case Qt::EditRole:
0039         return sanitizePath( pathConfig.path, true, false );
0040     case Qt::DisplayRole: {
0041         const QString& path = pathConfig.path;
0042         return (path == QLatin1String(".")) ? QStringLiteral("(project root)") : path;
0043     }
0044     case FullUrlDataRole:
0045         return QVariant::fromValue(QUrl::fromUserInput( sanitizePath( pathConfig.path, true, false ) ));
0046     case CompilerDataRole:
0047         return QVariant::fromValue(pathConfig.compiler);
0048     case ParserArgumentsRole:
0049         return QVariant::fromValue(pathConfig.parserArguments);
0050     default:
0051         break;
0052     }
0053     return QVariant();
0054 }
0055 
0056 int ProjectPathsModel::rowCount( const QModelIndex& parent ) const
0057 {
0058     if( parent.isValid() ) {
0059         return 0;
0060     }
0061     return projectPaths.count();
0062 }
0063 
0064 bool ProjectPathsModel::setData( const QModelIndex& index, const QVariant& value, int role )
0065 {
0066     if( !index.isValid() || index.row() < 0 || index.row() >= rowCount() || index.column() != 0 ) {
0067         return false;
0068     }
0069 
0070     // Do not allow to change path of the first entry; instead, add a new one in that case
0071     if( index.row() == 0 && ( role == Qt::EditRole || role == Qt::DisplayRole || role == FullUrlDataRole ) ) {
0072         QString addedPath = sanitizePath( value.toString(), false );
0073 
0074         // Do not allow duplicates
0075         for (const ConfigEntry& existingConfig : qAsConst(projectPaths)) {
0076             if( addedPath == existingConfig.path ) {
0077                 return false;
0078             }
0079         }
0080         projectPaths.insert( 1, ConfigEntry(sanitizePath( value.toString(), false ) ));
0081         emit dataChanged( this->index( 1, 0 ), this->index( projectPaths.count() - 1, 0 ) );
0082         return true;
0083     }
0084 
0085     ConfigEntry& pathConfig = projectPaths[ index.row() ];
0086     switch( role ) {
0087     case IncludesDataRole:
0088         pathConfig.includes = value.toStringList();
0089         break;
0090     case DefinesDataRole:
0091         pathConfig.defines = value.value<Defines>();
0092         break;
0093     case Qt::EditRole:
0094         pathConfig.path = sanitizePath( value.toString(), false );
0095         break;
0096     case Qt::DisplayRole:
0097         pathConfig.path = sanitizePath( value.toString(), true );
0098         break;
0099     case FullUrlDataRole:
0100         pathConfig.path = sanitizeUrl(value.toUrl());
0101         break;
0102     case CompilerDataRole:
0103         pathConfig.compiler = value.value<CompilerPointer>();
0104         break;
0105     case ParserArgumentsRole:
0106         pathConfig.parserArguments = value.value<ParserArguments>();
0107         break;
0108     default:
0109         return false;
0110     }
0111     emit dataChanged( index, index );
0112     return true;
0113 }
0114 
0115 Qt::ItemFlags ProjectPathsModel::flags( const QModelIndex& index ) const
0116 {
0117     if( !index.isValid() ) {
0118         return Qt::NoItemFlags;
0119     }
0120 
0121     if( index.row() == 0 ) {
0122         return Qt::ItemFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
0123     }
0124 
0125     return Qt::ItemFlags( Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled );
0126 }
0127 
0128 QVector< ConfigEntry > ProjectPathsModel::paths() const
0129 {
0130     return projectPaths;
0131 }
0132 
0133 void ProjectPathsModel::setPaths(const QVector< ConfigEntry >& paths )
0134 {
0135     beginResetModel();
0136     projectPaths.clear();
0137     for (const ConfigEntry& existingPathConfig : paths) {
0138         // Sanitize the path of loaded config
0139         ConfigEntry config = existingPathConfig;
0140         bool rootPath = config.path == QLatin1String(".") ? true : false;
0141         config.path = sanitizePath(rootPath ? QString() : config.path );
0142         addPathInternal(config, rootPath);
0143     }
0144     addPathInternal( ConfigEntry(sanitizePath( QString() )), true ); // add an empty "root" config entry if one does not exist
0145     endResetModel();
0146 }
0147 
0148 bool ProjectPathsModel::removeRows( int row, int count, const QModelIndex& parent )
0149 {
0150     if( row >= 0 && count > 0 && row < rowCount() ) {
0151         beginRemoveRows( parent, row, row + count - 1 );
0152 
0153         for( int i = 0; i < count; ++i ) {
0154             if( projectPaths.at(row).path == QLatin1String(".") ) {
0155                 continue; // we won't remove the root item
0156             }
0157             projectPaths.removeAt(row);
0158         }
0159 
0160         endRemoveRows();
0161         return true;
0162     }
0163     return false;
0164 }
0165 
0166 void ProjectPathsModel::addPath( const QUrl &url )
0167 {
0168     if( !project->path().isParentOf(KDevelop::Path(url)) ) {
0169         return;
0170     }
0171 
0172     beginInsertRows( QModelIndex(), rowCount(), rowCount() );
0173     addPathInternal( ConfigEntry(sanitizeUrl(url)), false );
0174     endInsertRows();
0175 }
0176 
0177 void ProjectPathsModel::addPathInternal( const ConfigEntry& config, bool prepend )
0178 {
0179     Q_ASSERT(!config.parserArguments.isAnyEmpty());
0180 
0181     // Do not allow duplicates
0182     for (const ConfigEntry& existingConfig : qAsConst(projectPaths)) {
0183         if( config.path == existingConfig.path ) {
0184             return;
0185         }
0186     }
0187     if( prepend ) {
0188         projectPaths.prepend( config );
0189     } else {
0190         projectPaths.append( config );
0191     }
0192 }
0193 
0194 QString ProjectPathsModel::sanitizeUrl( const QUrl& url, bool needRelative ) const
0195 {
0196     Q_ASSERT( project );
0197 
0198     if (needRelative) {
0199         const auto relativePath = project->path().relativePath(KDevelop::Path(url));
0200         return relativePath.isEmpty() ? QStringLiteral(".") : relativePath;
0201     }
0202     return url.adjusted(QUrl::StripTrailingSlash | QUrl::NormalizePathSegments).toString(QUrl::PreferLocalFile);
0203 }
0204 
0205 QString ProjectPathsModel::sanitizePath( const QString& path, bool expectRelative, bool needRelative ) const
0206 {
0207     Q_ASSERT( project );
0208     Q_ASSERT( expectRelative || project->inProject(IndexedString(path)) );
0209 
0210     QUrl url;
0211     if( expectRelative ) {
0212         url = KDevelop::Path(project->path(), path).toUrl();
0213     } else {
0214         url = QUrl::fromUserInput(path);
0215     }
0216     return sanitizeUrl( url, needRelative );
0217 }
0218 
0219 #include "moc_projectpathsmodel.cpp"