Commit 6406fd36 authored by Christian Kandeler's avatar Christian Kandeler

Analyzer: Re-design the diagnostics view.

The old one had a number of problems, mainly due to the awkward
delegate that was used for presenting the data. For instance:
    - Only one diagnostic at a time could be looked at
      in detail.
    - Once it had been opened, it was not possible to close
      such a detailed view again, other than by opening a new one.
We now use a tree view for showing the diagnostics, so users
can show and hide details about as many diagnostics as they
wish. That also gets us sensible item selection capabilities,
so features like suppressing several diagnostics at once can
be implemented in the future.

Change-Id: I840fdbfeca4d936ce600c8f6dde58b2ab93b0d00
Reviewed-by: default avatarNikolai Kosjar <nikolai.kosjar@theqtcompany.com>
parent 48b4509c
......@@ -14,6 +14,7 @@ SOURCES += \
analyzerrunconfigwidget.cpp \
analyzerutils.cpp \
detailederrorview.cpp \
diagnosticlocation.cpp \
startremotedialog.cpp
HEADERS += \
......@@ -27,6 +28,7 @@ HEADERS += \
analyzerrunconfigwidget.h \
analyzerutils.h \
detailederrorview.h \
diagnosticlocation.h \
startremotedialog.h
RESOURCES += \
......
......@@ -29,6 +29,8 @@ QtcPlugin {
"analyzerutils.h",
"detailederrorview.cpp",
"detailederrorview.h",
"diagnosticlocation.cpp",
"diagnosticlocation.h",
"ianalyzertool.cpp",
"ianalyzertool.h",
"startremotedialog.cpp",
......
......@@ -33,59 +33,12 @@
#include "analyzerbase_global.h"
#include <QListView>
#include <QTreeView>
#include <QStyledItemDelegate>
namespace Analyzer {
// Provides the details widget for the DetailedErrorView
class ANALYZER_EXPORT DetailedErrorDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
struct SummaryLineInfo {
QString errorText;
QString errorLocation;
};
public:
/// This delegate can only work on one view at a time, parent. parent will also be the parent
/// in the QObject parent-child system.
explicit DetailedErrorDelegate(QListView *parent);
virtual SummaryLineInfo summaryInfo(const QModelIndex &index) const = 0;
void copyToClipboard();
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
public slots:
void onCurrentSelectionChanged(const QModelIndex &now, const QModelIndex &previous);
void onViewResized();
void onLayoutChanged();
private slots:
void onVerticalScroll();
protected:
void openLinkInEditor(const QString &link);
mutable QPersistentModelIndex m_detailsIndex;
private:
// the constness of this function is a necessary lie because it is called from paint() const.
virtual QWidget *createDetailsWidget(const QFont &font, const QModelIndex &errorIndex,
QWidget *parent) const = 0;
virtual QString textualRepresentation() const = 0;
static const int s_itemMargin = 2;
mutable QWidget *m_detailsWidget;
mutable int m_detailsWidgetHeight;
};
// A QListView that displays additional details for the currently selected item
class ANALYZER_EXPORT DetailedErrorView : public QListView
class ANALYZER_EXPORT DetailedErrorView : public QTreeView
{
Q_OBJECT
......@@ -93,21 +46,18 @@ public:
DetailedErrorView(QWidget *parent = 0);
~DetailedErrorView();
// Reimplemented to connect delegate to connection model after it has
// been set by superclass implementation.
void setModel(QAbstractItemModel *model);
void setItemDelegate(QAbstractItemDelegate *delegate); // Takes ownership
void goNext();
void goBack();
signals:
void resized();
enum ItemRole {
LocationRole = Qt::UserRole,
FullTextRole
};
protected:
void updateGeometries();
void resizeEvent(QResizeEvent *e);
enum Column {
DiagnosticColumn,
LocationColumn,
};
private:
void contextMenuEvent(QContextMenuEvent *e) override;
......@@ -119,7 +69,7 @@ private:
QList<QAction *> commonActions() const;
virtual QList<QAction *> customActions() const;
QAction *m_copyAction;
QAction * const m_copyAction;
};
} // namespace Analyzer
......
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms and
** conditions see http://www.qt.io/terms-conditions. For further information
** use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include "diagnosticlocation.h"
namespace Analyzer {
DiagnosticLocation::DiagnosticLocation() : line(0), column(0)
{
}
DiagnosticLocation::DiagnosticLocation(const QString &filePath, int line, int column)
: filePath(filePath), line(line), column(column)
{
}
bool DiagnosticLocation::isValid() const
{
return !filePath.isEmpty();
}
bool operator==(const DiagnosticLocation &first, const DiagnosticLocation &second)
{
return first.filePath == second.filePath
&& first.line == second.line
&& first.column == second.column;
}
QDebug operator<<(QDebug dbg, const DiagnosticLocation &location)
{
dbg.nospace() << "Location(" << location.filePath << ", "
<< location.line << ", "
<< location.column << ')';
return dbg.space();
}
} // namespace Analyzer
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms and
** conditions see http://www.qt.io/terms-conditions. For further information
** use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#ifndef ANALYZERDIAGNOSTIC_H
#define ANALYZERDIAGNOSTIC_H
#include "analyzerbase_global.h"
#include <QDebug>
#include <QMetaType>
#include <QString>
namespace Analyzer {
class ANALYZER_EXPORT DiagnosticLocation
{
public:
DiagnosticLocation();
DiagnosticLocation(const QString &filePath, int line, int column);
bool isValid() const;
QString filePath;
// Both values start at 1.
int line;
int column;
};
ANALYZER_EXPORT bool operator==(const DiagnosticLocation &first, const DiagnosticLocation &second);
ANALYZER_EXPORT QDebug operator<<(QDebug dbg, const DiagnosticLocation &location);
} // namespace Analyzer
Q_DECLARE_METATYPE(Analyzer::DiagnosticLocation)
#endif // Include guard.
......@@ -47,245 +47,17 @@
#include <projectexplorer/session.h>
#include <utils/qtcassert.h>
#include <QDir>
#include <QDebug>
#include <QAction>
#include <QLabel>
#include <QPainter>
#include <QScrollBar>
#include <QSortFilterProxyModel>
#include <QStyledItemDelegate>
#include <QTextDocument>
#include <QVBoxLayout>
using namespace Valgrind::XmlProtocol;
namespace Valgrind {
namespace Internal {
class MemcheckErrorDelegate : public Analyzer::DetailedErrorDelegate
{
Q_OBJECT
public:
explicit MemcheckErrorDelegate(QListView *parent);
SummaryLineInfo summaryInfo(const QModelIndex &index) const;
private:
QWidget *createDetailsWidget(const QFont &font, const QModelIndex &errorIndex,
QWidget *parent) const;
QString textualRepresentation() const override;
};
static QString makeFrameName(const Frame &frame, const QString &relativeTo,
bool link = true, const QString &linkAttr = QString())
{
const QString d = frame.directory();
const QString f = frame.fileName();
const QString fn = frame.functionName();
const QString fullPath = frame.filePath();
QString path;
if (!d.isEmpty() && !f.isEmpty())
path = fullPath;
else
path = frame.object();
if (QFile::exists(path))
path = QFileInfo(path).canonicalFilePath();
if (path.startsWith(relativeTo))
path.remove(0, relativeTo.length());
if (frame.line() != -1)
path += QLatin1Char(':') + QString::number(frame.line());
// Since valgrind only runs on POSIX systems, converting path separators
// will ruin the paths on Windows. Leave it untouched.
path = path.toHtmlEscaped();
if (link && !f.isEmpty() && QFile::exists(fullPath)) {
// make a hyperlink label
path = QString::fromLatin1("<a href=\"file://%1:%2\" %4>%3</a>")
.arg(fullPath).arg(frame.line()).arg(path).arg(linkAttr);
}
if (!fn.isEmpty())
return QCoreApplication::translate("Valgrind::Internal", "%1 in %2").arg(fn.toHtmlEscaped(), path);
if (!path.isEmpty())
return path;
return QString::fromLatin1("0x%1").arg(frame.instructionPointer(), 0, 16);
}
static QString relativeToPath()
{
// The project for which we insert the snippet.
const ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
QString relativeTo(project ? project->projectDirectory().toString() : QDir::homePath());
const QChar slash = QLatin1Char('/');
if (!relativeTo.endsWith(slash))
relativeTo.append(slash);
return relativeTo;
}
static QString errorLocation(const QModelIndex &index, const Error &error,
bool link = false, bool absolutePath = false,
const QString &linkAttr = QString())
{
if (!index.isValid())
return QString();
const ErrorListModel *model = 0;
const QAbstractProxyModel *proxy = qobject_cast<const QAbstractProxyModel *>(index.model());
while (!model && proxy) {
model = qobject_cast<const ErrorListModel *>(proxy->sourceModel());
proxy = qobject_cast<const QAbstractProxyModel *>(proxy->sourceModel());
}
QTC_ASSERT(model, return QString());
const QString relativePath = absolutePath ? QString() : relativeToPath();
return QCoreApplication::translate("Valgrind::Internal", "in %1").
arg(makeFrameName(model->findRelevantFrame(error), relativePath,
link, linkAttr));
}
QWidget *MemcheckErrorDelegate::createDetailsWidget(const QFont & font,
const QModelIndex &errorIndex,
QWidget *parent) const
{
QWidget *widget = new QWidget(parent);
QTC_ASSERT(errorIndex.isValid(), return widget);
QVBoxLayout *layout = new QVBoxLayout;
// code + white-space:pre so the padding (see below) works properly
// don't include frameName here as it should wrap if required and pre-line is not supported
// by Qt yet it seems
const QString displayTextTemplate = QString::fromLatin1("<code style='white-space:pre'>%1:</code> %2");
const QString relativeTo = relativeToPath();
const Error error = errorIndex.data(ErrorListModel::ErrorRole).value<Error>();
QLabel *errorLabel = new QLabel();
errorLabel->setWordWrap(true);
errorLabel->setContentsMargins(0, 0, 0, 0);
errorLabel->setMargin(0);
errorLabel->setIndent(0);
QPalette p = errorLabel->palette();
QColor lc = p.color(QPalette::Text);
QString linkStyle = QString::fromLatin1("style=\"color:rgba(%1, %2, %3, %4);\"")
.arg(lc.red()).arg(lc.green()).arg(lc.blue()).arg(int(0.7 * 255));
p.setBrush(QPalette::Text, p.highlightedText());
errorLabel->setPalette(p);
errorLabel->setText(QString::fromLatin1("%1&nbsp;&nbsp;<span %4>%2</span>")
.arg(error.what(),
errorLocation(errorIndex, error, /*link=*/ true,
/*absolutePath=*/ false, linkStyle),
linkStyle));
connect(errorLabel, &QLabel::linkActivated, this, &MemcheckErrorDelegate::openLinkInEditor);
layout->addWidget(errorLabel);
const QVector<Stack> stacks = error.stacks();
for (int i = 0; i < stacks.count(); ++i) {
const Stack &stack = stacks.at(i);
// auxwhat for additional stacks
if (i > 0) {
QLabel *stackLabel = new QLabel(stack.auxWhat());
stackLabel->setWordWrap(true);
stackLabel->setContentsMargins(0, 0, 0, 0);
stackLabel->setMargin(0);
stackLabel->setIndent(0);
QPalette p = stackLabel->palette();
p.setBrush(QPalette::Text, p.highlightedText());
stackLabel->setPalette(p);
layout->addWidget(stackLabel);
}
int frameNr = 1;
foreach (const Frame &frame, stack.frames()) {
QString frameName = makeFrameName(frame, relativeTo);
QTC_ASSERT(!frameName.isEmpty(), /**/);
QLabel *frameLabel = new QLabel(widget);
frameLabel->setAutoFillBackground(true);
if (frameNr % 2 == 0) {
// alternating rows
QPalette p = frameLabel->palette();
p.setBrush(QPalette::Base, p.alternateBase());
frameLabel->setPalette(p);
}
QFont fixedPitchFont = font;
fixedPitchFont.setFixedPitch(true);
frameLabel->setFont(fixedPitchFont);
connect(frameLabel, &QLabel::linkActivated, this, &MemcheckErrorDelegate::openLinkInEditor);
// pad frameNr to 2 chars since only 50 frames max are supported by valgrind
const QString displayText = displayTextTemplate
.arg(frameNr++, 2).arg(frameName);
frameLabel->setText(displayText);
frameLabel->setToolTip(toolTipForFrame(frame));
frameLabel->setWordWrap(true);
frameLabel->setContentsMargins(0, 0, 0, 0);
frameLabel->setMargin(0);
frameLabel->setIndent(10);
layout->addWidget(frameLabel);
}
}
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
widget->setLayout(layout);
return widget;
}
MemcheckErrorDelegate::MemcheckErrorDelegate(QListView *parent)
: Analyzer::DetailedErrorDelegate(parent)
{
}
Analyzer::DetailedErrorDelegate::SummaryLineInfo MemcheckErrorDelegate::summaryInfo(
const QModelIndex &index) const
{
const Error error = index.data(ErrorListModel::ErrorRole).value<Error>();
SummaryLineInfo info;
info.errorText = error.what();
info.errorLocation = errorLocation(index, error);
return info;
}
QString MemcheckErrorDelegate::textualRepresentation() const
{
QTC_ASSERT(m_detailsIndex.isValid(), return QString());
QString content;
QTextStream stream(&content);
const Error error = m_detailsIndex.data(ErrorListModel::ErrorRole).value<Error>();
stream << error.what() << "\n";
stream << " "
<< errorLocation(m_detailsIndex, error, /*link=*/ false, /*absolutePath=*/ true)
<< "\n";
foreach (const Stack &stack, error.stacks()) {
if (!stack.auxWhat().isEmpty())
stream << stack.auxWhat();
int i = 1;
foreach (const Frame &frame, stack.frames())
stream << " " << i++ << ": " << makeFrameName(frame, QString(), false) << "\n";
}
stream.flush();
return content;
}
MemcheckErrorView::MemcheckErrorView(QWidget *parent)
: Analyzer::DetailedErrorView(parent),
m_settings(0)
{
MemcheckErrorDelegate *delegate = new MemcheckErrorDelegate(this);
setItemDelegate(delegate);
m_suppressAction = new QAction(this);
m_suppressAction->setText(tr("Suppress Error"));
m_suppressAction->setIcon(QIcon(QLatin1String(":/valgrind/images/eye_crossed.png")));
......@@ -343,5 +115,3 @@ QList<QAction *> MemcheckErrorView::customActions() const
} // namespace Internal
} // namespace Valgrind
#include "memcheckerrorview.moc"
......@@ -32,39 +32,25 @@
#ifndef LIBVALGRIND_PROTOCOL_ERRORLISTMODEL_H
#define LIBVALGRIND_PROTOCOL_ERRORLISTMODEL_H
#include <QAbstractItemModel>
#include <analyzerbase/detailederrorview.h>
#include <utils/treemodel.h>
#include <QSharedPointer>
namespace Valgrind {
namespace XmlProtocol {
class Error;
class ErrorListModelPrivate;
class Frame;
class ErrorListModel : public QAbstractItemModel
class ErrorListModel : public Utils::TreeModel
{
Q_OBJECT
public:
enum Column {
WhatColumn = 0,
LocationColumn,
AbsoluteFilePathColumn,
LineColumn,
UniqueColumn,
TidColumn,
KindColumn,
LeakedBlocksColumn,
LeakedBytesColumn,
HelgrindThreadIdColumn,
ColumnCount
};
enum Role {
ErrorRole = Qt::UserRole,
AbsoluteFilePathRole,
FileRole,
LineRole
ErrorRole = Analyzer::DetailedErrorView::FullTextRole + 1,
};
class RelevantFrameFinder
......@@ -80,26 +66,11 @@ public:
QSharedPointer<const RelevantFrameFinder> relevantFrameFinder() const;
void setRelevantFrameFinder(const QSharedPointer<const RelevantFrameFinder> &finder);
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex &child) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
Error error(const QModelIndex &index) const;
Frame findRelevantFrame(const Error &error) const;
void clear();
public slots:
void addError(const Valgrind::XmlProtocol::Error &error);
private:
class Private;
Private *const d;
ErrorListModelPrivate *const d;
};
} // namespace XmlProtocol
......
QTC_LIB_DEPENDS += utils ssh
QTC_PLUGIN_DEPENDS += projectexplorer
QTC_PLUGIN_DEPENDS += analyzerbase projectexplorer
include(../../qttest.pri)
include($$IDE_SOURCE_TREE/src/plugins/valgrind/valgrind_test.pri)
TARGET = tst_callgrindparsertests
......
QTC_LIB_DEPENDS += utils ssh
QTC_PLUGIN_DEPENDS += projectexplorer
QTC_PLUGIN_DEPENDS += analyzerbase projectexplorer
include(../../../../qtcreator.pri)
include(../../qttestrpath.pri)
include($$IDE_SOURCE_TREE/src/plugins/valgrind/valgrind_test.pri)
......
QTC_LIB_DEPENDS += utils ssh
QTC_PLUGIN_DEPENDS += projectexplorer
QTC_PLUGIN_DEPENDS += analyzerbase projectexplorer
include(../../qttest.pri)
include($$IDE_SOURCE_TREE/src/plugins/valgrind/valgrind_test.pri)
......
QTC_LIB_DEPENDS += utils ssh
QTC_PLUGIN_DEPENDS += projectexplorer
QTC_PLUGIN_DEPENDS += analyzerbase projectexplorer
include(../../qttest.pri)
include($$IDE_SOURCE_TREE/src/plugins/valgrind/valgrind_test.pri)
......
import qbs
QtcAutotest {
Depends { name: "AnalyzerBase" }
Depends { name: "QtcSsh" }
Depends { name: "Utils" }
Depends { name: "ProjectExplorer" }
......