diff --git a/plugins/problemreporter/problemsview.cpp b/plugins/problemreporter/problemsview.cpp index 9c6f073b7f..c174e7eac8 100644 --- a/plugins/problemreporter/problemsview.cpp +++ b/plugins/problemreporter/problemsview.cpp @@ -1,523 +1,530 @@ /* * Copyright 2015 Laszlo Kis-Adam * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemsview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "problemtreeview.h" #include "problemmodel.h" namespace KDevelop { void ProblemsView::setupActions() { { m_fullUpdateAction = new QAction(this); m_fullUpdateAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_fullUpdateAction->setText(i18n("Force Full Update")); - m_fullUpdateAction->setToolTip(i18nc("@info:tooltip", "Re-parse all watched documents")); m_fullUpdateAction->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect(m_fullUpdateAction, &QAction::triggered, this, [this]() { currentView()->model()->forceFullUpdate(); }); addAction(m_fullUpdateAction); } { m_scopeMenu = new KActionMenu(this); m_scopeMenu->setDelayed(false); m_scopeMenu->setToolTip(i18nc("@info:tooltip", "Which files to display the problems for")); m_scopeMenu->setObjectName(QStringLiteral("scopeMenu")); QActionGroup* scopeActions = new QActionGroup(this); m_currentDocumentAction = new QAction(this); m_currentDocumentAction->setText(i18n("Current Document")); m_currentDocumentAction->setToolTip(i18nc("@info:tooltip", "Display problems in current document")); QAction* openDocumentsAction = new QAction(this); openDocumentsAction->setText(i18n("Open Documents")); openDocumentsAction->setToolTip(i18nc("@info:tooltip", "Display problems in all open documents")); QAction* currentProjectAction = new QAction(this); currentProjectAction->setText(i18n("Current Project")); currentProjectAction->setToolTip(i18nc("@info:tooltip", "Display problems in current project")); QAction* allProjectAction = new QAction(this); allProjectAction->setText(i18n("All Projects")); allProjectAction->setToolTip(i18nc("@info:tooltip", "Display problems in all projects")); QVector actions; actions.push_back(m_currentDocumentAction); actions.push_back(openDocumentsAction); actions.push_back(currentProjectAction); actions.push_back(allProjectAction); m_showAllAction = new QAction(this); m_showAllAction->setText(i18n("Show All")); m_showAllAction->setToolTip(i18nc("@info:tooltip", "Display ALL problems")); actions.push_back(m_showAllAction); foreach (QAction* action, actions) { action->setCheckable(true); scopeActions->addAction(action); m_scopeMenu->addAction(action); } addAction(m_scopeMenu); QSignalMapper* scopeMapper = new QSignalMapper(this); scopeMapper->setMapping(m_currentDocumentAction, CurrentDocument); scopeMapper->setMapping(openDocumentsAction, OpenDocuments); scopeMapper->setMapping(currentProjectAction, CurrentProject); scopeMapper->setMapping(allProjectAction, AllProjects); connect(m_currentDocumentAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(openDocumentsAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(currentProjectAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(allProjectAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); { scopeMapper->setMapping(actions.last(), BypassScopeFilter); connect(actions.last(), &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); } connect(scopeMapper, static_cast(&QSignalMapper::mapped), this, [this](int index) { setScope(index); }); } { m_showImportsAction = new QAction(this); addAction(m_showImportsAction); m_showImportsAction->setCheckable(true); m_showImportsAction->setChecked(false); m_showImportsAction->setText(i18n("Show Imports")); m_showImportsAction->setToolTip(i18nc("@info:tooltip", "Display problems in imported files")); connect(m_showImportsAction, &QAction::triggered, this, [this](bool checked) { currentView()->model()->setShowImports(checked); }); } { m_severityActions = new QActionGroup(this); m_errorSeverityAction = new QAction(this); m_errorSeverityAction->setToolTip(i18nc("@info:tooltip", "Display errors")); m_errorSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-error"))); m_errorSeverityAction->setIconText(i18n("Show Errors")); m_warningSeverityAction = new QAction(this); m_warningSeverityAction->setToolTip(i18nc("@info:tooltip", "Display warnings")); m_warningSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); m_warningSeverityAction->setIconText(i18n("Show Warnings")); m_hintSeverityAction = new QAction(this); m_hintSeverityAction->setToolTip(i18nc("@info:tooltip", "Display hints")); m_hintSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); m_hintSeverityAction->setIconText(i18n("Show Hints")); QAction* severityActionArray[] = { m_errorSeverityAction, m_warningSeverityAction, m_hintSeverityAction }; for (int i = 0; i < 3; ++i) { severityActionArray[i]->setCheckable(true); m_severityActions->addAction(severityActionArray[i]); addAction(severityActionArray[i]); } m_severityActions->setExclusive(false); m_hintSeverityAction->setChecked(true); m_warningSeverityAction->setChecked(true); m_errorSeverityAction->setChecked(true); connect(m_errorSeverityAction, &QAction::toggled, this, &ProblemsView::handleSeverityActionToggled); connect(m_warningSeverityAction, &QAction::toggled, this, &ProblemsView::handleSeverityActionToggled); connect(m_hintSeverityAction, &QAction::toggled, this, &ProblemsView::handleSeverityActionToggled); } { m_groupingMenu = new KActionMenu(i18n("Grouping"), this); m_groupingMenu->setDelayed(false); QActionGroup* groupingActions = new QActionGroup(this); QAction* noGroupingAction = new QAction(i18n("None"), this); QAction* pathGroupingAction = new QAction(i18n("Path"), this); QAction* severityGroupingAction = new QAction(i18n("Severity"), this); QAction* groupingActionArray[] = { noGroupingAction, pathGroupingAction, severityGroupingAction }; for (unsigned i = 0; i < sizeof(groupingActionArray) / sizeof(QAction*); ++i) { QAction* action = groupingActionArray[i]; action->setCheckable(true); groupingActions->addAction(action); m_groupingMenu->addAction(action); } addAction(m_groupingMenu); noGroupingAction->setChecked(true); QSignalMapper* groupingMapper = new QSignalMapper(this); groupingMapper->setMapping(noGroupingAction, NoGrouping); groupingMapper->setMapping(pathGroupingAction, PathGrouping); groupingMapper->setMapping(severityGroupingAction, SeverityGrouping); connect(noGroupingAction, &QAction::triggered, groupingMapper, static_cast(&QSignalMapper::map)); connect(pathGroupingAction, &QAction::triggered, groupingMapper, static_cast(&QSignalMapper::map)); connect(severityGroupingAction, &QAction::triggered, groupingMapper, static_cast(&QSignalMapper::map)); connect(groupingMapper, static_cast(&QSignalMapper::mapped), this, [this](int index) { currentView()->model()->setGrouping(index); }); } { QTimer* filterTimer = new QTimer(this); filterTimer->setSingleShot(true); connect(filterTimer, &QTimer::timeout, this, [this]() { setFilter(m_filterEdit->text()); }); m_filterEdit = new QLineEdit(this); m_filterEdit->setClearButtonEnabled(true); m_filterEdit->setPlaceholderText(i18n("Search...")); QSizePolicy p(m_filterEdit->sizePolicy()); p.setHorizontalPolicy(QSizePolicy::Fixed); m_filterEdit->setSizePolicy(p); connect(m_filterEdit, &QLineEdit::textChanged, this, [filterTimer](const QString&) { filterTimer->start(500); }); QWidgetAction* filterAction = new QWidgetAction(this); filterAction->setDefaultWidget(m_filterEdit); addAction(filterAction); m_prevTabIdx = -1; setFocusProxy(m_filterEdit); } } void ProblemsView::updateActions() { auto problemModel = currentView()->model(); Q_ASSERT(problemModel); m_fullUpdateAction->setVisible(problemModel->features().testFlag(ProblemModel::CanDoFullUpdate)); + m_fullUpdateAction->setToolTip(problemModel->fullUpdateTooltip()); m_showImportsAction->setVisible(problemModel->features().testFlag(ProblemModel::CanShowImports)); m_scopeMenu->setVisible(problemModel->features().testFlag(ProblemModel::ScopeFilter)); m_severityActions->setVisible(problemModel->features().testFlag(ProblemModel::SeverityFilter)); m_groupingMenu->setVisible(problemModel->features().testFlag(ProblemModel::Grouping)); m_showAllAction->setVisible(problemModel->features().testFlag(ProblemModel::CanByPassScopeFilter)); m_showImportsAction->setChecked(false); problemModel->setShowImports(false); // Show All should be default if it's supported. It helps with error messages that are otherwise invisible if (problemModel->features().testFlag(ProblemModel::CanByPassScopeFilter)) { //actions.last()->setChecked(true); setScope(BypassScopeFilter); } else { m_currentDocumentAction->setChecked(true); setScope(CurrentDocument); } problemModel->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); setFocus(); // set focus to default widget (filterEdit) } /// TODO: Move to util? /// Note: Support for recursing into child indices would be nice class ItemViewWalker { public: explicit ItemViewWalker(QItemSelectionModel* itemView); void selectNextIndex(); void selectPreviousIndex(); enum Direction { NextIndex, PreviousIndex }; void selectIndex(Direction direction); private: QItemSelectionModel* m_selectionModel; }; ItemViewWalker::ItemViewWalker(QItemSelectionModel* itemView) : m_selectionModel(itemView) { } void ItemViewWalker::selectNextIndex() { selectIndex(NextIndex); } void ItemViewWalker::selectPreviousIndex() { selectIndex(PreviousIndex); } void ItemViewWalker::selectIndex(Direction direction) { if (!m_selectionModel) { return; } const QModelIndexList list = m_selectionModel->selectedRows(); const QModelIndex currentIndex = list.value(0); if (!currentIndex.isValid()) { /// no selection yet, just select the first const QModelIndex firstIndex = m_selectionModel->model()->index(0, 0); m_selectionModel->setCurrentIndex(firstIndex, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); return; } const int nextRow = currentIndex.row() + (direction == NextIndex ? 1 : -1); const QModelIndex nextIndex = currentIndex.sibling(nextRow, 0); if (!nextIndex.isValid()) { return; /// never invalidate the selection } m_selectionModel->setCurrentIndex(nextIndex, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); } ProblemsView::ProblemsView(QWidget* parent) : QWidget(parent) { setWindowTitle(i18n("Problems")); setWindowIcon(QIcon::fromTheme(QStringLiteral("script-error"), windowIcon())); auto layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); m_tabWidget = new QTabWidget(this); m_tabWidget->setTabPosition(QTabWidget::South); layout->addWidget(m_tabWidget); setupActions(); } ProblemsView::~ProblemsView() { } void ProblemsView::load() { m_tabWidget->clear(); KDevelop::ProblemModelSet* pms = KDevelop::ICore::self()->languageController()->problemModelSet(); QVector v = pms->models(); QVectorIterator itr(v); while (itr.hasNext()) { const KDevelop::ModelData& data = itr.next(); addModel(data); } connect(pms, &ProblemModelSet::added, this, &ProblemsView::onModelAdded); connect(pms, &ProblemModelSet::removed, this, &ProblemsView::onModelRemoved); connect(m_tabWidget, &QTabWidget::currentChanged, this, &ProblemsView::onCurrentChanged); if (m_tabWidget->currentIndex() == 0) { updateActions(); return; } m_tabWidget->setCurrentIndex(0); } void ProblemsView::onModelAdded(const ModelData& data) { addModel(data); } void ProblemsView::showModel(const QString& id) { for (int i = 0; i < m_models.size(); ++i) { if (m_models[i].id == id) { m_tabWidget->setCurrentIndex(i); return; } } } void ProblemsView::onModelRemoved(const QString& id) { for (int i = 0; i < m_models.size(); ++i) { if (m_models[i].id == id) { m_models.remove(i); QWidget* w = m_tabWidget->widget(i); m_tabWidget->removeTab(i); delete w; return; } } } void ProblemsView::onCurrentChanged(int idx) { if (idx == -1) return; setFilter(QStringLiteral(""), m_prevTabIdx); setFilter(QStringLiteral("")); m_prevTabIdx = idx; updateActions(); } void ProblemsView::onViewChanged() { ProblemTreeView* view = static_cast(sender()); int idx = m_tabWidget->indexOf(view); int rows = view->model()->rowCount(); updateTab(idx, rows); } void ProblemsView::addModel(const ModelData& newData) { // We implement follows tabs order: // // 1) First tab always used by "Parser" model due to it's the most important // problem listing, it should be at the front (K.Funk idea at #kdevelop IRC channel). // // 2) Other tabs are alphabetically ordered. static const QString parserId = QStringLiteral("Parser"); - ProblemTreeView* view = new ProblemTreeView(nullptr, newData.model); + auto model = newData.model; + auto view = new ProblemTreeView(nullptr, model); connect(view, &ProblemTreeView::changed, this, &ProblemsView::onViewChanged); + connect(model, &ProblemModel::fullUpdateTooltipChanged, + this, [this, model]() { + if (currentView()->model() == model) { + m_fullUpdateAction->setToolTip(model->fullUpdateTooltip()); + } + }); int insertIdx = 0; if (newData.id != parserId) { for (insertIdx = 0; insertIdx < m_models.size(); ++insertIdx) { const ModelData& currentData = m_models[insertIdx]; // Skip first element if it's already occupied by "Parser" model if (insertIdx == 0 && currentData.id == parserId) continue; if (currentData.name.localeAwareCompare(newData.name) > 0) break; } } m_tabWidget->insertTab(insertIdx, view, newData.name); m_models.insert(insertIdx, newData); - updateTab(insertIdx, view->model()->rowCount()); + updateTab(insertIdx, model->rowCount()); } void ProblemsView::updateTab(int idx, int rows) { if (idx < 0 || idx >= m_models.size()) return; const QString name = m_models[idx].name; const QString tabText = i18nc("%1: tab name, %2: number of problems", "%1 (%2)", name, rows); m_tabWidget->setTabText(idx, tabText); } ProblemTreeView* ProblemsView::currentView() const { return qobject_cast(m_tabWidget->currentWidget()); } void ProblemsView::selectNextItem() { auto view = currentView(); if (view) { ItemViewWalker walker(view->selectionModel()); walker.selectNextIndex(); view->openDocumentForCurrentProblem(); } } void ProblemsView::selectPreviousItem() { auto view = currentView(); if (view) { ItemViewWalker walker(view->selectionModel()); walker.selectPreviousIndex(); view->openDocumentForCurrentProblem(); } } void ProblemsView::handleSeverityActionToggled() { currentView()->model()->setSeverities( (m_errorSeverityAction->isChecked() ? IProblem::Error : IProblem::Severities()) | (m_warningSeverityAction->isChecked() ? IProblem::Warning : IProblem::Severities()) | (m_hintSeverityAction->isChecked() ? IProblem::Hint : IProblem::Severities()) ); } void ProblemsView::setScope(int scope) { m_scopeMenu->setText(i18n("Scope: %1", m_scopeMenu->menu()->actions().at(scope)->text())); currentView()->model()->setScope(scope); } void ProblemsView::setFilter(const QString& filterText) { setFilter(filterText, m_tabWidget->currentIndex()); } void ProblemsView::setFilter(const QString& filterText, int tabIdx) { if (tabIdx < 0 || tabIdx >= m_tabWidget->count()) return; ProblemTreeView* view = static_cast(m_tabWidget->widget(tabIdx)); int rows = view->setFilter(filterText); updateTab(tabIdx, rows); if (tabIdx == m_tabWidget->currentIndex()) { QSignalBlocker blocker(m_filterEdit); m_filterEdit->setText(filterText); } } } diff --git a/shell/problemmodel.cpp b/shell/problemmodel.cpp index c43ce3c9c6..7b3f867fd0 100644 --- a/shell/problemmodel.cpp +++ b/shell/problemmodel.cpp @@ -1,372 +1,389 @@ /* * KDevelop Problem Reporter * * Copyright 2007 Hamish Rodda * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { QIcon iconForSeverity(KDevelop::IProblem::Severity severity) { switch (severity) { case KDevelop::IProblem::Hint: return QIcon::fromTheme(QStringLiteral("dialog-information")); case KDevelop::IProblem::Warning: return QIcon::fromTheme(QStringLiteral("dialog-warning")); case KDevelop::IProblem::Error: return QIcon::fromTheme(QStringLiteral("dialog-error")); case KDevelop::IProblem::NoSeverity: return {}; } return {}; } } struct ProblemModelPrivate { explicit ProblemModelPrivate(KDevelop::ProblemStore *store) : m_problems(store) , m_features(KDevelop::ProblemModel::NoFeatures) + , m_fullUpdateTooltip(i18nc("@info:tooltip", "Re-parse all watched documents")) { } QScopedPointer m_problems; KDevelop::ProblemModel::Features m_features; + QString m_fullUpdateTooltip; }; namespace KDevelop { ProblemModel::ProblemModel(QObject * parent, ProblemStore *store) : QAbstractItemModel(parent) , d(new ProblemModelPrivate(store)) { if (!d->m_problems) { d->m_problems.reset(new FilteredProblemStore()); d->m_features = ScopeFilter | SeverityFilter | Grouping | CanByPassScopeFilter; } setScope(CurrentDocument); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ProblemModel::setCurrentDocument); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &ProblemModel::closedDocument); /// CompletionSettings include a list of todo markers we care for, so need to update connect(ICore::self()->languageController()->completionSettings(), &ICompletionSettings::settingsChanged, this, &ProblemModel::forceFullUpdate); if (ICore::self()->documentController()->activeDocument()) { setCurrentDocument(ICore::self()->documentController()->activeDocument()); } connect(d->m_problems.data(), &ProblemStore::beginRebuild, this, &ProblemModel::onBeginRebuild); connect(d->m_problems.data(), &ProblemStore::endRebuild, this, &ProblemModel::onEndRebuild); connect(d->m_problems.data(), &ProblemStore::problemsChanged, this, &ProblemModel::problemsChanged); } ProblemModel::~ ProblemModel() { } int ProblemModel::rowCount(const QModelIndex& parent) const { if (!parent.isValid()) { return d->m_problems->count(); } else { return d->m_problems->count(reinterpret_cast(parent.internalPointer())); } } static QString displayUrl(const QUrl &url, const QUrl &base) { if (base.isParentOf(url)) { return url.toDisplayString(QUrl::PreferLocalFile).mid(base.toDisplayString(QUrl::PreferLocalFile).length()); } else { return ICore::self()->projectController()->prettyFileName(url, IProjectController::FormatPlain); } } QVariant ProblemModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) return QVariant(); QUrl baseDirectory = d->m_problems->currentDocument().toUrl().adjusted(QUrl::RemoveFilename); IProblem::Ptr p = problemForIndex(index); if (!p.constData()) { if (role == Qt::DisplayRole && index.column() == Error) { ProblemStoreNode *node = reinterpret_cast(index.internalPointer()); if (node) { return node->label(); } } return {}; } if (role == SeverityRole) { return p->severity(); } else if (role == ProblemRole) { return QVariant::fromValue(p); } switch (role) { case Qt::DisplayRole: switch (index.column()) { case Source: return p->sourceString(); case Error: return p->description(); case File: return displayUrl(p->finalLocation().document.toUrl().adjusted(QUrl::NormalizePathSegments), baseDirectory); case Line: if (p->finalLocation().isValid()) { return QString::number(p->finalLocation().start().line() + 1); } break; case Column: if (p->finalLocation().isValid()) { return QString::number(p->finalLocation().start().column() + 1); } break; } break; case Qt::DecorationRole: if (index.column() == Error) { return iconForSeverity(p->severity()); } break; case Qt::ToolTipRole: return p->explanation(); default: break; } return {}; } QModelIndex ProblemModel::parent(const QModelIndex& index) const { ProblemStoreNode *node = reinterpret_cast(index.internalPointer()); if (!node) { return {}; } ProblemStoreNode *parent = node->parent(); if (!parent || parent->isRoot()) { return {}; } int idx = parent->index(); return createIndex(idx, 0, parent); } QModelIndex ProblemModel::index(int row, int column, const QModelIndex& parent) const { if (row < 0 || row >= rowCount(parent) || column < 0 || column >= LastColumn) { return QModelIndex(); } ProblemStoreNode *parentNode = reinterpret_cast(parent.internalPointer()); const ProblemStoreNode *node = d->m_problems->findNode(row, parentNode); return createIndex(row, column, (void*)node); } int ProblemModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return LastColumn; } IProblem::Ptr ProblemModel::problemForIndex(const QModelIndex& index) const { ProblemStoreNode *node = reinterpret_cast(index.internalPointer()); if (!node) { return {}; } else { return node->problem(); } } ProblemModel::Features ProblemModel::features() const { return d->m_features; } void ProblemModel::setFeatures(Features features) { d->m_features = features; } +QString ProblemModel::fullUpdateTooltip() const +{ + return d->m_fullUpdateTooltip; +} + +void ProblemModel::setFullUpdateTooltip(const QString& tooltip) +{ + if (d->m_fullUpdateTooltip == tooltip) { + return; + } + + d->m_fullUpdateTooltip = tooltip; + emit fullUpdateTooltipChanged(); +} + void ProblemModel::addProblem(const IProblem::Ptr &problem) { int c = d->m_problems->count(); beginInsertRows(QModelIndex(), c, c + 1); d->m_problems->addProblem(problem); endInsertRows(); } void ProblemModel::setProblems(const QVector &problems) { beginResetModel(); d->m_problems->setProblems(problems); endResetModel(); } void ProblemModel::clearProblems() { beginResetModel(); d->m_problems->clear(); endResetModel(); } QVector ProblemModel::problems(const KDevelop::IndexedString& document) { return d->m_problems->problems(document); } QVariant ProblemModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(orientation); if (role != Qt::DisplayRole) return {}; switch (section) { case Source: return i18nc("@title:column source of problem", "Source"); case Error: return i18nc("@title:column problem description", "Problem"); case File: return i18nc("@title:column file where problem was found", "File"); case Line: return i18nc("@title:column line number with problem", "Line"); case Column: return i18nc("@title:column column number with problem", "Column"); } return {}; } void ProblemModel::setCurrentDocument(IDocument* document) { Q_ASSERT(thread() == QThread::currentThread()); QUrl currentDocument = document->url(); /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt d->m_problems->setCurrentDocument(IndexedString(currentDocument)); } void ProblemModel::closedDocument(IDocument* document) { if (IndexedString(document->url()) == d->m_problems->currentDocument()) { // reset current document d->m_problems->setCurrentDocument(IndexedString()); } } void ProblemModel::onBeginRebuild() { beginResetModel(); } void ProblemModel::onEndRebuild() { endResetModel(); } void ProblemModel::setShowImports(bool showImports) { Q_ASSERT(thread() == QThread::currentThread()); d->m_problems->setShowImports(showImports); } bool ProblemModel::showImports() { return d->m_problems->showImports(); } void ProblemModel::setScope(int scope) { Q_ASSERT(thread() == QThread::currentThread()); if (!features().testFlag(ScopeFilter)) scope = ProblemScope::BypassScopeFilter; /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt d->m_problems->setScope(scope); } void ProblemModel::setSeverity(int severity) { switch (severity) { case KDevelop::IProblem::Error: setSeverities(KDevelop::IProblem::Error); break; case KDevelop::IProblem::Warning: setSeverities(KDevelop::IProblem::Error | KDevelop::IProblem::Warning); break; case KDevelop::IProblem::Hint: setSeverities(KDevelop::IProblem::Error | KDevelop::IProblem::Warning | KDevelop::IProblem::Hint); break; } } void ProblemModel::setSeverities(KDevelop::IProblem::Severities severities) { Q_ASSERT(thread() == QThread::currentThread()); /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt d->m_problems->setSeverities(severities); } void ProblemModel::setGrouping(int grouping) { /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt d->m_problems->setGrouping(grouping); } ProblemStore* ProblemModel::store() const { return d->m_problems.data(); } } diff --git a/shell/problemmodel.h b/shell/problemmodel.h index faef129041..0ed5227992 100644 --- a/shell/problemmodel.h +++ b/shell/problemmodel.h @@ -1,199 +1,210 @@ /* * KDevelop Problem Reporter * * Copyright 2007 Hamish Rodda * Copyright 2015 Laszlo Kis-Adam * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PROBLEMMODEL_H #define PROBLEMMODEL_H #include #include #include #include struct ProblemModelPrivate; namespace KDevelop { class IDocument; class ProblemStore; /** * @brief Wraps a ProblemStore and adds the QAbstractItemModel interface, so the it can be used in a model/view architecture. * * By default ProblemModel instantiates a FilteredProblemStore, with the following features on: * \li ScopeFilter * \li SeverityFilter * \li Grouping * \li CanByPassScopeFilter * * Has to following columns: * \li Error * \li Source * \li File * \li Line * \li Column * \li LastColumn * * Possible ProblemModel features * \li NoFeatures * \li CanDoFullUpdate * \li CanShowImports * \li ScopeFilter * \li SeverityFilter * \li Grouping * \li CanByPassScopeFilter * * Scope, severity, grouping, imports can be set using the slots named after these features. * * Usage example: * @code * IProblem::Ptr problem(new DetectedProblem); * problem->setDescription(QStringLiteral("Problem")); * ProblemModel *model = new ProblemModel(nullptr); * model->addProblem(problem); * model->rowCount(); // returns 1 * QModelIndex idx = model->index(0, 0); * model->data(index); // "Problem" * @endcode * */ class KDEVPLATFORMSHELL_EXPORT ProblemModel : public QAbstractItemModel { Q_OBJECT public: /// List of supportable features enum FeatureCode { NoFeatures = 0, /// No features :( CanDoFullUpdate = 1, /// Reload/Reparse problems CanShowImports = 2, /// Show problems from imported files. E.g.: Header files in C/C++ ScopeFilter = 4, /// Filter problems by scope. E.g.: current document, open documents, etc SeverityFilter = 8, /// Filter problems by severity. E.g.: hint, warning, error, etc Grouping = 16, /// Can group problems CanByPassScopeFilter = 32 /// Can bypass scope filter }; Q_DECLARE_FLAGS(Features, FeatureCode) explicit ProblemModel(QObject *parent, ProblemStore *store = nullptr); ~ProblemModel() override; enum Columns { Error, Source, File, Line, Column, LastColumn }; enum Roles { ProblemRole = Qt::UserRole + 1, SeverityRole }; int columnCount(const QModelIndex & parent = QModelIndex()) const override; QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex & index) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex & parent = QModelIndex()) const override; QVariant headerData ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override; IProblem::Ptr problemForIndex(const QModelIndex& index) const; /// Adds a new problem to the model void addProblem(const IProblem::Ptr &problem); /// Clears the problems, then adds a new set of them void setProblems(const QVector &problems); /// Clears the problems void clearProblems(); /// Retrieve problems for selected document QVector problems(const KDevelop::IndexedString& document); /// Retrieve the supported features Features features() const; /// Retrieve 'show imports' filter setting bool showImports(); /// Set the supported features void setFeatures(Features features); + /// Tooltip for "Force Full Update" action in the Problems View when the model + /// is active (correspondent tab is selected) + QString fullUpdateTooltip() const; + + /// Set the "Force Full Update" action tooltip + void setFullUpdateTooltip(const QString& tooltip); + signals: /// Emitted when the stored problems are changed with addProblem(), setProblems() and /// clearProblems() methods. This signal emitted only when internal problems storage is /// really changed: for example, it is not emitted when we call clearProblems() method /// for empty model. void problemsChanged(); + /// Emitted when the "Force Full Update" action tooltip is changed with setFullUpdateTooltip(). + /// This signal emitted only when tooltip is really changed. + void fullUpdateTooltipChanged(); + public slots: /// Show imports void setShowImports(bool showImports); /// Sets the scope filter. Uses int to be able to use QSignalMapper void setScope(int scope); /// Sets the severity filter. Uses int to be able to use QSignalMapper void setSeverity(int severity);///old-style severity filtering void setSeverities(KDevelop::IProblem::Severities severities);///new-style severity filtering void setGrouping(int grouping); /** * Force a full problem update. * E.g.: Reparse the source code. * Obviously it doesn't make sense for run-time problem checkers. */ virtual void forceFullUpdate(){} protected slots: /// Triggered when problems change virtual void onProblemsChanged(){} private slots: /// Triggered when the current document changes virtual void setCurrentDocument(IDocument* doc); virtual void closedDocument(IDocument* doc); /// Triggered before the problems are rebuilt void onBeginRebuild(); /// Triggered once the problems have been rebuilt void onEndRebuild(); protected: ProblemStore *store() const; private: QScopedPointer d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(ProblemModel::Features) } #endif // PROBLEMMODEL_H