forked from qt-creator/qt-creator
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: Nikolai Kosjar <nikolai.kosjar@theqtcompany.com>
This commit is contained in:
@@ -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 <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"
|
||||
|
||||
Reference in New Issue
Block a user