diff options
author | Nikolai Kosjar <nikolai.kosjar@qt.io> | 2019-01-25 10:17:56 +0100 |
---|---|---|
committer | Nikolai Kosjar <nikolai.kosjar@qt.io> | 2019-01-31 10:34:44 +0000 |
commit | 26a6cf3bb3bf7c64c05f4b6f84cefb1a8a84c148 (patch) | |
tree | 42b592f634643cdf0a936cf34ab44c13626c1ae7 | |
parent | d386b3c2412c4e0fb2dd32a51ebcf83ca2a32442 (diff) |
ClangTools: Organize diagnostics by file path
* Introduce the file path as a top level node.
* Remove the location column.
* Encode the line/column information in the DisplayRole, as for the
Clang Code Model tooltips.
* Double click on a diagnostic opens the editor.
Change-Id: I4c263537cc04c3c4feb6ccd5c395d60d8bee0bc3
Reviewed-by: Ivan Donchevskii <ivan.donchevskii@qt.io>
-rw-r--r-- | src/plugins/clangtools/clangtidyclazytool.cpp | 5 | ||||
-rw-r--r-- | src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp | 198 | ||||
-rw-r--r-- | src/plugins/clangtools/clangtoolsdiagnosticmodel.h | 19 | ||||
-rw-r--r-- | src/plugins/clangtools/clangtoolsdiagnosticview.cpp | 83 | ||||
-rw-r--r-- | src/plugins/clangtools/clangtoolsdiagnosticview.h | 10 | ||||
-rw-r--r-- | src/plugins/debugger/analyzer/detailederrorview.cpp | 11 | ||||
-rw-r--r-- | src/plugins/debugger/analyzer/detailederrorview.h | 6 |
7 files changed, 246 insertions, 86 deletions
diff --git a/src/plugins/clangtools/clangtidyclazytool.cpp b/src/plugins/clangtools/clangtidyclazytool.cpp index d7033df170..233f6d87eb 100644 --- a/src/plugins/clangtools/clangtidyclazytool.cpp +++ b/src/plugins/clangtools/clangtidyclazytool.cpp @@ -216,7 +216,8 @@ ClangTidyClazyTool::ClangTidyClazyTool() initDiagnosticView(); m_diagnosticView->setModel(m_diagnosticFilterModel); m_diagnosticView->setSortingEnabled(true); - m_diagnosticView->sortByColumn(Debugger::DetailedErrorView::LocationColumn, Qt::AscendingOrder); + m_diagnosticView->sortByColumn(Debugger::DetailedErrorView::DiagnosticColumn, + Qt::AscendingOrder); m_diagnosticView->setObjectName(QLatin1String("ClangTidyClazyIssuesView")); m_diagnosticView->setWindowTitle(tr("Clang-Tidy and Clazy Issues")); @@ -299,7 +300,7 @@ ClangTidyClazyTool::ClangTidyClazyTool() }); connect(m_applyFixitsButton, &QToolButton::clicked, [this]() { QVector<DiagnosticItem *> diagnosticItems; - m_diagnosticModel->rootItem()->forChildrenAtLevel(1, [&](TreeItem *item){ + m_diagnosticModel->rootItem()->forChildrenAtLevel(2, [&](TreeItem *item){ diagnosticItems += static_cast<DiagnosticItem *>(item); }); diff --git a/src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp b/src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp index f21a8e1b40..719324b359 100644 --- a/src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp +++ b/src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp @@ -29,6 +29,7 @@ #include "clangtoolsprojectsettings.h" #include "clangtoolsutils.h" +#include <coreplugin/fileiconprovider.h> #include <projectexplorer/project.h> #include <projectexplorer/session.h> #include <utils/qtcassert.h> @@ -37,11 +38,33 @@ #include <QFileInfo> #include <QLoggingCategory> +#include <tuple> + static Q_LOGGING_CATEGORY(LOG, "qtc.clangtools.model", QtWarningMsg) namespace ClangTools { namespace Internal { +FilePathItem::FilePathItem(const QString &filePath) + : m_filePath(filePath) +{} + +QVariant FilePathItem::data(int column, int role) const +{ + if (column == DiagnosticView::DiagnosticColumn) { + switch (role) { + case Qt::DisplayRole: + return m_filePath; + case Qt::DecorationRole: + return Core::FileIconProvider::icon(m_filePath); + default: + return QVariant(); + } + } + + return QVariant(); +} + class ExplainingStepItem : public Utils::TreeItem { public: @@ -57,7 +80,7 @@ ClangToolsDiagnosticModel::ClangToolsDiagnosticModel(QObject *parent) : Utils::TreeModel<>(parent) , m_filesWatcher(std::make_unique<QFileSystemWatcher>()) { - setHeader({tr("Issue"), tr("Location"), tr("Fixit Status")}); + setHeader({tr("Issue"), tr("Fixit Status")}); connectFileWatcher(); } @@ -85,6 +108,7 @@ void ClangToolsDiagnosticModel::addDiagnostics(const QList<Diagnostic> &diagnost }; for (const Diagnostic &d : diagnostics) { + // Check for duplicates const int previousItemCount = m_diagnostics.count(); m_diagnostics.insert(d); if (m_diagnostics.count() == previousItemCount) { @@ -92,9 +116,19 @@ void ClangToolsDiagnosticModel::addDiagnostics(const QList<Diagnostic> &diagnost continue; } + // Create file path item if necessary + const QString filePath = d.location.filePath; + FilePathItem *&filePathItem = m_filePathToItem[filePath]; + if (!filePathItem) { + filePathItem = new FilePathItem(filePath); + rootItem()->appendChild(filePathItem); + + addWatchedPath(d.location.filePath); + } + + // Add to file path item qCDebug(LOG) << "Adding diagnostic:" << d; - addWatchedPath(d.location.filePath); - rootItem()->appendChild(new DiagnosticItem(d, onFixitStatusChanged, this)); + filePathItem->appendChild(new DiagnosticItem(d, onFixitStatusChanged, this)); } } @@ -105,6 +139,7 @@ QSet<Diagnostic> ClangToolsDiagnosticModel::diagnostics() const void ClangToolsDiagnosticModel::clear() { + m_filePathToItem.clear(); m_diagnostics.clear(); clearAndSetupCache(); Utils::TreeModel<>::clear(); @@ -135,11 +170,11 @@ void ClangToolsDiagnosticModel::clearAndSetupCache() void ClangToolsDiagnosticModel::onFileChanged(const QString &path) { - for (Utils::TreeItem * const item : *rootItem()) { + rootItem()->forChildrenAtLevel(2, [&](Utils::TreeItem *item){ auto diagnosticItem = static_cast<DiagnosticItem *>(item); if (diagnosticItem->diagnostic().location.filePath == path) diagnosticItem->setFixItStatus(FixitStatus::Invalidated); - } + }); removeWatchedPath(path); } @@ -345,11 +380,15 @@ static QVariant iconData(const QString &type) return QVariant(); } -QVariant DiagnosticItem::data(int column, int role) const +static QString withLineColumnPrefixed(const QString &text, + const Debugger::DiagnosticLocation &location) { - if (column == Debugger::DetailedErrorView::LocationColumn) - return Debugger::DetailedErrorView::locationData(role, m_diagnostic.location); + return QString("%1:%2: %3") + .arg(QString::number(location.line), QString::number(location.column), text); +} +QVariant DiagnosticItem::data(int column, int role) const +{ if (column == DiagnosticView::FixItColumn) { if (role == Qt::CheckStateRole) { switch (m_fixitStatus) { @@ -378,24 +417,28 @@ QVariant DiagnosticItem::data(int column, int role) const return ClangToolsDiagnosticModel::tr("Applied"); } } - return QVariant(); + } else if (column == DiagnosticView::DiagnosticColumn) { + switch (role) { + case Debugger::DetailedErrorView::LocationRole: + return QVariant::fromValue(m_diagnostic.location); + case Debugger::DetailedErrorView::FullTextRole: + return fullText(m_diagnostic); + case ClangToolsDiagnosticModel::DiagnosticRole: + return QVariant::fromValue(m_diagnostic); + case ClangToolsDiagnosticModel::TextRole: + return m_diagnostic.description; + case Qt::DisplayRole: + return withLineColumnPrefixed(m_diagnostic.description, m_diagnostic.location); + case Qt::ToolTipRole: + return createDiagnosticToolTipString(m_diagnostic); + case Qt::DecorationRole: + return iconData(m_diagnostic.type); + default: + return QVariant(); + } } - // DiagnosticColumn - switch (role) { - case Debugger::DetailedErrorView::FullTextRole: - return fullText(m_diagnostic); - case ClangToolsDiagnosticModel::DiagnosticRole: - return QVariant::fromValue(m_diagnostic); - case Qt::DisplayRole: - return m_diagnostic.description; - case Qt::ToolTipRole: - return createDiagnosticToolTipString(m_diagnostic); - case Qt::DecorationRole: - return iconData(m_diagnostic.type); - default: - return QVariant(); - } + return QVariant(); } bool DiagnosticItem::setData(int column, const QVariant &data, int role) @@ -454,27 +497,32 @@ static QVariant iconForExplainingStepMessage(const QString &message) QVariant ExplainingStepItem::data(int column, int role) const { - if (column == Debugger::DetailedErrorView::LocationColumn) - return Debugger::DetailedErrorView::locationData(role, m_step.location); - if (column == DiagnosticView::FixItColumn) return QVariant(); - // DiagnosticColumn - switch (role) { - case Debugger::DetailedErrorView::FullTextRole: - return fullText(static_cast<DiagnosticItem *>(parent())->diagnostic()); - case ClangToolsDiagnosticModel::DiagnosticRole: - return QVariant::fromValue(static_cast<DiagnosticItem *>(parent())->diagnostic()); - case Qt::DisplayRole: - return m_step.message; - case Qt::ToolTipRole: - return createExplainingStepToolTipString(m_step); - case Qt::DecorationRole: - return iconForExplainingStepMessage(m_step.message); - default: - return QVariant(); + if (column == DiagnosticView::DiagnosticColumn) { + // DiagnosticColumn + switch (role) { + case Debugger::DetailedErrorView::LocationRole: + return QVariant::fromValue(m_step.location); + case Debugger::DetailedErrorView::FullTextRole: + return fullText(static_cast<DiagnosticItem *>(parent())->diagnostic()); + case ClangToolsDiagnosticModel::TextRole: + return m_step.message; + case ClangToolsDiagnosticModel::DiagnosticRole: + return QVariant::fromValue(static_cast<DiagnosticItem *>(parent())->diagnostic()); + case Qt::DisplayRole: + return m_step.message; + case Qt::ToolTipRole: + return createExplainingStepToolTipString(m_step); + case Qt::DecorationRole: + return iconForExplainingStepMessage(m_step.message); + default: + return QVariant(); + } } + + return QVariant(); } @@ -518,30 +566,58 @@ void DiagnosticFilterModel::addSuppressedDiagnostic( bool DiagnosticFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { - // Avoid filtering child diagnostics / explaining steps. - if (sourceParent.isValid()) - return true; - - // Is the diagnostic suppressed? auto model = static_cast<ClangToolsDiagnosticModel *>(sourceModel()); - auto item = static_cast<DiagnosticItem *>(model->rootItem()->childAt(sourceRow)); - const Diagnostic &diag = item->diagnostic(); - foreach (const SuppressedDiagnostic &d, m_suppressedDiagnostics) { - if (d.description != diag.description) - continue; - QString filePath = d.filePath.toString(); - QFileInfo fi(filePath); - if (fi.isRelative()) - filePath = m_lastProjectDirectory.toString() + QLatin1Char('/') + filePath; - if (filePath == diag.location.filePath) - return false; + Utils::TreeItem *item = model->itemForIndex(sourceParent); + + // DiagnosticItem + if (auto filePathItem = dynamic_cast<FilePathItem *>(item)) { + auto diagnosticItem = dynamic_cast<DiagnosticItem *>(filePathItem->childAt(sourceRow)); + QTC_ASSERT(diagnosticItem, return false); + + // Is the diagnostic explicitly suppressed? + const Diagnostic &diag = diagnosticItem->diagnostic(); + foreach (const SuppressedDiagnostic &d, m_suppressedDiagnostics) { + if (d.description != diag.description) + continue; + QString filePath = d.filePath.toString(); + QFileInfo fi(filePath); + if (fi.isRelative()) + filePath = m_lastProjectDirectory.toString() + QLatin1Char('/') + filePath; + if (filePath == diag.location.filePath) + return false; + } + + // Does the diagnostic match the filter? + return diag.description.contains(filterRegExp()); } - // Does the diagnostic match the filter? - if (diag.description.contains(filterRegExp())) - return true; + return true; +} + +bool DiagnosticFilterModel::lessThan(const QModelIndex &l, const QModelIndex &r) const +{ + auto model = static_cast<ClangToolsDiagnosticModel *>(sourceModel()); + Utils::TreeItem *itemLeft = model->itemForIndex(l); + const bool isComparingDiagnostics = !dynamic_cast<FilePathItem *>(itemLeft); + + if (sortColumn() == Debugger::DetailedErrorView::DiagnosticColumn && isComparingDiagnostics) { + using Debugger::DiagnosticLocation; + const int role = Debugger::DetailedErrorView::LocationRole; + + const auto leftLoc = sourceModel()->data(l, role).value<DiagnosticLocation>(); + const auto leftText = sourceModel()->data(l, ClangToolsDiagnosticModel::TextRole).toString(); + + const auto rightLoc = sourceModel()->data(r, role).value<DiagnosticLocation>(); + const auto rightText = sourceModel()->data(r, ClangToolsDiagnosticModel::TextRole).toString(); + + const int result = std::tie(leftLoc.line, leftLoc.column, leftText) + < std::tie(rightLoc.line, rightLoc.column, rightText); + if (sortOrder() == Qt::DescendingOrder) + return !result; // Ensure that we always sort location from top to bottom. + return result; + } - return false; + return QSortFilterProxyModel::lessThan(l, r); } void DiagnosticFilterModel::handleSuppressedDiagnosticsChanged() diff --git a/src/plugins/clangtools/clangtoolsdiagnosticmodel.h b/src/plugins/clangtools/clangtoolsdiagnosticmodel.h index b877b93b86..8a69e9def7 100644 --- a/src/plugins/clangtools/clangtoolsdiagnosticmodel.h +++ b/src/plugins/clangtools/clangtoolsdiagnosticmodel.h @@ -58,6 +58,16 @@ enum class FixitStatus { class ClangToolsDiagnosticModel; +class FilePathItem : public Utils::TreeItem +{ +public: + FilePathItem(const QString &filePath); + QVariant data(int column, int role) const override; + +private: + const QString m_filePath; +}; + class DiagnosticItem : public Utils::TreeItem { public: @@ -75,10 +85,11 @@ public: ReplacementOperations &fixitOperations() { return m_fixitOperations; } void setFixitOperations(const ReplacementOperations &replacements); + bool setData(int column, const QVariant &data, int role) override; + private: Qt::ItemFlags flags(int column) const override; QVariant data(int column, int role) const override; - bool setData(int column, const QVariant &data, int role) override; private: const Diagnostic m_diagnostic; @@ -101,9 +112,7 @@ public: void addDiagnostics(const QList<Diagnostic> &diagnostics); QSet<Diagnostic> diagnostics() const; - enum ItemRole { - DiagnosticRole = Debugger::DetailedErrorView::FullTextRole + 1 - }; + enum ItemRole { DiagnosticRole = Debugger::DetailedErrorView::FullTextRole + 1, TextRole }; void clear(); void removeWatchedPath(const QString &path); @@ -119,6 +128,7 @@ private: void clearAndSetupCache(); private: + QHash<QString, FilePathItem *> m_filePathToItem; QSet<Diagnostic> m_diagnostics; std::map<QVector<ExplainingStep>, QVector<DiagnosticItem *>> stepsToItemsCache; std::unique_ptr<QFileSystemWatcher> m_filesWatcher; @@ -138,6 +148,7 @@ public: private: bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + bool lessThan(const QModelIndex &l, const QModelIndex &r) const override; void handleSuppressedDiagnosticsChanged(); QPointer<ProjectExplorer::Project> m_project; diff --git a/src/plugins/clangtools/clangtoolsdiagnosticview.cpp b/src/plugins/clangtools/clangtoolsdiagnosticview.cpp index 56e61f9555..b10d26dd32 100644 --- a/src/plugins/clangtools/clangtoolsdiagnosticview.cpp +++ b/src/plugins/clangtools/clangtoolsdiagnosticview.cpp @@ -29,6 +29,8 @@ #include "clangtoolsprojectsettings.h" #include "clangtoolsutils.h" +#include <coreplugin/editormanager/editormanager.h> + #include <utils/fileutils.h> #include <utils/qtcassert.h> @@ -136,6 +138,51 @@ void DiagnosticView::suppressCurrentDiagnostic() } } +void DiagnosticView::goNext() +{ + const QModelIndex currentIndex = selectionModel()->currentIndex(); + selectIndex(getIndex(currentIndex, Next)); +} + +void DiagnosticView::goBack() +{ + const QModelIndex currentIndex = selectionModel()->currentIndex(); + selectIndex(getIndex(currentIndex, Previous)); +} + +QModelIndex DiagnosticView::getIndex(const QModelIndex &index, Direction direction) const +{ + QModelIndex parentIndex = index.parent(); + + // Use direct sibling for level 2 and 3 items is possible + if (parentIndex.isValid()) { + const QModelIndex nextIndex = index.sibling(index.row() + direction, index.column()); + if (nextIndex.isValid()) + return nextIndex; + } + + // Last level 3 item? Continue on level 2 item + if (parentIndex.parent().isValid()) + return getIndex(parentIndex, direction); + + // Find next/previous level 2 item + QModelIndex nextTopIndex = getTopLevelIndex(parentIndex.isValid() ? parentIndex : index, + direction); + while (!model()->hasChildren(nextTopIndex)) + nextTopIndex = getTopLevelIndex(nextTopIndex, direction); + return model()->index(direction == Next ? 0 : model()->rowCount(nextTopIndex) - 1, + 0, + nextTopIndex); +} + +QModelIndex DiagnosticView::getTopLevelIndex(const QModelIndex &index, Direction direction) const +{ + QModelIndex below = index.sibling(index.row() + direction, 0); + if (below.isValid()) + return below; + return model()->index(direction == Next ? 0 : model()->rowCount(index) - 1, 0); +} + QList<QAction *> DiagnosticView::customActions() const { return {m_suppressAction}; @@ -150,11 +197,7 @@ bool DiagnosticView::eventFilter(QObject *watched, QEvent *event) case Qt::Key_Return: case Qt::Key_Enter: case Qt::Key_Space: - const QModelIndex current = currentIndex(); - const QModelIndex location = model()->index(current.row(), - LocationColumn, - current.parent()); - emit clicked(location); + openEditorForCurrentIndex(); } return true; } @@ -163,6 +206,12 @@ bool DiagnosticView::eventFilter(QObject *watched, QEvent *event) } } +void DiagnosticView::mouseDoubleClickEvent(QMouseEvent *event) +{ + openEditorForCurrentIndex(); + Debugger::DetailedErrorView::mouseDoubleClickEvent(event); +} + void DiagnosticView::setSelectedFixItsCount(int fixItsCount) { if (m_ignoreSetSelectedFixItsCount) @@ -174,27 +223,35 @@ void DiagnosticView::setSelectedFixItsCount(int fixItsCount) clickableFixItHeader->viewport()->update(); } +void DiagnosticView::openEditorForCurrentIndex() +{ + const QVariant v = model()->data(currentIndex(), Debugger::DetailedErrorView::LocationRole); + const auto loc = v.value<Debugger::DiagnosticLocation>(); + if (loc.isValid()) + Core::EditorManager::openEditorAt(loc.filePath, loc.line, loc.column - 1); +} + void DiagnosticView::setModel(QAbstractItemModel *theProxyModel) { const auto proxyModel = static_cast<QSortFilterProxyModel *>(theProxyModel); - QAbstractItemModel *sourceModel = proxyModel->sourceModel(); + const auto sourceModel = static_cast<ClangToolsDiagnosticModel *>(proxyModel->sourceModel()); Debugger::DetailedErrorView::setModel(proxyModel); auto *clickableFixItHeader = new ClickableFixItHeader(Qt::Horizontal, this); - connect(clickableFixItHeader, &ClickableFixItHeader::fixItColumnClicked, - this, [=](bool checked) { + connect(clickableFixItHeader, &ClickableFixItHeader::fixItColumnClicked, this, [=](bool checked) { m_ignoreSetSelectedFixItsCount = true; - for (int row = 0; row < sourceModel->rowCount(); ++row) { - QModelIndex index = sourceModel->index(row, FixItColumn, QModelIndex()); - sourceModel->setData(index, checked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); - } + sourceModel->rootItem()->forChildrenAtLevel(2, [&](::Utils::TreeItem *item) { + auto diagnosticItem = static_cast<DiagnosticItem *>(item); + diagnosticItem->setData(FixItColumn, + checked ? Qt::Checked : Qt::Unchecked, + Qt::CheckStateRole); + }); m_ignoreSetSelectedFixItsCount = false; }); setHeader(clickableFixItHeader); clickableFixItHeader->setStretchLastSection(false); clickableFixItHeader->setSectionResizeMode(0, QHeaderView::Stretch); clickableFixItHeader->setSectionResizeMode(1, QHeaderView::ResizeToContents); - clickableFixItHeader->setSectionResizeMode(2, QHeaderView::ResizeToContents); const int fixitColumnWidth = clickableFixItHeader->sectionSizeHint(DiagnosticView::FixItColumn); const int checkboxWidth = clickableFixItHeader->height(); diff --git a/src/plugins/clangtools/clangtoolsdiagnosticview.h b/src/plugins/clangtools/clangtoolsdiagnosticview.h index a33af34b85..7cb287ab39 100644 --- a/src/plugins/clangtools/clangtoolsdiagnosticview.h +++ b/src/plugins/clangtools/clangtoolsdiagnosticview.h @@ -38,16 +38,24 @@ public: DiagnosticView(QWidget *parent = nullptr); enum ExtraColumn { - FixItColumn = LocationColumn + 1, + FixItColumn = DiagnosticColumn + 1, }; void setSelectedFixItsCount(int fixItsCount); private: + void openEditorForCurrentIndex(); void suppressCurrentDiagnostic(); + void goNext() override; + void goBack() override; + enum Direction { Next = 1, Previous = -1 }; + QModelIndex getIndex(const QModelIndex &index, Direction direction) const; + QModelIndex getTopLevelIndex(const QModelIndex &index, Direction direction) const; + QList<QAction *> customActions() const override; bool eventFilter(QObject *watched, QEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; void setModel(QAbstractItemModel *theProxyModel) override; QAction *m_suppressAction; diff --git a/src/plugins/debugger/analyzer/detailederrorview.cpp b/src/plugins/debugger/analyzer/detailederrorview.cpp index 0d7fdc1732..079bf34ac1 100644 --- a/src/plugins/debugger/analyzer/detailederrorview.cpp +++ b/src/plugins/debugger/analyzer/detailederrorview.cpp @@ -108,6 +108,13 @@ void DetailedErrorView::goBack() setCurrentRow(prevRow >= 0 ? prevRow : rowCount() - 1); } +void DetailedErrorView::selectIndex(const QModelIndex &index) +{ + selectionModel()->setCurrentIndex(index, + QItemSelectionModel::ClearAndSelect + | QItemSelectionModel::Rows); +} + QVariant DetailedErrorView::locationData(int role, const DiagnosticLocation &location) { switch (role) { @@ -158,9 +165,7 @@ int DetailedErrorView::currentRow() const void DetailedErrorView::setCurrentRow(int row) { - const QModelIndex index = model()->index(row, 0); - selectionModel()->setCurrentIndex(index, - QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + selectIndex(model()->index(row, 0)); } } // namespace Debugger diff --git a/src/plugins/debugger/analyzer/detailederrorview.h b/src/plugins/debugger/analyzer/detailederrorview.h index 845bbee362..5ee08770fe 100644 --- a/src/plugins/debugger/analyzer/detailederrorview.h +++ b/src/plugins/debugger/analyzer/detailederrorview.h @@ -42,8 +42,10 @@ public: DetailedErrorView(QWidget *parent = nullptr); ~DetailedErrorView() override; - void goNext(); - void goBack(); + virtual void goNext(); + virtual void goBack(); + + void selectIndex(const QModelIndex &index); enum ItemRole { LocationRole = Qt::UserRole, |