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

0001 /*
0002     SPDX-FileCopyrightText: 2009 Niko Sams <niko.sams@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "pathmappings.h"
0008 
0009 #include <debug.h>
0010 
0011 #include <QAbstractTableModel>
0012 #include <QVBoxLayout>
0013 #include <QTableView>
0014 #include <QHeaderView>
0015 #include <QAction>
0016 #include <QFile>
0017 #include <QIcon>
0018 
0019 #include <KLocalizedString>
0020 #include <KConfigGroup>
0021 
0022 namespace {
0023 
0024 static QUrl rebaseMatchingUrl(const QUrl& toRebase, const KConfigGroup& config, const QString& baseEntry, const QString& rebaseEntry)
0025 {
0026     const QUrl::UrlFormattingOption matchOpts = QUrl::NormalizePathSegments;
0027     const auto configGroups = config.groupList();
0028     for (const QString& group : configGroups) {
0029         KConfigGroup pathCfg = config.group(group);
0030         const QString baseStr = pathCfg.readEntry(baseEntry, QUrl()).url(matchOpts);
0031         const QString searchStr = toRebase.url(matchOpts);
0032         if (searchStr.contains(baseStr)) {
0033             const QUrl rebase = pathCfg.readEntry(rebaseEntry, QUrl());
0034             return rebase.resolved(QUrl(searchStr.mid(baseStr.length())));
0035         }
0036     }
0037     //No mapping found
0038     return toRebase;
0039 }
0040 
0041 }
0042 
0043 namespace KDevelop {
0044 
0045 const QString PathMappings::pathMappingsEntry(QStringLiteral("Path Mappings"));
0046 const QString PathMappings::pathMappingRemoteEntry(QStringLiteral("Remote"));
0047 const QString PathMappings::pathMappingLocalEntry(QStringLiteral("Local"));
0048 
0049 QUrl PathMappings::convertToLocalUrl(const KConfigGroup& config, const QUrl& remoteUrl)
0050 {
0051     if (remoteUrl.isLocalFile() && QFile::exists(remoteUrl.toLocalFile())) {
0052         return remoteUrl;
0053     }
0054 
0055     KConfigGroup cfg = config.group(pathMappingsEntry);
0056     return rebaseMatchingUrl(remoteUrl, cfg, pathMappingRemoteEntry, pathMappingLocalEntry);
0057 }
0058 
0059 QUrl PathMappings::convertToRemoteUrl(const KConfigGroup& config, const QUrl& localUrl)
0060 {
0061     KConfigGroup cfg = config.group(pathMappingsEntry);
0062     return rebaseMatchingUrl(localUrl, cfg, pathMappingLocalEntry, pathMappingRemoteEntry);
0063 }
0064 
0065 
0066 class PathMappingModel : public QAbstractTableModel
0067 {
0068     Q_OBJECT
0069 public:
0070     int columnCount(const QModelIndex& parent = QModelIndex()) const override
0071     {
0072         if (parent.isValid()) return 0;
0073         return 2;
0074     }
0075 
0076     int rowCount(const QModelIndex& parent = QModelIndex()) const override
0077     {
0078         if (parent.isValid()) return 0;
0079         return m_paths.count() + 1;
0080     }
0081 
0082     QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override
0083     {
0084         if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
0085             if (section == 0) {
0086                 return i18n("Remote Path");
0087             } else if (section == 1) {
0088                 return i18n("Local Path");
0089             }
0090         }
0091         return QAbstractTableModel::headerData(section, orientation, role);
0092     }
0093 
0094     QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override
0095     {
0096         if (!index.isValid()) return QVariant();
0097         if (index.parent().isValid()) return QVariant();
0098         if (index.column() > 1) return QVariant();
0099         if (index.row() > m_paths.count()) return QVariant();
0100         if (role == Qt::DisplayRole || role == Qt::EditRole) {
0101             if (index.row() == m_paths.count()) return QString();
0102             if (index.column() == 0) {
0103                 return m_paths[index.row()].remote.toDisplayString(QUrl::PreferLocalFile);
0104             } else if (index.column() == 1) {
0105                 return m_paths[index.row()].local.toDisplayString(QUrl::PreferLocalFile);
0106             }
0107         }
0108         return QVariant();
0109     }
0110 
0111     Qt::ItemFlags flags(const QModelIndex& index) const override
0112     {
0113         if (index.parent().isValid()) return Qt::NoItemFlags;
0114         if (!index.isValid()) return Qt::NoItemFlags;
0115         return ( Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled );
0116 
0117     }
0118 
0119     bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override
0120     {
0121         if (!index.isValid()) return false;
0122         if (index.parent().isValid()) return false;
0123         if (index.column() > 1) return false;
0124         if (index.row() > m_paths.count()) return false;
0125         if (role == Qt::EditRole) {
0126             if (index.row() == m_paths.count()) {
0127                 beginInsertRows(QModelIndex(), index.row()+1, index.row()+1);
0128                 m_paths.append(Path());
0129                 endInsertRows();
0130             }
0131             if (index.column() == 0) {
0132                 m_paths[index.row()].remote = QUrl::fromUserInput(value.toString());
0133             } else if (index.column() == 1) {
0134                 m_paths[index.row()].local = QUrl::fromLocalFile(value.toString());
0135             }
0136             emit dataChanged(index, index);
0137             return true;
0138         }
0139         return false;
0140     }
0141 
0142     bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override
0143     {
0144         if (parent.isValid()) return false;
0145         if (row+count > m_paths.count()) return false;
0146         beginRemoveRows(parent, row, row+count-1);
0147         for (int i=0; i<count; ++i) {
0148             qCDebug(DEBUGGER) << row + i;
0149             m_paths.removeAt(row + i);
0150         }
0151         qCDebug(DEBUGGER) << m_paths.count();
0152         endRemoveRows();
0153 
0154         return true;
0155     }
0156 
0157     void loadFromConfiguration(const KConfigGroup &config)
0158     {
0159         beginResetModel();
0160         m_paths.clear();
0161         KConfigGroup cfg = config.group(PathMappings::pathMappingsEntry);
0162         const auto pathCount = cfg.readEntry("Count", 0);
0163         m_paths.reserve(pathCount);
0164         for (int i = 0; i < pathCount; ++i) {
0165             KConfigGroup pCfg = cfg.group(QString::number(i+1));
0166             Path p;
0167             p.remote = pCfg.readEntry(PathMappings::pathMappingRemoteEntry, QUrl());
0168             p.local = pCfg.readEntry(PathMappings::pathMappingLocalEntry, QUrl());
0169             m_paths << p;
0170         }
0171         endResetModel();
0172     }
0173 
0174     void saveToConfiguration(KConfigGroup config) const
0175     {
0176         qCDebug(DEBUGGER) << m_paths.count();
0177 
0178         KConfigGroup cfg = config.group(PathMappings::pathMappingsEntry);
0179         cfg.writeEntry("Count", m_paths.count());
0180         int i=0;
0181         for (const Path& p : m_paths) {
0182             i++;
0183             KConfigGroup pCfg = cfg.group(QString::number(i));
0184             pCfg.writeEntry(PathMappings::pathMappingRemoteEntry, p.remote);
0185             pCfg.writeEntry(PathMappings::pathMappingLocalEntry, p.local);
0186         }
0187         cfg.sync();
0188     }
0189 
0190 private:
0191     struct Path {
0192         QUrl remote;
0193         QUrl local;
0194     };
0195     QVector<Path> m_paths;
0196 };
0197 
0198 
0199 PathMappingsWidget::PathMappingsWidget(QWidget* parent): QWidget(parent)
0200 {
0201     auto *verticalLayout = new QVBoxLayout(this);
0202 
0203     m_pathMappingTable = new QTableView(this);
0204     m_pathMappingTable->setSelectionBehavior(QAbstractItemView::SelectRows);
0205     m_pathMappingTable->horizontalHeader()->setDefaultSectionSize(150);
0206     m_pathMappingTable->horizontalHeader()->setStretchLastSection(true);
0207 
0208     verticalLayout->addWidget(m_pathMappingTable);
0209 
0210     m_pathMappingTable->setModel(new PathMappingModel());
0211     connect(m_pathMappingTable->model(), &QAbstractItemModel::dataChanged, this, &PathMappingsWidget::changed);
0212     connect(m_pathMappingTable->model(), &QAbstractItemModel::rowsRemoved, this, &PathMappingsWidget::changed);
0213     connect(m_pathMappingTable->model(), &QAbstractItemModel::rowsInserted, this, &PathMappingsWidget::changed);
0214 
0215     auto* deletePath = new QAction(
0216         QIcon::fromTheme(QStringLiteral("edit-delete")),
0217         i18n( "Delete" ),
0218         this
0219     );
0220     connect(deletePath, &QAction::triggered, this, &PathMappingsWidget::deletePath);
0221     deletePath->setShortcut(Qt::Key_Delete);
0222     deletePath->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0223     m_pathMappingTable->addAction(deletePath);
0224 
0225 }
0226 
0227 void PathMappingsWidget::deletePath()
0228 {
0229     const auto selectedRows = m_pathMappingTable->selectionModel()->selectedRows();
0230     for (const QModelIndex& i : selectedRows) {
0231         m_pathMappingTable->model()->removeRow(i.row(), i.parent());
0232     }
0233 }
0234 void PathMappingsWidget::loadFromConfiguration(const KConfigGroup& cfg)
0235 {
0236     static_cast<PathMappingModel*>(m_pathMappingTable->model())->loadFromConfiguration(cfg);
0237 }
0238 
0239 void PathMappingsWidget::saveToConfiguration(const KConfigGroup& cfg) const
0240 {
0241     static_cast<PathMappingModel*>(m_pathMappingTable->model())->saveToConfiguration(cfg);
0242 }
0243 
0244 }
0245 
0246 #include "pathmappings.moc"
0247 #include "moc_pathmappings.cpp"