diff --git a/plugins/problemreporter/problemreportermodel.cpp b/plugins/problemreporter/problemreportermodel.cpp index 743692fd3..0afcc3e6c 100644 --- a/plugins/problemreporter/problemreportermodel.cpp +++ b/plugins/problemreporter/problemreportermodel.cpp @@ -1,199 +1,168 @@ /* * 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. */ #include "problemreportermodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; const int ProblemReporterModel::MinTimeout = 1000; const int ProblemReporterModel::MaxTimeout = 5000; ProblemReporterModel::ProblemReporterModel(QObject* parent) : ProblemModel(parent, new FilteredProblemStore()) - , m_showImports(false) { setFeatures(CanDoFullUpdate | CanShowImports | ScopeFilter | SeverityFilter); m_minTimer = new QTimer(this); m_minTimer->setInterval(MinTimeout); m_minTimer->setSingleShot(true); connect(m_minTimer, &QTimer::timeout, this, &ProblemReporterModel::timerExpired); m_maxTimer = new QTimer(this); m_maxTimer->setInterval(MaxTimeout); m_maxTimer->setSingleShot(true); connect(m_maxTimer, &QTimer::timeout, this, &ProblemReporterModel::timerExpired); connect(store(), &FilteredProblemStore::changed, this, &ProblemReporterModel::onProblemsChanged); connect(ICore::self()->languageController()->staticAssistantsManager(), &StaticAssistantsManager::problemsChanged, this, &ProblemReporterModel::onProblemsChanged); } ProblemReporterModel::~ProblemReporterModel() { } -QVector ProblemReporterModel::problems(const KDevelop::IndexedString& url, bool showImports) const +QVector ProblemReporterModel::problems(const QSet& docs) const { QVector result; - QSet visitedContexts; - KDevelop::DUChainReadLocker lock; - problemsInternal(KDevelop::DUChain::self()->chainForDocument(url), showImports, visitedContexts, result); - return result; -} + DUChainReadLocker lock; -QVector ProblemReporterModel::problems(const QSet& urls, bool showImports) const -{ - QVector result; - QSet visitedContexts; - KDevelop::DUChainReadLocker lock; - foreach (const KDevelop::IndexedString& url, urls) { - problemsInternal(KDevelop::DUChain::self()->chainForDocument(url), showImports, visitedContexts, result); + foreach (const IndexedString& doc, docs) { + if (doc.isEmpty()) + continue; + + TopDUContext* ctx = DUChain::self()->chainForDocument(doc); + if (!ctx) + continue; + + foreach (ProblemPointer p, DUChainUtils::allProblemsForContext(ctx)) { + result.append(p); + } } + return result; } void ProblemReporterModel::forceFullUpdate() { Q_ASSERT(thread() == QThread::currentThread()); - QSet documents = store()->documents()->get(); - KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); - foreach (const KDevelop::IndexedString& document, documents) { + QSet documents = store()->documents()->get(); + if (showImports()) + documents += store()->documents()->getImports(); + + DUChainReadLocker lock(DUChain::lock()); + foreach (const IndexedString& document, documents) { if (document.isEmpty()) continue; - KDevelop::TopDUContext::Features updateType = KDevelop::TopDUContext::ForceUpdate; + TopDUContext::Features updateType = TopDUContext::ForceUpdate; if (documents.size() == 1) - updateType = KDevelop::TopDUContext::ForceUpdateRecursive; - KDevelop::DUChain::self()->updateContextForUrl( + updateType = TopDUContext::ForceUpdateRecursive; + DUChain::self()->updateContextForUrl( document, - (KDevelop::TopDUContext::Features)(updateType | KDevelop::TopDUContext::VisibleDeclarationsAndContexts)); - } -} - -void ProblemReporterModel::problemsInternal(KDevelop::TopDUContext* context, bool showImports, - QSet& visitedContexts, - QVector& result) const -{ - if (!context || visitedContexts.contains(context)) { - return; - } - ReferencedTopDUContext top(context); - foreach (KDevelop::ProblemPointer p, DUChainUtils::allProblemsForContext(top)) { - if (p && p->severity() <= store()->severity()) { - result.append(p); - } - } - visitedContexts.insert(context); - if (showImports) { - bool isProxy = context->parsingEnvironmentFile() && context->parsingEnvironmentFile()->isProxyContext(); - foreach (const KDevelop::DUContext::Import& ctx, context->importedParentContexts()) { - if (!ctx.indexedContext().indexedTopContext().isLoaded()) - continue; - KDevelop::TopDUContext* topCtx = dynamic_cast(ctx.context(nullptr)); - if (topCtx) { - /// If we are starting at a proxy-context, only recurse into other proxy-contexts, because those contain the problems. - if (!isProxy - || (topCtx->parsingEnvironmentFile() && topCtx->parsingEnvironmentFile()->isProxyContext())) - problemsInternal(topCtx, showImports, visitedContexts, result); - } - } + (TopDUContext::Features)(updateType | TopDUContext::VisibleDeclarationsAndContexts)); } } void ProblemReporterModel::onProblemsChanged() { rebuildProblemList(); } void ProblemReporterModel::timerExpired() { m_minTimer->stop(); m_maxTimer->stop(); rebuildProblemList(); } void ProblemReporterModel::setCurrentDocument(KDevelop::IDocument* doc) { Q_ASSERT(thread() == QThread::currentThread()); beginResetModel(); - QUrl currentDocument = doc->url(); /// Will trigger signal changed() if problems change - store()->setCurrentDocument(KDevelop::IndexedString(currentDocument)); + store()->setCurrentDocument(IndexedString(doc->url())); endResetModel(); } void ProblemReporterModel::problemsUpdated(const KDevelop::IndexedString& url) { Q_ASSERT(thread() == QThread::currentThread()); - if (store()->documents()->get().contains(url)) { - /// m_minTimer will expire in MinTimeout unless some other parsing job finishes in this period. - m_minTimer->start(); - /// m_maxTimer will expire unconditionally in MaxTimeout - if (!m_maxTimer->isActive()) { - m_maxTimer->start(); - } - } -} + // skip update for urls outside current scope + if (!store()->documents()->get().contains(url) && + !(showImports() && store()->documents()->getImports().contains(url))) + return; -void ProblemReporterModel::setShowImports(bool showImports) -{ - if (m_showImports != showImports) { - Q_ASSERT(thread() == QThread::currentThread()); - m_showImports = showImports; - rebuildProblemList(); + /// m_minTimer will expire in MinTimeout unless some other parsing job finishes in this period. + m_minTimer->start(); + /// m_maxTimer will expire unconditionally in MaxTimeout + if (!m_maxTimer->isActive()) { + m_maxTimer->start(); } } void ProblemReporterModel::rebuildProblemList() { - QVector problems; /// No locking here, because it may be called from an already locked context beginResetModel(); - problems = this->problems(store()->documents()->get(), m_showImports); - store()->setProblems(problems); + QVector allProblems = problems(store()->documents()->get()); + + if (showImports()) + allProblems += problems(store()->documents()->getImports()); + + store()->setProblems(allProblems); endResetModel(); } diff --git a/plugins/problemreporter/problemreportermodel.h b/plugins/problemreporter/problemreportermodel.h index 0fa61a6bd..aded34ac5 100644 --- a/plugins/problemreporter/problemreportermodel.h +++ b/plugins/problemreporter/problemreportermodel.h @@ -1,86 +1,77 @@ /* * 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 PROBLEMREPORTERMODEL_H #define PROBLEMREPORTERMODEL_H #include namespace KDevelop { class IndexedString; class TopDUContext; } class WatchedDocumentSet; class QTimer; /** * @brief ProblemModel subclass that retrieves the problems from DUChain. * * Provides a ProblemModel interface so these problems can be shown in the Problems toolview. */ class ProblemReporterModel : public KDevelop::ProblemModel { Q_OBJECT public: explicit ProblemReporterModel(QObject* parent); ~ProblemReporterModel() override; - /** - * Get problems for @ref url. - */ - QVector problems(const KDevelop::IndexedString& url, bool showImports) const; /** * Get merged list of problems for all @ref urls. */ - QVector problems(const QSet& urls, bool showImports) const; + QVector problems(const QSet& urls) const; public Q_SLOTS: /** * List of problems for @ref url has been updated */ void problemsUpdated(const KDevelop::IndexedString& url); - void setShowImports(bool showImports) override; void forceFullUpdate() override; protected Q_SLOTS: /// Triggered when the problemstore's problems have changed void onProblemsChanged() override; private Q_SLOTS: void timerExpired(); void setCurrentDocument(KDevelop::IDocument* doc) override; private: - void problemsInternal(KDevelop::TopDUContext* context, bool showImports, - QSet& visitedContexts, - QVector& result) const; void rebuildProblemList(); - bool m_showImports; /// include problems from imported documents QTimer* m_minTimer; QTimer* m_maxTimer; const static int MinTimeout; const static int MaxTimeout; }; #endif diff --git a/plugins/problemreporter/problemreporterplugin.cpp b/plugins/problemreporter/problemreporterplugin.cpp index c04ef4fa8..115edff83 100644 --- a/plugins/problemreporter/problemreporterplugin.cpp +++ b/plugins/problemreporter/problemreporterplugin.cpp @@ -1,254 +1,254 @@ /* * KDevelop Problem Reporter * * Copyright 2006 Adam Treat * Copyright 2006-2007 Hamish Rodda * Copyright 2007-2008 David Nolden * * 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 "problemreporterplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "problemhighlighter.h" #include "problemtreeview.h" #include "problemreportermodel.h" #include "language/assistant/staticassistantsmanager.h" #include #include #include #include #include #include #include "shell/problemmodelset.h" #include "problemsview.h" #include Q_LOGGING_CATEGORY(PLUGIN_PROBLEMREPORTER, "kdevplatform.plugins.problemreporter") K_PLUGIN_FACTORY_WITH_JSON(KDevProblemReporterFactory, "kdevproblemreporter.json", registerPlugin();) using namespace KDevelop; class ProblemReporterFactory : public KDevelop::IToolViewFactory { public: QWidget* create(QWidget* parent = nullptr) override { Q_UNUSED(parent); ProblemsView* v = new ProblemsView(); v->load(); return v; } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ProblemReporterView"); } }; ProblemReporterPlugin::ProblemReporterPlugin(QObject* parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevproblemreporter"), parent) , m_factory(new ProblemReporterFactory) , m_model(new ProblemReporterModel(this)) { KDevelop::ProblemModelSet* pms = core()->languageController()->problemModelSet(); pms->addModel(QStringLiteral("Parser"), i18n("Parser"), m_model); core()->uiController()->addToolView(i18n("Problems"), m_factory); setXMLFile(QStringLiteral("kdevproblemreporter.rc")); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &ProblemReporterPlugin::documentClosed); connect(ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &ProblemReporterPlugin::textDocumentCreated); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ProblemReporterPlugin::documentActivated); connect(DUChain::self(), &DUChain::updateReady, this, &ProblemReporterPlugin::updateReady); connect(ICore::self()->languageController()->staticAssistantsManager(), &StaticAssistantsManager::problemsChanged, this, &ProblemReporterPlugin::updateHighlight); connect(pms, &ProblemModelSet::showRequested, this, &ProblemReporterPlugin::showModel); connect(pms, &ProblemModelSet::problemsChanged, this, &ProblemReporterPlugin::updateOpenedDocumentsHighlight); } ProblemReporterPlugin::~ProblemReporterPlugin() { qDeleteAll(m_highlighters); } ProblemReporterModel* ProblemReporterPlugin::model() const { return m_model; } void ProblemReporterPlugin::unload() { KDevelop::ProblemModelSet* pms = KDevelop::ICore::self()->languageController()->problemModelSet(); pms->removeModel(QStringLiteral("Parser")); core()->uiController()->removeToolView(m_factory); } void ProblemReporterPlugin::documentClosed(IDocument* doc) { if (!doc->textDocument()) return; IndexedString url(doc->url()); delete m_highlighters.take(url); m_reHighlightNeeded.remove(url); } void ProblemReporterPlugin::textDocumentCreated(KDevelop::IDocument* document) { Q_ASSERT(document->textDocument()); m_highlighters.insert(IndexedString(document->url()), new ProblemHighlighter(document->textDocument())); DUChain::self()->updateContextForUrl(IndexedString(document->url()), KDevelop::TopDUContext::AllDeclarationsContextsAndUses, this); } void ProblemReporterPlugin::documentActivated(KDevelop::IDocument* document) { IndexedString documentUrl(document->url()); if (m_reHighlightNeeded.contains(documentUrl)) { m_reHighlightNeeded.remove(documentUrl); updateHighlight(documentUrl); } } void ProblemReporterPlugin::updateReady(const IndexedString& url, const KDevelop::ReferencedTopDUContext&) { m_model->problemsUpdated(url); updateHighlight(url); } void ProblemReporterPlugin::updateHighlight(const KDevelop::IndexedString& url) { ProblemHighlighter* ph = m_highlighters.value(url); if (!ph) return; KDevelop::ProblemModelSet* pms(core()->languageController()->problemModelSet()); QVector documentProblems; foreach (const ModelData& modelData, pms->models()) { - documentProblems += modelData.model->problems(url); + documentProblems += modelData.model->problems({url}); } ph->setProblems(documentProblems); } void ProblemReporterPlugin::showModel(const QString& id) { auto w = dynamic_cast(core()->uiController()->findToolView(i18n("Problems"), m_factory)); if (w) w->showModel(id); } KDevelop::ContextMenuExtension ProblemReporterPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension extension; KDevelop::EditorContext* editorContext = dynamic_cast(context); if (editorContext) { DUChainReadLocker lock(DUChain::lock(), 1000); if (!lock.locked()) { qCDebug(PLUGIN_PROBLEMREPORTER) << "failed to lock duchain in time"; return extension; } QString title; QList actions; TopDUContext* top = DUChainUtils::standardContextForUrl(editorContext->url()); if (top) { foreach (KDevelop::ProblemPointer problem, top->problems()) { if (problem->range().contains( top->transformToLocalRevision(KTextEditor::Cursor(editorContext->position())))) { KDevelop::IAssistant::Ptr solution = problem->solutionAssistant(); if (solution) { title = solution->title(); foreach (KDevelop::IAssistantAction::Ptr action, solution->actions()) actions << action->toKAction(); } } } } if (!actions.isEmpty()) { QString text; if (title.isEmpty()) text = i18n("Solve Problem"); else { text = i18n("Solve: %1", KDevelop::htmlToPlainText(title)); } QAction* menuAction = new QAction(text, nullptr); QMenu* menu(new QMenu(text, nullptr)); menuAction->setMenu(menu); foreach (QAction* action, actions) menu->addAction(action); extension.addAction(ContextMenuExtension::ExtensionGroup, menuAction); } } return extension; } void ProblemReporterPlugin::updateOpenedDocumentsHighlight() { foreach(auto document, core()->documentController()->openDocuments()) { // Skip non-text documents. // This also fixes crash caused by calling updateOpenedDocumentsHighlight() method without // any opened documents. In this case documentController()->openDocuments() returns single // (non-text) document with url like file:///tmp/kdevelop_QW2530.patch which has fatal bug: // if we call isActive() method from this document the crash will happens. if (!document->isTextDocument()) continue; IndexedString documentUrl(document->url()); if (document->isActive()) updateHighlight(documentUrl); else m_reHighlightNeeded.insert(documentUrl); } } #include "problemreporterplugin.moc" // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/problemreporter/problemsview.cpp b/plugins/problemreporter/problemsview.cpp index 90c38967c..3ad367dea 100644 --- a/plugins/problemreporter/problemsview.cpp +++ b/plugins/problemreporter/problemsview.cpp @@ -1,523 +1,523 @@ /* * 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 Error")); 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_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); - setScope(CurrentDocument); // 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: 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); connect(view, &ProblemTreeView::changed, this, &ProblemsView::onViewChanged); 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()); } 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/filteredproblemstore.cpp b/shell/filteredproblemstore.cpp index 40962a6a6..c96f62278 100644 --- a/shell/filteredproblemstore.cpp +++ b/shell/filteredproblemstore.cpp @@ -1,311 +1,315 @@ /* * 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 "filteredproblemstore.h" #include "problem.h" #include "watcheddocumentset.h" #include "problemstorenode.h" #include using namespace KDevelop; namespace { /// Adds diagnostics as sub-nodes void addDiagnostics(ProblemStoreNode *node, const QVector &diagnostics) { foreach (const IProblem::Ptr &ptr, diagnostics) { ProblemNode *child = new ProblemNode(node, ptr); node->addChild(child); addDiagnostics(child, ptr->diagnostics()); } } /** * @brief Base class for grouping strategy classes * * These classes build the problem tree based on the respective strategies */ class GroupingStrategy { public: GroupingStrategy( ProblemStoreNode *root ) : m_rootNode(root) , m_groupedRootNode(new ProblemStoreNode()) { } virtual ~GroupingStrategy(){ } /// Add a problem to the appropriate group virtual void addProblem(const IProblem::Ptr &problem) = 0; /// Find the specified noe const ProblemStoreNode* findNode(int row, ProblemStoreNode *parent = nullptr) const { if (parent == nullptr) return m_groupedRootNode->child(row); else return parent->child(row); } /// Returns the number of children nodes int count(ProblemStoreNode *parent = nullptr) { if (parent == nullptr) return m_groupedRootNode->count(); else return parent->count(); } /// Clears the problems virtual void clear() { m_groupedRootNode->clear(); } protected: ProblemStoreNode *m_rootNode; QScopedPointer m_groupedRootNode; }; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// Implements no grouping strategy, that is just stores the problems without any grouping class NoGroupingStrategy final : public GroupingStrategy { public: NoGroupingStrategy(ProblemStoreNode *root) : GroupingStrategy(root) { } void addProblem(const IProblem::Ptr &problem) override { ProblemNode *node = new ProblemNode(m_groupedRootNode.data(), problem); addDiagnostics(node, problem->diagnostics()); m_groupedRootNode->addChild(node); } }; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// Implements grouping based on path class PathGroupingStrategy final : public GroupingStrategy { public: PathGroupingStrategy(ProblemStoreNode *root) : GroupingStrategy(root) { } void addProblem(const IProblem::Ptr &problem) override { QString path = problem->finalLocation().document.str(); /// See if we already have this path ProblemStoreNode *parent = nullptr; foreach (ProblemStoreNode *node, m_groupedRootNode->children()) { if (node->label() == path) { parent = node; break; } } /// If not add it! if (parent == nullptr) { parent = new LabelNode(m_groupedRootNode.data(), path); m_groupedRootNode->addChild(parent); } ProblemNode *node = new ProblemNode(parent, problem); addDiagnostics(node, problem->diagnostics()); parent->addChild(node); } }; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// Implements grouping based on severity class SeverityGroupingStrategy final : public GroupingStrategy { public: enum SeverityGroups { GroupError = 0, GroupWarning = 1, GroupHint = 2 }; SeverityGroupingStrategy(ProblemStoreNode *root) : GroupingStrategy(root) { /// Create the groups on construction, so there's no need to search for them on addition m_groupedRootNode->addChild(new LabelNode(m_groupedRootNode.data(), i18n("Error"))); m_groupedRootNode->addChild(new LabelNode(m_groupedRootNode.data(), i18n("Warning"))); m_groupedRootNode->addChild(new LabelNode(m_groupedRootNode.data(), i18n("Hint"))); } void addProblem(const IProblem::Ptr &problem) override { ProblemStoreNode *parent = nullptr; switch (problem->severity()) { case IProblem::Error: parent = m_groupedRootNode->child(GroupError); break; case IProblem::Warning: parent = m_groupedRootNode->child(GroupWarning); break; case IProblem::Hint: parent = m_groupedRootNode->child(GroupHint); break; default: break; } ProblemNode *node = new ProblemNode(m_groupedRootNode.data(), problem); addDiagnostics(node, problem->diagnostics()); parent->addChild(node); } void clear() override { m_groupedRootNode->child(GroupError)->clear(); m_groupedRootNode->child(GroupWarning)->clear(); m_groupedRootNode->child(GroupHint)->clear(); } }; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// namespace KDevelop { struct FilteredProblemStorePrivate { FilteredProblemStorePrivate(FilteredProblemStore* q) : q(q) , m_strategy(new NoGroupingStrategy(q->rootNode())) , m_grouping(NoGrouping) { } /// Tells if the problem matches the filters bool match(const IProblem::Ptr &problem) const; FilteredProblemStore* q; QScopedPointer m_strategy; GroupingMethod m_grouping; }; FilteredProblemStore::FilteredProblemStore(QObject *parent) : ProblemStore(parent) , d(new FilteredProblemStorePrivate(this)) { } FilteredProblemStore::~FilteredProblemStore() { } void FilteredProblemStore::addProblem(const IProblem::Ptr &problem) { ProblemStore::addProblem(problem); if (d->match(problem)) d->m_strategy->addProblem(problem); } const ProblemStoreNode* FilteredProblemStore::findNode(int row, ProblemStoreNode *parent) const { return d->m_strategy->findNode(row, parent); } int FilteredProblemStore::count(ProblemStoreNode *parent) const { return d->m_strategy->count(parent); } void FilteredProblemStore::clear() { d->m_strategy->clear(); ProblemStore::clear(); } void FilteredProblemStore::rebuild() { emit beginRebuild(); d->m_strategy->clear(); foreach (ProblemStoreNode *node, rootNode()->children()) { IProblem::Ptr problem = node->problem(); if (d->match(problem)) { d->m_strategy->addProblem(problem); } } emit endRebuild(); } void FilteredProblemStore::setGrouping(int grouping) { GroupingMethod g = GroupingMethod(grouping); if(g == d->m_grouping) return; d->m_grouping = g; switch (g) { case NoGrouping: d->m_strategy.reset(new NoGroupingStrategy(rootNode())); break; case PathGrouping: d->m_strategy.reset(new PathGroupingStrategy(rootNode())); break; case SeverityGrouping: d->m_strategy.reset(new SeverityGroupingStrategy(rootNode())); break; } rebuild(); emit changed(); } int FilteredProblemStore::grouping() const { return d->m_grouping; } bool FilteredProblemStorePrivate::match(const IProblem::Ptr &problem) const { + if (q->scope() != ProblemScope::BypassScopeFilter && + !q->documents()->get().contains(problem.data()->finalLocation().document) && + !(q->showImports() && q->documents()->getImports().contains(problem.data()->finalLocation().document))) + return false; if(problem->severity()!=IProblem::NoSeverity) { /// If the problem severity isn't in the filter severities it's discarded if(!q->severities().testFlag(problem->severity())) return false; } else { if(!q->severities().testFlag(IProblem::Hint))//workaround for problems wothout correctly set severity return false; } return true; } } diff --git a/shell/problemmodel.cpp b/shell/problemmodel.cpp index 9ea30d726..6094bb4ec 100644 --- a/shell/problemmodel.cpp +++ b/shell/problemmodel.cpp @@ -1,357 +1,372 @@ /* * 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 { ProblemModelPrivate(KDevelop::ProblemStore *store) : m_problems(store) , m_features(KDevelop::ProblemModel::NoFeatures) { } QScopedPointer m_problems; KDevelop::ProblemModel::Features m_features; }; 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(), 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; } 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 e14e3ebb1..faef12904 100644 --- a/shell/problemmodel.h +++ b/shell/problemmodel.h @@ -1,196 +1,199 @@ /* * 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); 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(); public slots: /// Show imports - virtual void setShowImports(bool){} + void setShowImports(bool showImports); /// Sets the scope filter. Uses int to be able to use QSignalMapper - virtual void setScope(int scope); + void setScope(int scope); /// Sets the severity filter. Uses int to be able to use QSignalMapper - virtual void setSeverity(int severity);///old-style severity filtering + void setSeverity(int severity);///old-style severity filtering - virtual void setSeverities(KDevelop::IProblem::Severities severities);///new-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 diff --git a/shell/problemstore.cpp b/shell/problemstore.cpp index af3ee4419..ae96bebef 100644 --- a/shell/problemstore.cpp +++ b/shell/problemstore.cpp @@ -1,264 +1,274 @@ /* * 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 "problemstore.h" #include #include #include "problemstorenode.h" struct ProblemStorePrivate { ProblemStorePrivate() : m_documents(nullptr) , m_severities(KDevelop::IProblem::Error | KDevelop::IProblem::Warning | KDevelop::IProblem::Hint) , m_rootNode(new KDevelop::ProblemStoreNode()) { } /// Watched document set. Only problems that are in files in this set are stored. KDevelop::WatchedDocumentSet *m_documents; /// The severity filter setting KDevelop::IProblem::Severities m_severities; /// The problems list KDevelop::ProblemStoreNode *m_rootNode; /// Path of the currently open document KDevelop::IndexedString m_currentDocument; /// All stored problems QVector m_allProblems; }; namespace KDevelop { ProblemStore::ProblemStore(QObject *parent) : QObject(parent), d(new ProblemStorePrivate) { } ProblemStore::~ProblemStore() { clear(); delete d->m_rootNode; } void ProblemStore::addProblem(const IProblem::Ptr &problem) { ProblemNode *node = new ProblemNode(d->m_rootNode); node->setProblem(problem); d->m_rootNode->addChild(node); d->m_allProblems += problem; emit problemsChanged(); } void ProblemStore::setProblems(const QVector &problems) { int oldSize = d->m_allProblems.size(); // set signals block to prevent problemsChanged() emitting during clean { QSignalBlocker blocker(this); clear(); } foreach (const IProblem::Ptr &problem, problems) { d->m_rootNode->addChild(new ProblemNode(d->m_rootNode, problem)); } rebuild(); if (d->m_allProblems.size() != oldSize || d->m_allProblems != problems) { d->m_allProblems = problems; emit problemsChanged(); } } QVector ProblemStore::problems(const KDevelop::IndexedString& document) const { QVector documentProblems; foreach (auto problem, d->m_allProblems) { if (problem->finalLocation().document == document) documentProblems += problem; } return documentProblems; } const ProblemStoreNode* ProblemStore::findNode(int row, ProblemStoreNode *parent) const { Q_UNUSED(parent); return d->m_rootNode->child(row); } int ProblemStore::count(ProblemStoreNode *parent) const { if(parent) return parent->count(); else return d->m_rootNode->count(); } void ProblemStore::clear() { d->m_rootNode->clear(); if (!d->m_allProblems.isEmpty()) { d->m_allProblems.clear(); emit problemsChanged(); } } void ProblemStore::rebuild() { } void ProblemStore::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 ProblemStore::setSeverities(KDevelop::IProblem::Severities severities) { if(severities != d->m_severities) { d->m_severities = severities; rebuild(); emit changed(); } } int ProblemStore::severity() const { if (d->m_severities.testFlag(KDevelop::IProblem::Hint)) return KDevelop::IProblem::Hint; if (d->m_severities.testFlag(KDevelop::IProblem::Warning)) return KDevelop::IProblem::Warning; if (d->m_severities.testFlag(KDevelop::IProblem::Error)) return KDevelop::IProblem::Error; return 0; } KDevelop::IProblem::Severities ProblemStore::severities() const { return d->m_severities; } WatchedDocumentSet* ProblemStore::documents() const { return d->m_documents; } void ProblemStore::setScope(int scope) { ProblemScope cast_scope = static_cast(scope); - - if (cast_scope == BypassScopeFilter) { - return; - } + bool showImports = false; if (d->m_documents) { if(cast_scope == d->m_documents->getScope()) return; + showImports = d->m_documents->showImports(); delete d->m_documents; } switch (cast_scope) { case CurrentDocument: d->m_documents = new CurrentDocumentSet(d->m_currentDocument, this); break; case OpenDocuments: d->m_documents = new OpenDocumentSet(this); break; case CurrentProject: d->m_documents = new CurrentProjectSet(d->m_currentDocument, this); break; case AllProjects: d->m_documents = new AllProjectSet(this); break; case BypassScopeFilter: - // handled above + d->m_documents = new BypassSet(this); break; } + d->m_documents->setShowImports(showImports); + rebuild(); connect(d->m_documents, &WatchedDocumentSet::changed, this, &ProblemStore::onDocumentSetChanged); emit changed(); } int ProblemStore::scope() const { Q_ASSERT(d->m_documents); return d->m_documents->getScope(); } void ProblemStore::setGrouping(int grouping) { Q_UNUSED(grouping); } +void ProblemStore::setShowImports(bool showImports) +{ + d->m_documents->setShowImports(showImports); +} + +int ProblemStore::showImports() const +{ + return d->m_documents->showImports(); +} + void ProblemStore::setCurrentDocument(const IndexedString &doc) { d->m_currentDocument = doc; d->m_documents->setCurrentDocument(doc); } const KDevelop::IndexedString& ProblemStore::currentDocument() const { return d->m_currentDocument; } void ProblemStore::onDocumentSetChanged() { rebuild(); emit changed(); } ProblemStoreNode* ProblemStore::rootNode() { return d->m_rootNode; } } diff --git a/shell/problemstore.h b/shell/problemstore.h index 55a48e5ca..17fd18d72 100644 --- a/shell/problemstore.h +++ b/shell/problemstore.h @@ -1,157 +1,163 @@ /* * 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 PROBLEMSTORE_H #define PROBLEMSTORE_H #include #include #include #include #include struct ProblemStorePrivate; namespace KDevelop { class WatchedDocumentSet; class ProblemStoreNode; /** * @brief Stores and handles problems. Does no ordering or filtering, those should be done in subclasses. * * Used to store problems that are ordered, filtered somewhere else. For example: DUChain problems gathered by ProblemReporter. * Stores the problems in ProblemStoreNodes. * When implementing a subclass, first and foremost the rebuild method needs to be implemented, which is called every time there's a change in scope and severity filter. * If grouping is desired then also the setGrouping method must be implemented. * ProblemStore depending on settings uses CurrentDocumentSet, OpenDocumentSet, CurrentProjectSet, or AllProjectSet for scope support (NOTE: Filtering still has to be implemented in either a subclass, or somewhere else). * When the scope changes it emits the changed() signal. * * Scope set / query methods: * \li setScope() * \li scope() * * Valid scope settings: * \li CurrentDocument * \li OpenDocuments * \li CurrentProject * \li AllProjects * \li BypassScopeFilter * * Usage example: * @code * QVector problems; * // Add 4 problems * ... * ProblemStore *store = new ProblemStore(); * store->setProblems(problems); * store->count(); // Returns 4 * * ProblemStoreNode *node = store->findNode(0); // Returns the node with the first problem * @endcode * */ class KDEVPLATFORMSHELL_EXPORT ProblemStore : public QObject { Q_OBJECT public: explicit ProblemStore(QObject *parent = nullptr); ~ProblemStore() override; /// Adds a problem virtual void addProblem(const IProblem::Ptr &problem); /// Clears the current problems, and adds new ones from a list virtual void setProblems(const QVector &problems); /// Retrieve problems for selected document QVector problems(const KDevelop::IndexedString& document) const; /// Finds the specified node virtual const ProblemStoreNode* findNode(int row, ProblemStoreNode *parent = nullptr) const; /// Returns the number of problems virtual int count(ProblemStoreNode *parent = nullptr) const; /// Clears the problems virtual void clear(); /// Rebuild the problems list, if applicable. It does nothing in the base class. virtual void rebuild(); /// Specifies the severity filter virtual void setSeverity(int severity);///old-style severity access virtual void setSeverities(KDevelop::IProblem::Severities severities);///new-style severity access /// Retrives the severity filter settings int severity() const;///old-style severity access KDevelop::IProblem::Severities severities() const;//new-style severity access /// Retrieves the currently watched document set WatchedDocumentSet* documents() const; /// Sets the scope filter void setScope(int scope); /// Returns the current scope int scope() const; /// Sets the grouping method virtual void setGrouping(int grouping); + /// Set 'show imports' filter value + void setShowImports(bool showImports); + + /// Retrieve 'show imports' filter setting + int showImports() const; + /// Sets the currently shown document (in the editor, it's triggered by the IDE) void setCurrentDocument(const IndexedString &doc); /// Retrives the path of the current document const KDevelop::IndexedString& currentDocument() const; signals: /// Emitted when any store setting (grouping, scope, severity, document) is changed void changed(); /// Emitted when the stored problems are changed with clear(), addProblem() and setProblems() /// methods. This signal emitted only when internal problems storage is really changed: /// for example, it is not emitted when we call clear() method for empty storage. void problemsChanged(); /// Emitted before the problemlist is rebuilt void beginRebuild(); /// Emitted once the problemlist has been rebuilt void endRebuild(); private slots: /// Triggered when the watched document set changes. E.g.:document closed, new one added, etc virtual void onDocumentSetChanged(); protected: ProblemStoreNode* rootNode(); private: QScopedPointer d; }; } #endif diff --git a/shell/watcheddocumentset.cpp b/shell/watcheddocumentset.cpp index 367b20aab..9bcc77ca1 100644 --- a/shell/watcheddocumentset.cpp +++ b/shell/watcheddocumentset.cpp @@ -1,187 +1,359 @@ /* * KDevelop Problem Reporter * * Copyright 2010 Dmitry Risenberg * * 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 "watcheddocumentset.h" #include +#include #include #include -#include #include -#include +#include +#include #include +#include namespace KDevelop { +enum ActionFlag { + DoUpdate = 1, + DoEmit = 2 +}; +Q_DECLARE_FLAGS(ActionFlags, ActionFlag); +Q_DECLARE_OPERATORS_FOR_FLAGS(ActionFlags); + +class WatchedDocumentSetPrivate : public QObject +{ + Q_OBJECT + +public: + using DocumentSet = WatchedDocumentSet::DocumentSet; + + WatchedDocumentSetPrivate(WatchedDocumentSet* documentSet) + : m_documentSet(documentSet) + , m_showImports(false) + { + connect(DUChain::self(), &DUChain::updateReady, this, &WatchedDocumentSetPrivate::updateReady); + } + + inline bool showImports() const + { + return m_showImports; + } + + void setShowImports(bool showImports) + { + if (m_showImports == showImports) + return; + + DocumentSet oldImports = m_imports; + + m_showImports = showImports; + updateImports(); + + if (m_imports != oldImports) + emit m_documentSet->changed(); + } + + inline const DocumentSet& documents() const + { + return m_documents; + } + + inline const DocumentSet& imports() const + { + return m_imports; + } + + inline void doUpdate(ActionFlags flags) + { + if (flags.testFlag(DoUpdate)) + updateImports(); + + if (flags.testFlag(DoEmit)) + emit m_documentSet->changed(); + } + + void setDocuments(const DocumentSet& docs, ActionFlags flags = nullptr) + { + m_documents = docs; + doUpdate(flags); + } + + void addDocument(const IndexedString& doc, ActionFlags flags = nullptr) + { + if (m_documents.contains(doc)) + return; + + m_documents.insert(doc); + doUpdate(flags); + } + + void delDocument(const IndexedString& doc, ActionFlags flags = nullptr) + { + if (!m_documents.contains(doc)) + return; + + m_documents.remove(doc); + doUpdate(flags); + } + + void updateImports() + { + if (!m_showImports) { + if (!m_imports.isEmpty()) { + m_imports.clear(); + return; + } + return; + } + + getImportsFromDUChain(); + } + +private: + void getImportsFromDU(TopDUContext* context, QSet& visitedContexts) + { + if (!context || visitedContexts.contains(context)) + return; + + visitedContexts.insert(context); + foreach (const DUContext::Import& ctx, context->importedParentContexts()) { + TopDUContext* topCtx = dynamic_cast(ctx.context(nullptr)); + + if (topCtx) + getImportsFromDU(topCtx, visitedContexts); + } + } + + void getImportsFromDUChain() + { + QSet visitedContexts; + + m_imports.clear(); + foreach (const IndexedString& doc, m_documents) { + TopDUContext* ctx = DUChain::self()->chainForDocument(doc); + getImportsFromDU(ctx, visitedContexts); + visitedContexts.remove(ctx); + } + + foreach (TopDUContext* ctx, visitedContexts) { + m_imports.insert(ctx->url()); + } + } + + void updateReady(const IndexedString& doc, const ReferencedTopDUContext&) + { + if (!m_showImports || !m_documents.contains(doc)) + return; + + DocumentSet oldImports = m_imports; + + updateImports(); + if (m_imports != oldImports) + emit m_documentSet->changed(); + } + + WatchedDocumentSet* m_documentSet; + + DocumentSet m_documents; + DocumentSet m_imports; + + bool m_showImports; +}; + WatchedDocumentSet::WatchedDocumentSet(QObject* parent) - :QObject(parent) + : QObject(parent) + , d(new WatchedDocumentSetPrivate(this)) +{ +} + +WatchedDocumentSet::~WatchedDocumentSet() +{ +} + +bool WatchedDocumentSet::showImports() const +{ + return d->showImports(); +} + +void WatchedDocumentSet::setShowImports(bool showImports) { + d->setShowImports(showImports); } void WatchedDocumentSet::setCurrentDocument(const IndexedString&) { } WatchedDocumentSet::DocumentSet WatchedDocumentSet::get() const { - return m_documents; + return d->documents(); } -CurrentDocumentSet::CurrentDocumentSet(const IndexedString& document, QObject *parent) +WatchedDocumentSet::DocumentSet WatchedDocumentSet::getImports() const +{ + return d->imports(); +} + +CurrentDocumentSet::CurrentDocumentSet(const IndexedString& document, QObject* parent) : WatchedDocumentSet(parent) { - m_documents.insert(document); + d->setDocuments({document}, DoUpdate); } void CurrentDocumentSet::setCurrentDocument(const IndexedString& url) { - m_documents.clear(); - m_documents.insert(url); - emit changed(); + d->setDocuments({url}, DoUpdate | DoEmit); } ProblemScope CurrentDocumentSet::getScope() const { return CurrentDocument; } -OpenDocumentSet::OpenDocumentSet(QObject *parent) +OpenDocumentSet::OpenDocumentSet(QObject* parent) : WatchedDocumentSet(parent) { - QList docs = ICore::self()->documentController()->openDocuments(); - foreach (IDocument* doc, docs) { - m_documents.insert(IndexedString(doc->url())); + foreach (IDocument* doc, ICore::self()->documentController()->openDocuments()) { + d->addDocument(IndexedString(doc->url())); } + d->updateImports(); + connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &OpenDocumentSet::documentClosed); connect(ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &OpenDocumentSet::documentCreated); } void OpenDocumentSet::documentClosed(IDocument* doc) { - if (m_documents.remove(IndexedString(doc->url()))) { - emit changed(); - } + d->delDocument(IndexedString(doc->url()), DoUpdate | DoEmit); } void OpenDocumentSet::documentCreated(IDocument* doc) { - m_documents.insert(IndexedString(doc->url())); - emit changed(); + d->addDocument(IndexedString(doc->url()), DoUpdate | DoEmit); } ProblemScope OpenDocumentSet::getScope() const { return OpenDocuments; } -ProjectSet::ProjectSet(QObject *parent) +ProjectSet::ProjectSet(QObject* parent) : WatchedDocumentSet(parent) { } void ProjectSet::fileAdded(ProjectFileItem* file) { - m_documents.insert(file->indexedPath()); - emit changed(); + d->addDocument(IndexedString(file->indexedPath()), DoUpdate | DoEmit); } void ProjectSet::fileRemoved(ProjectFileItem* file) { - if (m_documents.remove(file->indexedPath())) { - emit changed(); - } + d->delDocument(IndexedString(file->indexedPath()), DoUpdate | DoEmit); } void ProjectSet::fileRenamed(const Path& oldFile, ProjectFileItem* newFile) { - if (m_documents.remove(IndexedString(oldFile.pathOrUrl()))) { - m_documents.insert(newFile->indexedPath()); - } + d->delDocument(IndexedString(oldFile.pathOrUrl())); + d->addDocument(IndexedString(newFile->indexedPath()), DoUpdate | DoEmit); } void ProjectSet::trackProjectFiles(const IProject* project) { if (project) { // The implementation should derive from QObject somehow QObject* fileManager = dynamic_cast(project->projectFileManager()); if (fileManager) { // can't use new signal/slot syntax here, IProjectFileManager is no a QObject connect(fileManager, SIGNAL(fileAdded(ProjectFileItem*)), this, SLOT(fileAdded(ProjectFileItem*))); connect(fileManager, SIGNAL(fileRemoved(ProjectFileItem*)), this, SLOT(fileRemoved(ProjectFileItem*))); connect(fileManager, SIGNAL(fileRenamed(Path,ProjectFileItem*)), this, SLOT(fileRenamed(Path,ProjectFileItem*))); } } } -CurrentProjectSet::CurrentProjectSet(const IndexedString& document, QObject *parent) - : ProjectSet(parent), m_currentProject(nullptr) +CurrentProjectSet::CurrentProjectSet(const IndexedString& document, QObject* parent) + : ProjectSet(parent) + , m_currentProject(nullptr) { setCurrentDocumentInternal(document); - trackProjectFiles(m_currentProject); } void CurrentProjectSet::setCurrentDocument(const IndexedString& url) { setCurrentDocumentInternal(url); } void CurrentProjectSet::setCurrentDocumentInternal(const IndexedString& url) { IProject* projectForUrl = ICore::self()->projectController()->findProjectForUrl(url.toUrl()); if (projectForUrl && projectForUrl != m_currentProject) { - m_documents.clear(); m_currentProject = projectForUrl; - - foreach (const IndexedString &indexedString, m_currentProject->fileSet()) { - m_documents.insert(indexedString); - } - emit changed(); + d->setDocuments(m_currentProject->fileSet()); + d->addDocument(IndexedString(m_currentProject->path().toLocalFile()), DoUpdate | DoEmit); + trackProjectFiles(m_currentProject); } } ProblemScope CurrentProjectSet::getScope() const { return CurrentProject; } -AllProjectSet::AllProjectSet(QObject *parent) +AllProjectSet::AllProjectSet(QObject* parent) : ProjectSet(parent) { foreach(const IProject* project, ICore::self()->projectController()->projects()) { foreach (const IndexedString &indexedString, project->fileSet()) { - m_documents.insert(indexedString); + d->addDocument(indexedString); } + d->addDocument(IndexedString(project->path().toLocalFile())); trackProjectFiles(project); } + d->updateImports(); + emit changed(); } ProblemScope AllProjectSet::getScope() const { return AllProjects; } +BypassSet::BypassSet(QObject* parent) + : WatchedDocumentSet(parent) +{ +} + +ProblemScope BypassSet::getScope() const +{ + return BypassScopeFilter; +} + } +#include "watcheddocumentset.moc" diff --git a/shell/watcheddocumentset.h b/shell/watcheddocumentset.h index 9884d0bb6..28a1ef1f9 100644 --- a/shell/watcheddocumentset.h +++ b/shell/watcheddocumentset.h @@ -1,134 +1,150 @@ /* * KDevelop Problem Reporter * * Copyright 2010 Dmitry Risenberg * * 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 KDEVPLATFORM_PLUGIN_WATCHEDDOCUMENTSET_H #define KDEVPLATFORM_PLUGIN_WATCHEDDOCUMENTSET_H #include #include #include #include #include "problemconstants.h" namespace KDevelop { class IDocument; class IProject; class ProjectFileItem; class Path; - +class WatchedDocumentSetPrivate; /** * Helper class that tracks set of documents and notifies its owner whenever this set changes. Derived classes implement different tracking strategies. */ class KDEVPLATFORMSHELL_EXPORT WatchedDocumentSet : public QObject { Q_OBJECT public: - typedef QSet DocumentSet; + using DocumentSet = QSet; + explicit WatchedDocumentSet(QObject* parent); + ~WatchedDocumentSet() override; + + bool showImports() const; + void setShowImports(bool showImports); + virtual DocumentSet get() const; + virtual DocumentSet getImports() const; + virtual void setCurrentDocument(const IndexedString& url); virtual ProblemScope getScope() const = 0; - ~WatchedDocumentSet() override {} signals: void changed(); protected: - DocumentSet m_documents; + QScopedPointer d; }; /** * Tracks a document that is current at any given moment. * When a new file is activated, it becomes tracked instead of the old one. */ class CurrentDocumentSet : public WatchedDocumentSet { Q_OBJECT public: explicit CurrentDocumentSet(const IndexedString& document, QObject* parent); void setCurrentDocument(const IndexedString& url) override; ProblemScope getScope() const override; }; /** * Tracks all open documents. */ class OpenDocumentSet : public WatchedDocumentSet { Q_OBJECT public: explicit OpenDocumentSet(QObject* parent); ProblemScope getScope() const override; private slots: void documentClosed(IDocument* doc); void documentCreated(IDocument* doc); }; /** * Tracks documents that are in the same project as the current file. * If current file is not in any project, none are tracked. */ class ProjectSet : public WatchedDocumentSet { Q_OBJECT public: explicit ProjectSet(QObject* parent); protected: void trackProjectFiles(const IProject* project); protected slots: void fileAdded(ProjectFileItem*); void fileRemoved(ProjectFileItem* file); void fileRenamed(const Path& oldFile, ProjectFileItem* newFile); }; /** * Tracks files in all open projects. */ class CurrentProjectSet : public ProjectSet { Q_OBJECT public: explicit CurrentProjectSet(const IndexedString& document, QObject* parent); void setCurrentDocument(const IndexedString& url) override; ProblemScope getScope() const override; private: void setCurrentDocumentInternal(const IndexedString& url); // to avoid virtual in constructor IProject* m_currentProject; }; class AllProjectSet : public ProjectSet { Q_OBJECT public: explicit AllProjectSet(QObject* parent); ProblemScope getScope() const override; }; +class BypassSet : public WatchedDocumentSet +{ + Q_OBJECT +public: + explicit BypassSet(QObject* parent); + + ProblemScope getScope() const override; +}; + } #endif // KDEVPLATFORM_PLUGIN_WATCHEDDOCUMENTSET_H