File indexing completed on 2024-04-28 04:39:08

0001 /*
0002     SPDX-FileCopyrightText: 2008 Alexander Dymo <adymo@kdevelop.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "projectfilterconfigpage.h"
0008 
0009 #include <QKeyEvent>
0010 
0011 #include <KMessageWidget>
0012 #include <KLocalizedString>
0013 
0014 #include <interfaces/iproject.h>
0015 #include <project/projectmodel.h>
0016 
0017 #include "ui_projectfiltersettings.h"
0018 
0019 #include <debug.h>
0020 #include "filtermodel.h"
0021 #include "comboboxdelegate.h"
0022 #include "projectfilterprovider.h"
0023 
0024 using namespace KDevelop;
0025 
0026 ProjectFilterConfigPage::ProjectFilterConfigPage(ProjectFilterProvider* provider, const ProjectConfigOptions& options, QWidget* parent)
0027     : ProjectConfigPage<ProjectFilterSettings>(provider, options, parent)
0028     , m_model(new FilterModel(this))
0029     , m_projectFilterProvider(provider)
0030     , m_ui(new Ui::ProjectFilterSettings)
0031 {
0032     m_ui->setupUi(this);
0033 
0034     m_ui->messageWidget->hide();
0035 
0036     m_ui->filters->setSelectionMode(QAbstractItemView::SingleSelection);
0037     m_ui->filters->setModel(m_model);
0038     m_ui->filters->setRootIsDecorated(false);
0039     m_ui->filters->header()->setSectionResizeMode(FilterModel::Pattern, QHeaderView::Stretch);
0040     m_ui->filters->header()->setSectionResizeMode(FilterModel::Targets, QHeaderView::ResizeToContents);
0041     m_ui->filters->header()->setSectionResizeMode(FilterModel::Inclusive, QHeaderView::ResizeToContents);
0042     m_ui->filters->setItemDelegateForColumn(FilterModel::Targets,
0043         new ComboBoxDelegate(QVector<ComboBoxDelegate::Item>{
0044                 {i18nc("@item", "Files"),             static_cast<int>(Filter::Files)},
0045                 {i18nc("@item", "Folders"),           static_cast<int>(Filter::Folders)},
0046                 {i18nc("@item", "Files and Folders"), static_cast<int>(Filter::Folders | Filter::Files)}}
0047             , this));
0048     m_ui->filters->setItemDelegateForColumn(FilterModel::Inclusive,
0049         new ComboBoxDelegate(QVector<ComboBoxDelegate::Item>{
0050                 {i18nc("@item", "Exclude"), false},
0051                 {i18nc("@item", "Include"), true}}
0052             , this));
0053     m_ui->filters->installEventFilter(this);
0054     m_ui->filters->setDragEnabled(true);
0055     m_ui->filters->setDragDropMode(QAbstractItemView::InternalMove);
0056     m_ui->filters->setAutoScroll(true);
0057 
0058     reset();
0059     selectionChanged();
0060 
0061     connect(m_ui->filters->selectionModel(), &QItemSelectionModel::currentChanged,
0062             this, &ProjectFilterConfigPage::selectionChanged);
0063     connect(this, &ProjectFilterConfigPage::changed, this, &ProjectFilterConfigPage::selectionChanged);
0064 
0065     connect(m_model, &FilterModel::modelReset, this, &ProjectFilterConfigPage::checkFiltersAndEmitChanged);
0066     connect(m_model, &FilterModel::dataChanged, this, &ProjectFilterConfigPage::checkFiltersAndEmitChanged);
0067     connect(m_model, &FilterModel::rowsRemoved, this, &ProjectFilterConfigPage::checkFiltersAndEmitChanged);
0068     // Don't recheck filters when a row is:
0069     // 1. Inserted - because existing errors remain. And if there were no errors, a new
0070     //    empty-pattern error would be shown - annoyingly and uselessly - on each insertion, before
0071     //    the user had a chance to enter a new pattern.
0072     // 2. Moved - because neither existing errors disappear nor new ones appear after this operation.
0073     //    When there are multiple errors, the row order determines which error message is shown.
0074     //    But the message choice in this corner case is not important enough to recheck all filters.
0075     connect(m_model, &FilterModel::rowsInserted, this, &ProjectFilterConfigPage::changed);
0076     connect(m_model, &FilterModel::rowsMoved, this, &ProjectFilterConfigPage::changed);
0077 
0078     connect(m_ui->add, &QPushButton::clicked, this, &ProjectFilterConfigPage::add);
0079     connect(m_ui->remove, &QPushButton::clicked, this, &ProjectFilterConfigPage::remove);
0080     connect(m_ui->moveUp, &QPushButton::clicked, this, &ProjectFilterConfigPage::moveUp);
0081     connect(m_ui->moveDown, &QPushButton::clicked, this, &ProjectFilterConfigPage::moveDown);
0082 }
0083 
0084 ProjectFilterConfigPage::~ProjectFilterConfigPage()
0085 {
0086 }
0087 
0088 void ProjectFilterConfigPage::apply()
0089 {
0090     ProjectConfigPage::apply();
0091     writeFilters(m_model->filters(), project()->projectConfiguration());
0092     m_projectFilterProvider->updateProjectFilters(project());
0093 }
0094 
0095 void ProjectFilterConfigPage::reset()
0096 {
0097     ProjectConfigPage::reset();
0098     m_model->setFilters(readFilters(project()->projectConfiguration()));
0099 }
0100 
0101 void ProjectFilterConfigPage::defaults()
0102 {
0103     ProjectConfigPage::defaults();
0104     m_model->setFilters(defaultFilters());
0105 }
0106 
0107 bool ProjectFilterConfigPage::eventFilter(QObject* object, QEvent* event)
0108 {
0109     if (object == m_ui->filters && event->type() == QEvent::KeyRelease) {
0110         auto* key = static_cast<QKeyEvent*>(event);
0111         if (key->key() == Qt::Key_Delete && key->modifiers() == Qt::NoModifier && m_ui->filters->currentIndex().isValid()) {
0112             // workaround https://bugs.kde.org/show_bug.cgi?id=324451
0113             // there is no other way I see to figure out whether an editor is showing...
0114             auto* editor = m_ui->filters->viewport()->findChild<QWidget*>();
0115             if (!editor || !editor->isVisible()) {
0116                 // editor is not showing
0117                 remove();
0118                 return true; // eat event
0119             }
0120         }
0121     }
0122 
0123     return ProjectConfigPage::eventFilter(object, event);
0124 }
0125 
0126 void ProjectFilterConfigPage::selectionChanged()
0127 {
0128     bool hasSelection = m_ui->filters->currentIndex().isValid();
0129     int row = -1;
0130     if (hasSelection) {
0131         row = m_ui->filters->currentIndex().row();
0132     }
0133     m_ui->remove->setEnabled(hasSelection);
0134 
0135     m_ui->moveDown->setEnabled(hasSelection && row != m_model->rowCount() - 1);
0136     m_ui->moveUp->setEnabled(hasSelection && row != 0);
0137 }
0138 
0139 void ProjectFilterConfigPage::add()
0140 {
0141     m_model->insertRows(m_model->rowCount(), 1);
0142     const QModelIndex index = m_model->index(m_model->rowCount() - 1, FilterModel::Pattern, QModelIndex());
0143     m_ui->filters->setCurrentIndex(index);
0144     m_ui->filters->edit(index);
0145 }
0146 
0147 void ProjectFilterConfigPage::remove()
0148 {
0149     Q_ASSERT(m_ui->filters->currentIndex().isValid());
0150     m_model->removeRows(m_ui->filters->currentIndex().row(), 1);
0151 }
0152 
0153 void ProjectFilterConfigPage::moveUp()
0154 {
0155     Q_ASSERT(m_ui->filters->currentIndex().isValid());
0156     m_model->moveFilterUp(m_ui->filters->currentIndex().row());
0157 }
0158 
0159 void ProjectFilterConfigPage::moveDown()
0160 {
0161     Q_ASSERT(m_ui->filters->currentIndex().isValid());
0162     m_model->moveFilterDown(m_ui->filters->currentIndex().row());
0163 }
0164 
0165 void ProjectFilterConfigPage::checkFilters()
0166 {
0167     // check for errors, only show one error at once
0168     QString errorText;
0169     const auto filters = m_model->filters();
0170     for (const auto& filter : filters) {
0171         if (filter.pattern.isEmpty()) {
0172             errorText = i18n("A filter with an empty pattern will match all items. Use <code>\"*\"</code> to make this explicit.");
0173             break;
0174         } else if (filter.pattern.endsWith(QLatin1Char('/')) && filter.targets == Filter::Files) {
0175             errorText = i18n("A filter ending on <code>\"/\"</code> can never match a file.");
0176             break;
0177         }
0178     }
0179 
0180     if (!errorText.isEmpty()) {
0181         m_ui->messageWidget->setMessageType(KMessageWidget::Error);
0182         m_ui->messageWidget->setText(errorText);
0183         m_ui->messageWidget->animatedShow();
0184     } else {
0185         m_ui->messageWidget->animatedHide();
0186     }
0187 }
0188 
0189 void ProjectFilterConfigPage::checkFiltersAndEmitChanged()
0190 {
0191     checkFilters();
0192 
0193     emit changed();
0194 }
0195 
0196 QString ProjectFilterConfigPage::fullName() const
0197 {
0198     return i18nc("@title:tab", "Configure Project Filter");
0199 }
0200 
0201 QIcon ProjectFilterConfigPage::icon() const
0202 {
0203     return QIcon::fromTheme(QStringLiteral("view-filter"));
0204 }
0205 
0206 QString ProjectFilterConfigPage::name() const
0207 {
0208     return i18nc("@title:tab", "Project Filter");
0209 }
0210 
0211 #include "moc_projectfilterconfigpage.cpp"