diff --git a/plugins/problemreporter/problemhighlighter.cpp b/plugins/problemreporter/problemhighlighter.cpp index cab75ffa9..23a439a1d 100644 --- a/plugins/problemreporter/problemhighlighter.cpp +++ b/plugins/problemreporter/problemhighlighter.cpp @@ -1,205 +1,208 @@ /* * KDevelop Problem Reporter * * Copyright 2008 Hamish Rodda * Copyright 2008-2009 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 "problemhighlighter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KTextEditor; using namespace KDevelop; namespace { QColor colorForSeverity(IProblem::Severity severity) { KColorScheme scheme(QPalette::Active); switch (severity) { case IProblem::Error: return scheme.foreground(KColorScheme::NegativeText).color(); case IProblem::Warning: return scheme.foreground(KColorScheme::NeutralText).color(); case IProblem::Hint: default: return scheme.foreground(KColorScheme::PositiveText).color(); } } } ProblemHighlighter::ProblemHighlighter(KTextEditor::Document* document) : m_document(document) { Q_ASSERT(m_document); connect(ICore::self()->languageController()->completionSettings(), &ICompletionSettings::settingsChanged, this, &ProblemHighlighter::settingsChanged); connect(m_document.data(), &Document::aboutToReload, this, &ProblemHighlighter::clearProblems); if (qobject_cast(m_document)) { // can't use new signal/slot syntax here, MovingInterface is not a QObject connect(m_document, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(clearProblems())); } connect(m_document, SIGNAL(aboutToRemoveText(KTextEditor::Range)), this, SLOT(aboutToRemoveText(KTextEditor::Range))); } void ProblemHighlighter::settingsChanged() { // Re-highlight setProblems(m_problems); } ProblemHighlighter::~ProblemHighlighter() { if (m_topHLRanges.isEmpty() || !m_document) return; qDeleteAll(m_topHLRanges); } void ProblemHighlighter::setProblems(const QVector& problems) { if (!m_document) return; + if (m_problems == problems) + return; + const bool hadProblems = !m_problems.isEmpty(); m_problems = problems; qDeleteAll(m_topHLRanges); m_topHLRanges.clear(); m_problemsForRanges.clear(); IndexedString url(m_document->url()); /// TODO: create a better MarkInterface that makes it possible to add the marks to the scrollbar /// but having no background. /// also make it nicer together with other plugins, this would currently fail with /// this method... const uint errorMarkType = KTextEditor::MarkInterface::Error; const uint warningMarkType = KTextEditor::MarkInterface::Warning; KTextEditor::MarkInterface* markIface = dynamic_cast(m_document.data()); if (markIface && hadProblems) { // clear previously added marks foreach (KTextEditor::Mark* mark, markIface->marks()) { if (mark->type == errorMarkType || mark->type == warningMarkType) { markIface->removeMark(mark->line, mark->type); } } } if (problems.isEmpty()) { return; } DUChainReadLocker lock; TopDUContext* top = DUChainUtils::standardContextForUrl(m_document->url()); KTextEditor::MovingInterface* iface = dynamic_cast(m_document.data()); Q_ASSERT(iface); foreach (const IProblem::Ptr& problem, problems) { if (problem->finalLocation().document != url || !problem->finalLocation().isValid()) continue; KTextEditor::Range range; if (top) range = top->transformFromLocalRevision(RangeInRevision::castFromSimpleRange(problem->finalLocation())); else range = problem->finalLocation(); if (range.end().line() >= m_document->lines()) range.end() = KTextEditor::Cursor(m_document->endOfLine(m_document->lines() - 1)); if (range.isEmpty()) { range.setEnd(range.end() + KTextEditor::Cursor(0, 1)); } KTextEditor::MovingRange* problemRange = iface->newMovingRange(range); m_problemsForRanges.insert(problemRange, problem); m_topHLRanges.append(problemRange); if (problem->source() != IProblem::ToDo && (problem->severity() != IProblem::Hint || ICore::self()->languageController()->completionSettings()->highlightSemanticProblems())) { KTextEditor::Attribute::Ptr attribute(new KTextEditor::Attribute()); attribute->setUnderlineStyle(QTextCharFormat::WaveUnderline); attribute->setUnderlineColor(colorForSeverity(problem->severity())); problemRange->setAttribute(attribute); } if (markIface && ICore::self()->languageController()->completionSettings()->highlightProblematicLines()) { uint mark; if (problem->severity() == IProblem::Error) { mark = errorMarkType; } else if (problem->severity() == IProblem::Warning) { mark = warningMarkType; } else { continue; } markIface->addMark(problem->finalLocation().start().line(), mark); } } } void ProblemHighlighter::aboutToRemoveText(const KTextEditor::Range& range) { if (range.onSingleLine()) { // no need to optimize this return; } QList::iterator it = m_topHLRanges.begin(); while (it != m_topHLRanges.end()) { if (range.contains((*it)->toRange())) { m_problemsForRanges.remove(*it); delete (*it); it = m_topHLRanges.erase(it); } else { ++it; } } } void ProblemHighlighter::clearProblems() { setProblems({}); } diff --git a/plugins/problemreporter/problemreporterplugin.cpp b/plugins/problemreporter/problemreporterplugin.cpp index 6f54709fb..9dc96e151 100644 --- a/plugins/problemreporter/problemreporterplugin.cpp +++ b/plugins/problemreporter/problemreporterplugin.cpp @@ -1,213 +1,246 @@ /* * 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"), 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) { - auto allProblems = m_model->problems(url, false); - ph->setProblems(allProblems); + 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); } + + ph->setProblems(documentProblems); } void ProblemReporterPlugin::showModel(const QString& name) { auto w = dynamic_cast(core()->uiController()->findToolView(i18n("Problems"), m_factory)); if (w) w->showModel(name); } 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()) { + 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/problemreporterplugin.h b/plugins/problemreporter/problemreporterplugin.h index 0347b604b..19c8dd11b 100644 --- a/plugins/problemreporter/problemreporterplugin.h +++ b/plugins/problemreporter/problemreporterplugin.h @@ -1,80 +1,83 @@ /* * 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. */ #ifndef KDEVPLATFORM_PLUGIN_PROBLEMREPORTERPLUGIN_H #define KDEVPLATFORM_PLUGIN_PROBLEMREPORTERPLUGIN_H #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(PLUGIN_PROBLEMREPORTER) namespace KTextEditor { class Document; } namespace KDevelop { class IDocument; } class ProblemHighlighter; class ProblemReporterModel; class ProblemReporterPlugin : public KDevelop::IPlugin { Q_OBJECT public: explicit ProblemReporterPlugin(QObject* parent, const QVariantList& = QVariantList()); ~ProblemReporterPlugin() override; KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context) override; // KDevelop::Plugin methods void unload() override; ProblemReporterModel* model() const; private Q_SLOTS: void updateReady(const KDevelop::IndexedString& url, const KDevelop::ReferencedTopDUContext&); void updateHighlight(const KDevelop::IndexedString& url); void textDocumentCreated(KDevelop::IDocument* document); + void documentActivated(KDevelop::IDocument* document); void showModel(const QString& name); private: + void updateOpenedDocumentsHighlight(); class ProblemReporterFactory* m_factory; ProblemReporterModel* m_model; QHash m_highlighters; + QSet m_reHighlightNeeded; public slots: void documentClosed(KDevelop::IDocument*); }; #endif // KDEVPLATFORM_PLUGIN_PROBLEMREPORTERPLUGIN_H // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/shell/problemmodel.cpp b/shell/problemmodel.cpp index 7e1ace1b6..9ea30d726 100644 --- a/shell/problemmodel.cpp +++ b/shell/problemmodel.cpp @@ -1,351 +1,357 @@ /* * 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::setScope(int scope) { Q_ASSERT(thread() == QThread::currentThread()); /// 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 4bbfea5ef..e14e3ebb1 100644 --- a/shell/problemmodel.h +++ b/shell/problemmodel.h @@ -1,186 +1,196 @@ /* * 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; /// 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){} /// Sets the scope filter. Uses int to be able to use QSignalMapper virtual 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 virtual 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/problemmodelset.cpp b/shell/problemmodelset.cpp index 52a5e8592..be033e227 100644 --- a/shell/problemmodelset.cpp +++ b/shell/problemmodelset.cpp @@ -1,93 +1,98 @@ /* * 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 "problemmodelset.h" +#include "problemmodel.h" #include namespace KDevelop { struct ProblemModelSetPrivate { QVector data; }; ProblemModelSet::ProblemModelSet(QObject *parent) : QObject(parent) , d(new ProblemModelSetPrivate()) { } ProblemModelSet::~ProblemModelSet() = default; void ProblemModelSet::addModel(const QString &name, ProblemModel *model) { ModelData m; m.name = name; m.model = model; d->data.push_back(m); + connect(model, &ProblemModel::problemsChanged, this, &ProblemModelSet::problemsChanged); + emit added(m); } ProblemModel* ProblemModelSet::findModel(const QString &name) const { ProblemModel *model = nullptr; foreach (const ModelData &data, d->data) { if (data.name == name) { model = data.model; break; } } return model; } void ProblemModelSet::removeModel(const QString &name) { QVector::iterator itr = d->data.begin(); while (itr != d->data.end()) { if(itr->name == name) break; ++itr; } - if(itr != d->data.end()) + if(itr != d->data.end()) { + (*itr).model->disconnect(this); d->data.erase(itr); + } emit removed(name); } void ProblemModelSet::showModel(const QString &name) { for (const ModelData &data : d->data) { if (data.name == name) { emit showRequested(name); return; } } } QVector ProblemModelSet::models() const { return d->data; } } diff --git a/shell/problemmodelset.h b/shell/problemmodelset.h index f9a986591..167441cdc 100644 --- a/shell/problemmodelset.h +++ b/shell/problemmodelset.h @@ -1,102 +1,105 @@ /* * 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 PROBLEMMODELSET_H #define PROBLEMMODELSET_H #include #include #include class QAbstractItemModel; namespace KDevelop { class ProblemModel; /// Struct that handles the model and it's name as one unit, stored in ProblemModelSet struct ModelData { QString name; ProblemModel *model; }; struct ProblemModelSetPrivate; /** * @brief Stores name/model pairs and emits signals when they are added/removed. * * Typically it's used from plugins, which maintains a reference to the model added. * Therefore It assumes that models get removed, so it doesn't delete! * * Usage example: * @code * ProblemModelSet *set = new ProblemModelSet(); * ProblemModel *model = new ProblemModel(nullptr); * set->addModel(QStringLiteral("MODEL"), model); // added() signal is emitted * set->models().count(); // returns 1 * set->findModel(QStringLiteral("MODEL")); // returns the model just added * set->removeModel(QStringLiteral("MODEL")); // removed() signal is emitted * @endcode * */ class KDEVPLATFORMSHELL_EXPORT ProblemModelSet : public QObject { Q_OBJECT public: explicit ProblemModelSet(QObject *parent = nullptr); ~ProblemModelSet() override; /// Adds a model void addModel(const QString &name, ProblemModel *model); /// Finds a model ProblemModel* findModel(const QString &name) const; /// Removes a model void removeModel(const QString &name); /// Show model in ProblemsView void showModel(const QString &name); /// Retrieves a list of models stored QVector models() const; signals: /// Emitted when a new model is added void added(const ModelData &model); /// Emitted when a model is removed void removed(const QString &name); /// Emitted when showModel() is called void showRequested(const QString &name); + /// Emitted when any model emits problemsChanged() + void problemsChanged(); + private: QScopedPointer d; }; } Q_DECLARE_TYPEINFO(KDevelop::ModelData, Q_MOVABLE_TYPE); #endif diff --git a/shell/problemstore.cpp b/shell/problemstore.cpp index 9a86014ae..af3ee4419 100644 --- a/shell/problemstore.cpp +++ b/shell/problemstore.cpp @@ -1,230 +1,264 @@ /* * 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) { - clear(); + 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; } if (d->m_documents) { if(cast_scope == d->m_documents->getScope()) return; 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 break; } 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::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 9fa8c4fda..55a48e5ca 100644 --- a/shell/problemstore.h +++ b/shell/problemstore.h @@ -1,149 +1,157 @@ /* * 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); /// 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 the problems change + /// 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