Commit 274d095f authored by Christian Stenger's avatar Christian Stenger
Browse files

Use QC's TreeModel for TestResultModel



This changes the model to be a real tree instead of a list.
Additionally the results pane now displays the results as
tree as well.

Change-Id: I69ba7bbfcd75ce17c3a0d4052498d9c1c7382d43
Reviewed-by: default avatarNiels Weber <niels.weber@theqtcompany.com>
parent 292b4847
......@@ -91,6 +91,16 @@ Result::Type TestResult::toResultType(int rt)
return Result::MESSAGE_FATAL;
case Result::MESSAGE_INTERNAL:
return Result::MESSAGE_INTERNAL;
case Result::MESSAGE_TEST_CASE_START:
return Result::MESSAGE_TEST_CASE_START;
case Result::MESSAGE_TEST_CASE_SUCCESS:
return Result::MESSAGE_TEST_CASE_SUCCESS;
case Result::MESSAGE_TEST_CASE_WARN:
return Result::MESSAGE_TEST_CASE_WARN;
case Result::MESSAGE_TEST_CASE_FAIL:
return Result::MESSAGE_TEST_CASE_FAIL;
case Result::MESSAGE_TEST_CASE_END:
return Result::MESSAGE_TEST_CASE_END;
case Result::MESSAGE_CURRENT_TEST:
return Result::MESSAGE_CURRENT_TEST;
default:
......@@ -120,6 +130,11 @@ QString TestResult::resultToString(const Result::Type type)
case Result::MESSAGE_FATAL:
return QLatin1String("FATAL");
case Result::MESSAGE_INTERNAL:
case Result::MESSAGE_TEST_CASE_START:
case Result::MESSAGE_TEST_CASE_SUCCESS:
case Result::MESSAGE_TEST_CASE_WARN:
case Result::MESSAGE_TEST_CASE_FAIL:
case Result::MESSAGE_TEST_CASE_END:
case Result::MESSAGE_CURRENT_TEST:
return QString();
case Result::BLACKLISTED_PASS:
......@@ -155,6 +170,11 @@ QColor TestResult::colorForType(const Result::Type type)
case Result::MESSAGE_FATAL:
return QColor("#640000");
case Result::MESSAGE_INTERNAL:
case Result::MESSAGE_TEST_CASE_START:
case Result::MESSAGE_TEST_CASE_SUCCESS:
case Result::MESSAGE_TEST_CASE_WARN:
case Result::MESSAGE_TEST_CASE_FAIL:
case Result::MESSAGE_TEST_CASE_END:
case Result::MESSAGE_CURRENT_TEST:
return QColor("transparent");
default:
......
......@@ -41,6 +41,11 @@ enum Type {
MESSAGE_WARN,
MESSAGE_FATAL,
MESSAGE_INTERNAL,
MESSAGE_TEST_CASE_START,
MESSAGE_TEST_CASE_SUCCESS,
MESSAGE_TEST_CASE_WARN,
MESSAGE_TEST_CASE_FAIL,
MESSAGE_TEST_CASE_END,
MESSAGE_CURRENT_TEST,
UNKNOWN // ???
};
......@@ -65,6 +70,7 @@ public:
void setDescription(const QString &description) { m_description = description; }
void setFileName(const QString &fileName) { m_file = fileName; }
void setLine(int line) { m_line = line; }
void setResult(Result::Type type) { m_result = type; }
static Result::Type resultFromString(const QString &resultString);
static Result::Type toResultType(int rt);
......
......@@ -37,11 +37,6 @@ TestResultDelegate::TestResultDelegate(QObject *parent)
{
}
TestResultDelegate::~TestResultDelegate()
{
}
QString TestResultDelegate::outputString(const TestResult &testResult, bool selected)
{
const QString desc = testResult.description();
......@@ -88,7 +83,6 @@ void TestResultDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
painter->save();
QFontMetrics fm(opt.font);
QColor background;
QColor foreground;
const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(opt.widget);
......@@ -96,22 +90,23 @@ void TestResultDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
if (selected) {
painter->setBrush(opt.palette.highlight().color());
background = opt.palette.highlight().color();
foreground = opt.palette.highlightedText().color();
} else {
painter->setBrush(opt.palette.background().color());
background = opt.palette.background().color();
foreground = opt.palette.text().color();
}
painter->setPen(Qt::NoPen);
painter->drawRect(opt.rect);
painter->setPen(foreground);
TestResultFilterModel *resultFilterModel = static_cast<TestResultFilterModel *>(view->model());
TestResultModel *resultModel = static_cast<TestResultModel *>(resultFilterModel->sourceModel());
LayoutPositions positions(opt, resultModel);
TestResult testResult = resultModel->testResult(resultFilterModel->mapToSource(index));
LayoutPositions positions(opt, resultFilterModel);
const TestResult &testResult = resultFilterModel->testResult(index);
// draw the indicator by ourself as we paint across it with the delegate
QStyleOptionViewItemV4 indicatorOpt = option;
indicatorOpt.rect = QRect(0, opt.rect.y(), positions.indentation(), opt.rect.height());
opt.widget->style()->drawPrimitive(QStyle::PE_IndicatorBranch, &indicatorOpt, painter);
QIcon icon = index.data(Qt::DecorationRole).value<QIcon>();
if (!icon.isNull())
......@@ -163,7 +158,7 @@ void TestResultDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
}
painter->setClipRect(opt.rect);
painter->setPen(QColor::fromRgb(150, 150, 150));
painter->setPen(opt.palette.midlight().color());
painter->drawLine(0, opt.rect.bottom(), opt.rect.right(), opt.rect.bottom());
painter->restore();
}
......@@ -181,13 +176,12 @@ QSize TestResultDelegate::sizeHint(const QStyleOptionViewItem &option, const QMo
QFontMetrics fm(opt.font);
int fontHeight = fm.height();
TestResultFilterModel *resultFilterModel = static_cast<TestResultFilterModel *>(view->model());
TestResultModel *resultModel = static_cast<TestResultModel *>(resultFilterModel->sourceModel());
LayoutPositions positions(opt, resultModel);
LayoutPositions positions(opt, resultFilterModel);
QSize s;
s.setWidth(opt.rect.width());
if (selected) {
TestResult testResult = resultModel->testResult(resultFilterModel->mapToSource(index));
const TestResult &testResult = resultFilterModel->testResult(index);
QString output = outputString(testResult, selected);
output.replace(QLatin1Char('\n'), QChar::LineSeparator);
......
......@@ -33,7 +33,6 @@ class TestResultDelegate : public QStyledItemDelegate
Q_OBJECT
public:
explicit TestResultDelegate(QObject *parent = 0);
~TestResultDelegate();
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
......@@ -55,15 +54,19 @@ private:
class LayoutPositions
{
public:
LayoutPositions(QStyleOptionViewItemV4 &options, TestResultModel *model)
LayoutPositions(QStyleOptionViewItemV4 &options, TestResultFilterModel *filterModel)
: m_totalWidth(options.rect.width()),
m_maxFileLength(model->maxWidthOfFileName(options.font)),
m_maxLineLength(model->maxWidthOfLineNumber(options.font)),
m_realFileLength(m_maxFileLength),
m_top(options.rect.top()),
m_bottom(options.rect.bottom())
{
TestResultModel *srcModel = static_cast<TestResultModel *>(filterModel->sourceModel());
m_maxFileLength = srcModel->maxWidthOfFileName(options.font);
m_maxLineLength = srcModel->maxWidthOfLineNumber(options.font);
m_realFileLength = m_maxFileLength;
m_typeAreaWidth = QFontMetrics(options.font).width(QLatin1String("XXXXXXXX"));
m_indentation = options.widget ? options.widget->style()->pixelMetric(
QStyle::PM_TreeViewIndentation, &options) : 0;
m_level = srcModel->hasChildren(filterModel->mapToSource(options.index)) ? 1 : 2;
int flexibleArea = lineAreaLeft() - textAreaLeft() - ITEM_SPACING;
if (m_maxFileLength > flexibleArea / 2)
m_realFileLength = flexibleArea / 2;
......@@ -71,7 +74,7 @@ private:
}
int top() const { return m_top + ITEM_MARGIN; }
int left() const { return ITEM_MARGIN; }
int left() const { return ITEM_MARGIN + m_indentation * m_level; }
int right() const { return m_totalWidth - ITEM_MARGIN; }
int bottom() const { return m_bottom; }
int minimumHeight() const { return ICON_SIZE + 2 * ITEM_MARGIN; }
......@@ -80,17 +83,19 @@ private:
int fontHeight() const { return m_fontHeight; }
int typeAreaLeft() const { return left() + ICON_SIZE + ITEM_SPACING; }
int typeAreaWidth() const { return m_typeAreaWidth; }
int textAreaLeft() const { return typeAreaLeft() + m_typeAreaWidth + ITEM_SPACING; }
int textAreaLeft() const { return typeAreaLeft() + m_typeAreaWidth + ITEM_SPACING
+ (1 - m_level) * m_indentation; }
int textAreaWidth() const { return fileAreaLeft() - ITEM_SPACING - textAreaLeft(); }
int fileAreaLeft() const { return lineAreaLeft() - ITEM_SPACING - m_realFileLength; }
int lineAreaLeft() const { return right() - m_maxLineLength; }
int indentation() const { return m_indentation; }
QRect typeArea() const { return QRect(typeAreaLeft(), top(),
typeAreaWidth(), m_fontHeight); }
QRect textArea() const { return QRect(textAreaLeft(), top(),
fileAreaLeft() - ITEM_SPACING, m_fontHeight); }
textAreaWidth(), m_fontHeight); }
QRect fileArea() const { return QRect(fileAreaLeft(), top(),
lineAreaLeft() - ITEM_SPACING, m_fontHeight); }
m_realFileLength + ITEM_SPACING, m_fontHeight); }
QRect lineArea() const { return QRect(lineAreaLeft(), top(),
m_maxLineLength, m_fontHeight); }
......@@ -104,6 +109,8 @@ private:
int m_bottom;
int m_fontHeight;
int m_typeAreaWidth;
int m_level;
int m_indentation;
static const int ICON_SIZE = 16;
static const int ITEM_MARGIN = 2;
......
......@@ -19,47 +19,22 @@
#include "testresultmodel.h"
#include <QDebug>
#include <QFontMetrics>
#include <QIcon>
#include <QSortFilterProxyModel>
namespace Autotest {
namespace Internal {
TestResultModel::TestResultModel(QObject *parent) :
QAbstractItemModel(parent),
m_widthOfLineNumber(0),
m_maxWidthOfFileName(0),
m_lastMaxWidthIndex(0)
{
}
TestResultModel::~TestResultModel()
{
m_testResults.clear();
}
QModelIndex TestResultModel::index(int row, int column, const QModelIndex &parent) const
{
if (parent.isValid())
return QModelIndex();
return createIndex(row, column);
}
/********************************* TestResultItem ******************************************/
QModelIndex TestResultModel::parent(const QModelIndex &) const
TestResultItem::TestResultItem(TestResult *testResult)
: m_testResult(testResult)
{
return QModelIndex();
}
int TestResultModel::rowCount(const QModelIndex &parent) const
TestResultItem::~TestResultItem()
{
return parent.isValid() ? 0 : m_testResults.size();
}
int TestResultModel::columnCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : 1;
delete m_testResult;
}
static QIcon testResultIcon(Result::Type result) {
......@@ -77,116 +52,201 @@ static QIcon testResultIcon(Result::Type result) {
QIcon(QLatin1String(":/images/fatal.png")),
}; // provide an icon for unknown??
if (result < 0 || result >= Result::MESSAGE_INTERNAL)
return QIcon();
if (result < 0 || result >= Result::MESSAGE_INTERNAL) {
switch (result) {
case Result::MESSAGE_TEST_CASE_SUCCESS:
return icons[Result::PASS];
case Result::MESSAGE_TEST_CASE_FAIL:
return icons[Result::FAIL];
case Result::MESSAGE_TEST_CASE_WARN:
return icons[Result::MESSAGE_WARN];
default:
return QIcon();
}
}
return icons[result];
}
QVariant TestResultModel::data(const QModelIndex &index, int role) const
QVariant TestResultItem::data(int column, int role) const
{
if (!index.isValid() || index.row() >= m_testResults.count() || index.column() != 0)
return QVariant();
if (role == Qt::DisplayRole) {
const TestResult &tr = m_testResults.at(index.row());
switch (tr.result()) {
case Result::PASS:
case Result::FAIL:
case Result::EXPECTED_FAIL:
case Result::UNEXPECTED_PASS:
case Result::SKIP:
case Result::BLACKLISTED_PASS:
case Result::BLACKLISTED_FAIL:
case Result::BENCHMARK:
return QString::fromLatin1("%1::%2 (%3) - %4").arg(tr.className(), tr.testCase(),
tr.dataTag(), tr.fileName());
default:
return tr.description();
if (role == Qt::DecorationRole)
return m_testResult ? testResultIcon(m_testResult->result()) : QVariant();
return Utils::TreeItem::data(column, role);
}
void TestResultItem::updateDescription(const QString &description)
{
QTC_ASSERT(m_testResult, return);
m_testResult->setDescription(description);
}
void TestResultItem::updateResult()
{
if (m_testResult->result() != Result::MESSAGE_TEST_CASE_START)
return;
Result::Type newResult = Result::MESSAGE_TEST_CASE_SUCCESS;
foreach (Utils::TreeItem *child, children()) {
const TestResult *current = static_cast<TestResultItem *>(child)->testResult();
if (current) {
switch (current->result()) {
case Result::FAIL:
case Result::MESSAGE_FATAL:
case Result::UNEXPECTED_PASS:
m_testResult->setResult(Result::MESSAGE_TEST_CASE_FAIL);
return;
case Result::EXPECTED_FAIL:
case Result::MESSAGE_WARN:
case Result::SKIP:
case Result::BLACKLISTED_FAIL:
case Result::BLACKLISTED_PASS:
newResult = Result::MESSAGE_TEST_CASE_WARN;
break;
default: {}
}
}
}
if (role == Qt::DecorationRole) {
const TestResult &tr = m_testResults.at(index.row());
return testResultIcon(tr.result());
}
m_testResult->setResult(newResult);
}
return QVariant();
/********************************* TestResultModel *****************************************/
TestResultModel::TestResultModel(QObject *parent)
: Utils::TreeModel(parent),
m_widthOfLineNumber(0),
m_maxWidthOfFileName(0)
{
}
void TestResultModel::addTestResult(const TestResult &testResult)
QVariant TestResultModel::data(const QModelIndex &idx, int role) const
{
const bool isCurrentTestMssg = testResult.result() == Result::MESSAGE_CURRENT_TEST;
TestResult lastMssg = m_testResults.empty() ? TestResult() : m_testResults.last();
if (!idx.isValid())
return QVariant();
if (role == Qt::DecorationRole)
return itemForIndex(idx)->data(0, Qt::DecorationRole);
return QVariant();
}
int position = m_testResults.size();
void TestResultModel::addTestResult(TestResult *testResult, bool autoExpand)
{
const bool isCurrentTestMssg = testResult->result() == Result::MESSAGE_CURRENT_TEST;
if (isCurrentTestMssg && lastMssg.result() == Result::MESSAGE_CURRENT_TEST) {
lastMssg.setDescription(testResult.description());
m_testResults.replace(m_testResults.size() - 1, lastMssg);
const QModelIndex changed = index(m_testResults.size() - 1, 0, QModelIndex());
emit dataChanged(changed, changed);
QVector<Utils::TreeItem *> topLevelItems = rootItem()->children();
int lastRow = topLevelItems.size() - 1;
TestResultItem *newItem = new TestResultItem(testResult);
// we'll add the new item, so raising it's counter
if (!isCurrentTestMssg) {
int count = m_testResultCount.value(testResult->result(), 0);
m_testResultCount.insert(testResult->result(), ++count);
} else {
if (!isCurrentTestMssg && position) // decrement only if at least one other item
--position;
beginInsertRows(QModelIndex(), position, position);
m_testResults.insert(position, testResult);
endInsertRows();
// MESSAGE_CURRENT_TEST should always be the last top level item
if (lastRow >= 0) {
TestResultItem *current = static_cast<TestResultItem *>(topLevelItems.at(lastRow));
const TestResult *result = current->testResult();
if (result && result->result() == Result::MESSAGE_CURRENT_TEST) {
current->updateDescription(testResult->description());
emit dataChanged(current->index(), current->index());
return;
}
}
rootItem()->appendChild(newItem);
return;
}
if (!isCurrentTestMssg) {
int count = m_testResultCount.value(testResult.result(), 0);
m_testResultCount.insert(testResult.result(), ++count);
// FIXME this might be totally wrong... we need some more unique information!
for (int row = lastRow; row >= 0; --row) {
TestResultItem *current = static_cast<TestResultItem *>(topLevelItems.at(row));
const TestResult *result = current->testResult();
if (result && result->className() == testResult->className()) {
current->appendChild(newItem);
if (autoExpand)
current->expand();
if (testResult->result() == Result::MESSAGE_TEST_CASE_END) {
current->updateResult();
emit dataChanged(current->index(), current->index());
}
return;
}
}
// if we have a MESSAGE_CURRENT_TEST present, add the new top level item before it
if (lastRow >= 0) {
TestResultItem *current = static_cast<TestResultItem *>(topLevelItems.at(lastRow));
const TestResult *result = current->testResult();
if (result && result->result() == Result::MESSAGE_CURRENT_TEST) {
rootItem()->insertChild(current->index().row(), newItem);
return;
}
}
rootItem()->appendChild(newItem);
}
void TestResultModel::removeCurrentTestMessage()
{
TestResult lastMssg = m_testResults.empty() ? TestResult() : m_testResults.last();
if (lastMssg.result() == Result::MESSAGE_CURRENT_TEST) {
beginRemoveRows(QModelIndex(), m_testResults.size() - 1, m_testResults.size() - 1);
m_testResults.removeLast();
endRemoveRows();
QVector<Utils::TreeItem *> topLevelItems = rootItem()->children();
for (int row = topLevelItems.size() - 1; row >= 0; --row) {
TestResultItem *current = static_cast<TestResultItem *>(topLevelItems.at(row));
if (current->testResult()->result() == Result::MESSAGE_CURRENT_TEST) {
delete takeItem(current);
break;
}
}
}
void TestResultModel::clearTestResults()
{
if (m_testResults.size() == 0)
return;
beginRemoveRows(QModelIndex(), 0, m_testResults.size() - 1);
m_testResults.clear();
clear();
m_testResultCount.clear();
m_lastMaxWidthIndex = 0;
m_processedIndices.clear();
m_maxWidthOfFileName = 0;
m_widthOfLineNumber = 0;
endRemoveRows();
}
TestResult TestResultModel::testResult(const QModelIndex &index) const
TestResult TestResultModel::testResult(const QModelIndex &idx)
{
if (!index.isValid())
return TestResult(QString(), QString());
return m_testResults.at(index.row());
if (idx.isValid())
return *(static_cast<TestResultItem *>(itemForIndex(idx))->testResult());
return TestResult();
}
int TestResultModel::maxWidthOfFileName(const QFont &font)
{
int count = m_testResults.size();
if (count == 0)
return 0;
if (m_maxWidthOfFileName > 0 && font == m_measurementFont && m_lastMaxWidthIndex == count - 1)
return m_maxWidthOfFileName;
QFontMetrics fm(font);
m_measurementFont = font;
for (int i = m_lastMaxWidthIndex; i < count; ++i) {
QString filename = m_testResults.at(i).fileName();
const int pos = filename.lastIndexOf(QLatin1Char('/'));
if (pos != -1)
filename = filename.mid(pos +1);
m_maxWidthOfFileName = qMax(m_maxWidthOfFileName, fm.width(filename));
if (font != m_measurementFont) {
m_processedIndices.clear();
m_maxWidthOfFileName = 0;
m_measurementFont = font;
}
const QFontMetrics fm(font);
const QVector<Utils::TreeItem *> &topLevelItems = rootItem()->children();
const int count = topLevelItems.size();
for (int row = 0; row < count; ++row) {
int processed = row < m_processedIndices.size() ? m_processedIndices.at(row) : 0;
const QVector<Utils::TreeItem *> &children = topLevelItems.at(row)->children();
const int itemCount = children.size();
if (processed < itemCount) {
for (int childRow = processed; childRow < itemCount; ++childRow) {
const TestResultItem *item = static_cast<TestResultItem *>(children.at(childRow));
if (const TestResult *result = item->testResult()) {
QString fileName = result->fileName();
const int pos = fileName.lastIndexOf(QLatin1Char('/'));
if (pos != -1)
fileName = fileName.mid(pos + 1);
m_maxWidthOfFileName = qMax(m_maxWidthOfFileName, fm.width(fileName));
}
}
if (row < m_processedIndices.size())
m_processedIndices.replace(row, itemCount);
else
m_processedIndices.insert(row, itemCount);
}
}
m_lastMaxWidthIndex = count - 1;
return m_maxWidthOfFileName;
}
......@@ -200,11 +260,6 @@ int TestResultModel::maxWidthOfLineNumber(const QFont &font)
return m_widthOfLineNumber;
}
int TestResultModel::resultTypeCount(Result::Type type)
{
return m_testResultCount.value(type, 0);
}
/********************************** Filter Model **********************************/
TestResultFilterModel::TestResultFilterModel(TestResultModel *sourceModel, QObject *parent)
......@@ -222,7 +277,9 @@ void TestResultFilterModel::enableAllResultTypes()
<< Result::MESSAGE_WARN << Result::MESSAGE_INTERNAL
<< Result::MESSAGE_FATAL << Result::UNKNOWN << Result::BLACKLISTED_PASS
<< Result::BLACKLISTED_FAIL << Result::BENCHMARK
<< Result::MESSAGE_CURRENT_TEST;
<< Result::MESSAGE_CURRENT_TEST << Result::MESSAGE_TEST_CASE_START
<< Result::MESSAGE_TEST_CASE_SUCCESS << Result::MESSAGE_TEST_CASE_WARN
<< Result::MESSAGE_TEST_CASE_FAIL << Result::MESSAGE_TEST_CASE_END;
invalidateFilter();
}
......@@ -230,8 +287,12 @@ void TestResultFilterModel::toggleTestResultType(Result::Type type)
{
if (m_enabled.contains(type)) {
m_enabled.remove(type);
if (type == Result::MESSAGE_INTERNAL)
m_enabled.remove(Result::MESSAGE_TEST_CASE_END);
} else {
m_enabled.insert(type);
if (type == Result::MESSAGE_INTERNAL)
m_enabled.insert(Result::MESSAGE_TEST_CASE_END);
}
invalidateFilter();
}
......
......@@ -27,43 +27,47 @@
#include <QFont>
#include <QSet>
#include <utils/treemodel.h>
namespace Autotest {
namespace Internal {
class TestResultModel : public QAbstractItemModel
class TestResultItem : public Utils::TreeItem
{
public:
explicit TestResultItem(TestResult *testResult);
~TestResultItem();
QVariant data(int column, int role) const;
const TestResult *testResult() const { return m_testResult; }
void updateDescription(const QString &description);
void updateResult();
private:
TestResult *m_testResult;