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"