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

0001 /*
0002     SPDX-FileCopyrightText: 2008 Aleix Pol <aleixpol@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "projectitemlineedit.h"
0008 
0009 #include <QAction>
0010 #include <QCompleter>
0011 #include <QDialog>
0012 #include <QDialogButtonBox>
0013 #include <QHeaderView>
0014 #include <QLabel>
0015 #include <QMenu>
0016 #include <QPushButton>
0017 #include <QTreeView>
0018 #include <QValidator>
0019 #include <QVBoxLayout>
0020 
0021 #include <KLocalizedString>
0022 
0023 #include <interfaces/icore.h>
0024 #include <interfaces/iprojectcontroller.h>
0025 #include <project/projectmodel.h>
0026 #include <util/kdevstringhandler.h>
0027 #include <interfaces/iproject.h>
0028 #include "projectproxymodel.h"
0029 
0030 constexpr QChar sep = QLatin1Char('/');
0031 constexpr QChar escape = QLatin1Char('\\');
0032 
0033 
0034 class ProjectItemCompleter : public QCompleter
0035 {
0036     Q_OBJECT
0037 public:
0038     explicit ProjectItemCompleter(QObject* parent=nullptr);
0039 
0040     QString separator() const { return sep; }
0041     QStringList splitPath(const QString &path) const override;
0042     QString pathFromIndex(const QModelIndex& index) const override;
0043 
0044     void setBaseItem( KDevelop::ProjectBaseItem* item ) { mBase = item; }
0045 
0046 private:
0047     KDevelop::ProjectModel* mModel;
0048     KDevelop::ProjectBaseItem* mBase = nullptr;
0049 };
0050 
0051 class ProjectItemValidator : public QValidator
0052 {
0053     Q_OBJECT
0054 public:
0055     explicit ProjectItemValidator(QObject* parent = nullptr );
0056     QValidator::State validate( QString& input, int& pos ) const override;
0057 
0058     void setBaseItem( KDevelop::ProjectBaseItem* item ) { mBase = item; }
0059 
0060 private:
0061     KDevelop::ProjectBaseItem* mBase = nullptr;
0062 };
0063 
0064 ProjectItemCompleter::ProjectItemCompleter(QObject* parent)
0065     : QCompleter(parent)
0066     , mModel(KDevelop::ICore::self()->projectController()->projectModel())
0067 
0068 {
0069     setModel(mModel);
0070     setCaseSensitivity( Qt::CaseInsensitive );
0071 }
0072 
0073 
0074 QStringList ProjectItemCompleter::splitPath(const QString& path) const
0075 {
0076     return joinProjectBasePath( KDevelop::splitWithEscaping( path, sep, escape ), mBase );
0077 }
0078 
0079 QString ProjectItemCompleter::pathFromIndex(const QModelIndex& index) const
0080 {
0081     QString postfix;
0082     if(mModel->itemFromIndex(index)->folder())
0083         postfix=sep;
0084     return KDevelop::joinWithEscaping(removeProjectBasePath( mModel->pathFromIndex(index), mBase ), sep, escape)+postfix;
0085 }
0086 
0087 
0088 ProjectItemValidator::ProjectItemValidator(QObject* parent): QValidator(parent)
0089 {
0090 }
0091 
0092 
0093 QValidator::State ProjectItemValidator::validate(QString& input, int& pos) const
0094 {
0095     Q_UNUSED( pos );
0096     KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel();
0097     QStringList path = joinProjectBasePath( KDevelop::splitWithEscaping( input, sep, escape ), mBase );
0098     QModelIndex idx = model->pathToIndex( path );
0099     QValidator::State state = input.isEmpty() ? QValidator::Intermediate : QValidator::Invalid;
0100     if( idx.isValid() )
0101     {
0102         state = QValidator::Acceptable;
0103     } else if( path.count() > 1 )
0104     {
0105         // Check beginning of path and if that is ok, then try to find a child
0106         QString end = path.takeLast();
0107         idx = model->pathToIndex( path );
0108         if( idx.isValid() )
0109         {
0110             for( int i = 0; i < model->rowCount( idx ); i++ )
0111             {
0112                 if( model->data( model->index( i, 0, idx ) ).toString().startsWith( end, Qt::CaseInsensitive ) )
0113                 {
0114                     state = QValidator::Intermediate;
0115                     break;
0116                 }
0117             }
0118         }
0119     } else if( path.count() == 1 )
0120     {
0121         // Check for a project whose name beings with the input
0122         QString first = path.first();
0123         const auto projects = KDevelop::ICore::self()->projectController()->projects();
0124         bool matchesAnyName = std::any_of(projects.begin(), projects.end(), [&](KDevelop::IProject* project) {
0125             return (project->name().startsWith(first, Qt::CaseInsensitive));
0126         });
0127         if (matchesAnyName) {
0128             state = QValidator::Intermediate;
0129         }
0130     }
0131     return state;
0132 }
0133 
0134 class ProjectItemLineEditPrivate
0135 {
0136 public:
0137     explicit ProjectItemLineEditPrivate(ProjectItemLineEdit* q)
0138         : completer(new ProjectItemCompleter(q))
0139         , validator(new ProjectItemValidator(q))
0140     {
0141     }
0142     KDevelop::ProjectBaseItem* base = nullptr;
0143     ProjectItemCompleter* completer;
0144     ProjectItemValidator* validator;
0145     KDevelop::IProject* suggestion = nullptr;
0146 };
0147 
0148 ProjectItemLineEdit::ProjectItemLineEdit(QWidget* parent)
0149     : QLineEdit(parent),
0150       d_ptr(new ProjectItemLineEditPrivate(this))
0151 {
0152     Q_D(ProjectItemLineEdit);
0153 
0154     setCompleter(d->completer);
0155     setValidator(d->validator);
0156     setPlaceholderText( i18nc("@info:placeholder", "Enter the path to an item from the projects tree..." ) );
0157 
0158     auto* selectItemAction = new QAction(QIcon::fromTheme(QStringLiteral("folder-document")), i18nc("@action", "Select..."), this);
0159     connect(selectItemAction, &QAction::triggered, this, &ProjectItemLineEdit::selectItemDialog);
0160     addAction(selectItemAction);
0161 
0162     setContextMenuPolicy(Qt::CustomContextMenu);
0163     connect(this, &ProjectItemLineEdit::customContextMenuRequested, this, &ProjectItemLineEdit::showCtxMenu);
0164 }
0165 
0166 ProjectItemLineEdit::~ProjectItemLineEdit() = default;
0167 
0168 void ProjectItemLineEdit::showCtxMenu(const QPoint& p)
0169 {
0170     QScopedPointer<QMenu> menu(createStandardContextMenu());
0171     menu->addActions(actions());
0172     menu->exec(mapToGlobal(p));
0173 }
0174 
0175 bool ProjectItemLineEdit::selectItemDialog()
0176 {
0177     Q_D(ProjectItemLineEdit);
0178 
0179     KDevelop::ProjectModel* model=KDevelop::ICore::self()->projectController()->projectModel();
0180 
0181     QDialog dialog;
0182     dialog.setWindowTitle(i18nc("@title:window", "Select an Item"));
0183 
0184     auto mainLayout = new QVBoxLayout(&dialog);
0185 
0186     auto* view = new QTreeView(&dialog);
0187     auto* proxymodel = new ProjectProxyModel(view);
0188     proxymodel->setSourceModel(model);
0189     view->header()->hide();
0190     view->setModel(proxymodel);
0191     view->setSelectionMode(QAbstractItemView::SingleSelection);
0192     mainLayout->addWidget(new QLabel(i18n("Select the item you want to get the path from.")));
0193     mainLayout->addWidget(view);
0194 
0195     auto* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
0196     QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
0197     okButton->setDefault(true);
0198     okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
0199     connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
0200     connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
0201     mainLayout->addWidget(buttonBox);
0202 
0203     if (d->suggestion) {
0204         const QModelIndex idx = proxymodel->proxyIndexFromItem(d->suggestion->projectItem());
0205         view->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect);
0206     }
0207 
0208     int res = dialog.exec();
0209 
0210     if(res==QDialog::Accepted && view->selectionModel()->hasSelection()) {
0211         QModelIndex idx=proxymodel->mapToSource(view->selectionModel()->selectedIndexes().first());
0212 
0213         setText(KDevelop::joinWithEscaping(model->pathFromIndex(idx), sep, escape));
0214         selectAll();
0215         return true;
0216     }
0217     return false;
0218 }
0219 
0220 void ProjectItemLineEdit::setItemPath(const QStringList& list)
0221 {
0222     Q_D(ProjectItemLineEdit);
0223 
0224     setText(KDevelop::joinWithEscaping(removeProjectBasePath(list, d->base), sep, escape));
0225 }
0226 
0227 QStringList ProjectItemLineEdit::itemPath() const
0228 {
0229     Q_D(const ProjectItemLineEdit);
0230 
0231     return joinProjectBasePath(KDevelop::splitWithEscaping(text(), sep, escape), d->base);
0232 }
0233 
0234 void ProjectItemLineEdit::setBaseItem(KDevelop::ProjectBaseItem* item)
0235 {
0236     Q_D(ProjectItemLineEdit);
0237 
0238     d->base = item;
0239     d->validator->setBaseItem(d->base);
0240     d->completer->setBaseItem(d->base);
0241 }
0242 
0243 KDevelop::ProjectBaseItem* ProjectItemLineEdit::baseItem() const
0244 {
0245     Q_D(const ProjectItemLineEdit);
0246 
0247     return d->base;
0248 }
0249 
0250 KDevelop::ProjectBaseItem* ProjectItemLineEdit::currentItem() const
0251 {
0252     KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel();
0253     return model->itemFromIndex(model->pathToIndex(KDevelop::splitWithEscaping(text(), QLatin1Char('/'), QLatin1Char('\\'))));
0254 }
0255 
0256 void ProjectItemLineEdit::setSuggestion(KDevelop::IProject* project)
0257 {
0258     Q_D(ProjectItemLineEdit);
0259 
0260     d->suggestion = project;
0261 }
0262 
0263 #include "projectitemlineedit.moc"
0264 #include "moc_projectitemlineedit.cpp"