forked from qt-creator/qt-creator
Add callgrind plugin.
Merge-request: 284 Reviewed-by: hjk <qtc-committer@nokia.com>
This commit is contained in:
502
src/plugins/callgrind/callgrindvisualisation.cpp
Normal file
502
src/plugins/callgrind/callgrindvisualisation.cpp
Normal file
@@ -0,0 +1,502 @@
|
||||
/**************************************************************************
|
||||
**
|
||||
** This file is part of Qt Creator
|
||||
**
|
||||
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
**
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** No Commercial Usage
|
||||
**
|
||||
** This file contains pre-release code and may not be distributed.
|
||||
** You may use this file in accordance with the terms and conditions
|
||||
** contained in the Technology Preview License Agreement accompanying
|
||||
** this package.
|
||||
**
|
||||
** 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 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Nokia gives you certain additional
|
||||
** rights. These rights are described in the Nokia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** If you have questions regarding the use of this file, please contact
|
||||
** Nokia at qt-info@nokia.com.
|
||||
**
|
||||
**************************************************************************/
|
||||
|
||||
#include "callgrindvisualisation.h"
|
||||
|
||||
#include "callgrindhelper.h"
|
||||
|
||||
#include <valgrind/callgrind/callgrindabstractmodel.h>
|
||||
#include <valgrind/callgrind/callgrinddatamodel.h>
|
||||
#include <valgrind/callgrind/callgrindfunction.h>
|
||||
#include <valgrind/callgrind/callgrindproxymodel.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QDebug>
|
||||
#include <QGraphicsRectItem>
|
||||
#include <QGraphicsScene>
|
||||
#include <QGraphicsSimpleTextItem>
|
||||
#include <QMouseEvent>
|
||||
#include <QStaticText>
|
||||
#include <QStyleOptionGraphicsItem>
|
||||
#include <QPair>
|
||||
#include <QPersistentModelIndex>
|
||||
#include <QLinkedList>
|
||||
|
||||
#define VISUALISATION_DEBUG 0
|
||||
// Margin from hardcoded value in:
|
||||
// QGraphicsView::fitInView(const QRectF &rect,
|
||||
// Qt::AspectRatioMode aspectRatioMode)
|
||||
// Bug report here: http://bugreports.qt.nokia.com/browse/QTBUG-11945
|
||||
#define FIT_IN_VIEW_MARGIN 2;
|
||||
|
||||
using namespace Valgrind::Callgrind;
|
||||
|
||||
namespace Callgrind {
|
||||
namespace Internal {
|
||||
|
||||
class FunctionGraphicsTextItem : public QAbstractGraphicsShapeItem
|
||||
{
|
||||
public:
|
||||
FunctionGraphicsTextItem(const QString &text, QGraphicsItem *parent);
|
||||
|
||||
virtual void paint(QPainter *painter,
|
||||
const QStyleOptionGraphicsItem *option, QWidget *widget);
|
||||
virtual QRectF boundingRect() const;
|
||||
|
||||
void setRotateText(bool rotate);
|
||||
|
||||
private:
|
||||
QString m_text;
|
||||
QStaticText m_staticText;
|
||||
bool m_rotateText;
|
||||
QColor m_textColor;
|
||||
qreal m_previousViewportDimension;
|
||||
};
|
||||
|
||||
class FunctionGraphicsItem : public QGraphicsRectItem
|
||||
{
|
||||
public:
|
||||
enum DataKey {
|
||||
FunctionCallKey
|
||||
};
|
||||
|
||||
FunctionGraphicsItem(const QString &text, qreal x, qreal y,
|
||||
qreal width, qreal height, QGraphicsItem *parent = 0);
|
||||
|
||||
virtual void paint(QPainter *painter,
|
||||
const QStyleOptionGraphicsItem *option, QWidget *widget);
|
||||
FunctionGraphicsTextItem *textItem() const;
|
||||
|
||||
private:
|
||||
FunctionGraphicsTextItem *m_text;
|
||||
};
|
||||
|
||||
FunctionGraphicsTextItem::FunctionGraphicsTextItem(const QString &text,
|
||||
QGraphicsItem *parent)
|
||||
: QAbstractGraphicsShapeItem(parent)
|
||||
, m_text(text)
|
||||
, m_rotateText(true)
|
||||
, m_previousViewportDimension(0)
|
||||
{
|
||||
setFlag(QGraphicsItem::ItemIgnoresTransformations);
|
||||
setAcceptedMouseButtons(0); // do not steal focus from parent item
|
||||
setToolTip(text);
|
||||
}
|
||||
|
||||
void FunctionGraphicsTextItem::setRotateText(bool rotate)
|
||||
{
|
||||
m_rotateText = rotate;
|
||||
}
|
||||
|
||||
void FunctionGraphicsTextItem::paint(QPainter *painter,
|
||||
const QStyleOptionGraphicsItem *,
|
||||
QWidget *widget)
|
||||
{
|
||||
const qreal textHeight = painter->fontMetrics().height();
|
||||
// Magic number based on what looked best.
|
||||
const int margin = 2 + FIT_IN_VIEW_MARGIN;
|
||||
const QRectF viewportRect =
|
||||
widget->rect().adjusted(margin, margin, -margin, -margin);
|
||||
|
||||
const qreal maxWidth = viewportRect.width()
|
||||
* parentItem()->boundingRect().width()
|
||||
/ scene()->sceneRect().width();
|
||||
const qreal maxHeight = viewportRect.height()
|
||||
* parentItem()->boundingRect().height()
|
||||
/ scene()->sceneRect().height();
|
||||
|
||||
qreal textMaxHeight;
|
||||
qreal textMaxWidth;
|
||||
qreal viewportDim;
|
||||
if (m_rotateText) {
|
||||
viewportDim = viewportRect.height();
|
||||
textMaxHeight = maxWidth;
|
||||
textMaxWidth = maxHeight;
|
||||
} else {
|
||||
viewportDim = viewportRect.width();
|
||||
textMaxHeight = maxHeight;
|
||||
textMaxWidth = maxWidth;
|
||||
}
|
||||
|
||||
if (textHeight > textMaxHeight)
|
||||
return;
|
||||
|
||||
if (viewportDim != m_previousViewportDimension) {
|
||||
const QString &elidedText =
|
||||
painter->fontMetrics().elidedText(m_text, Qt::ElideRight,
|
||||
textMaxWidth);
|
||||
m_staticText.setText(elidedText);
|
||||
m_staticText.prepare();
|
||||
|
||||
m_previousViewportDimension = viewportDim;
|
||||
}
|
||||
|
||||
#if VISUALISATION_DEBUG
|
||||
painter->setPen(Qt::red);
|
||||
painter->drawRect(boundingRect());
|
||||
#endif
|
||||
|
||||
painter->save();
|
||||
int textLeft = 0;
|
||||
int textTop = 0;
|
||||
if (m_rotateText) {
|
||||
painter->rotate(90);
|
||||
textLeft = 2;
|
||||
textTop = -textHeight/2;
|
||||
}
|
||||
else {
|
||||
const int textWidth = painter->fontMetrics().width(m_staticText.text());
|
||||
textLeft = -textWidth/2;
|
||||
textTop = (maxHeight - textHeight)/2;
|
||||
}
|
||||
painter->drawStaticText(textLeft, textTop, m_staticText);
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
QRectF FunctionGraphicsTextItem::boundingRect() const
|
||||
{
|
||||
return mapRectFromParent(parentItem()->boundingRect());
|
||||
}
|
||||
|
||||
FunctionGraphicsItem::FunctionGraphicsItem(const QString &text,
|
||||
qreal x, qreal y, qreal width,
|
||||
qreal height, QGraphicsItem *parent)
|
||||
: QGraphicsRectItem(x, y, width, height, parent)
|
||||
, m_text(0)
|
||||
{
|
||||
setFlag(QGraphicsItem::ItemIsSelectable);
|
||||
setFlag(QGraphicsItem::ItemClipsToShape);
|
||||
setFlag(QGraphicsItem::ItemClipsChildrenToShape);
|
||||
setToolTip(text);
|
||||
|
||||
m_text = new FunctionGraphicsTextItem(text, this);
|
||||
m_text->setPos(rect().center().x(), y);
|
||||
}
|
||||
|
||||
FunctionGraphicsTextItem *FunctionGraphicsItem::textItem() const
|
||||
{
|
||||
return m_text;
|
||||
}
|
||||
|
||||
void FunctionGraphicsItem::paint(QPainter *painter,
|
||||
const QStyleOptionGraphicsItem *option,
|
||||
QWidget *)
|
||||
{
|
||||
painter->save();
|
||||
|
||||
QRectF rect = this->rect();
|
||||
const QColor &color = brush().color();
|
||||
if (option->state & QStyle::State_Selected) {
|
||||
QLinearGradient gradient(0, 0, rect.width(), rect.height());
|
||||
gradient.setColorAt(0, color.lighter(250));
|
||||
gradient.setColorAt(1, color.darker());
|
||||
painter->setBrush(gradient);
|
||||
}
|
||||
else {
|
||||
painter->setBrush(color);
|
||||
}
|
||||
|
||||
#if VISUALISATION_DEBUG
|
||||
painter->setPen(Qt::blue);
|
||||
painter->drawRect(boundingRect());
|
||||
#endif
|
||||
|
||||
QPen pen = painter->pen();
|
||||
pen.setColor(color.darker());
|
||||
pen.setWidthF(0.5);
|
||||
painter->setPen(pen);
|
||||
qreal halfPenWidth = pen.widthF()/2.0;
|
||||
rect.adjust(halfPenWidth, halfPenWidth, -halfPenWidth, -halfPenWidth);
|
||||
painter->drawRect(rect);
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
class Visualisation::Private
|
||||
{
|
||||
public:
|
||||
Private(Visualisation *qq);
|
||||
~Private();
|
||||
|
||||
void handleMousePressEvent(QMouseEvent *event, bool doubleClicked);
|
||||
qreal sceneHeight() const;
|
||||
qreal sceneWidth() const;
|
||||
|
||||
Visualisation *q;
|
||||
DataProxyModel *m_model;
|
||||
QGraphicsScene m_scene;
|
||||
int m_modelColumn;
|
||||
};
|
||||
|
||||
Visualisation::Private::Private(Visualisation *qq)
|
||||
: q(qq)
|
||||
, m_model(new DataProxyModel(qq))
|
||||
, m_modelColumn(-1)
|
||||
{
|
||||
// setup scene
|
||||
m_scene.setObjectName("Visualisation Scene");
|
||||
///NOTE: with size 100x100 the Qt-internal mouse selection fails...
|
||||
m_scene.setSceneRect(0, 0, 1024, 1024);
|
||||
|
||||
// setup model
|
||||
m_model->setMinimumInclusiveCostRatio(0.1);
|
||||
connect(m_model,
|
||||
SIGNAL(filterFunctionChanged(const Function*,const Function*)),
|
||||
qq, SLOT(populateScene()));
|
||||
}
|
||||
|
||||
Visualisation::Private::~Private()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Visualisation::Private::handleMousePressEvent(QMouseEvent *event,
|
||||
bool doubleClicked)
|
||||
{
|
||||
// find the first item that accepts mouse presses under the cursor position
|
||||
QGraphicsItem *itemAtPos = 0;
|
||||
foreach(QGraphicsItem *item, q->items(event->pos())) {
|
||||
if (!(item->acceptedMouseButtons() & event->button()))
|
||||
continue;
|
||||
|
||||
itemAtPos = item;
|
||||
break;
|
||||
}
|
||||
|
||||
// if there is an item, select it
|
||||
if (itemAtPos) {
|
||||
const Function *func = q->functionForItem(itemAtPos);
|
||||
|
||||
if (doubleClicked) {
|
||||
q->functionActivated(func);
|
||||
}
|
||||
else {
|
||||
q->scene()->clearSelection();
|
||||
itemAtPos->setSelected(true);
|
||||
q->functionSelected(func);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
qreal Visualisation::Private::sceneHeight() const
|
||||
{
|
||||
return m_scene.height() - FIT_IN_VIEW_MARGIN;
|
||||
}
|
||||
|
||||
qreal Visualisation::Private::sceneWidth() const
|
||||
{
|
||||
// Magic number to improve margins appearance
|
||||
return m_scene.width() + 1;
|
||||
}
|
||||
|
||||
Visualisation::Visualisation(QWidget *parent)
|
||||
: QGraphicsView(parent)
|
||||
, d(new Private(this))
|
||||
{
|
||||
setObjectName("Visualisation View");
|
||||
setScene(&d->m_scene);
|
||||
setRenderHint(QPainter::Antialiasing);
|
||||
}
|
||||
|
||||
Visualisation::~Visualisation()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
const Function *Visualisation::functionForItem(QGraphicsItem *item) const
|
||||
{
|
||||
return item->data(FunctionGraphicsItem::FunctionCallKey).value<const Function *>();
|
||||
}
|
||||
|
||||
QGraphicsItem *Visualisation::itemForFunction(const Function *function) const
|
||||
{
|
||||
foreach(QGraphicsItem *item, items()) {
|
||||
if (functionForItem(item) == function)
|
||||
return item;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Visualisation::setFunction(const Function *function)
|
||||
{
|
||||
d->m_model->setFilterFunction(function);
|
||||
}
|
||||
|
||||
const Function *Visualisation::function() const
|
||||
{
|
||||
return d->m_model->filterFunction();
|
||||
}
|
||||
|
||||
void Visualisation::setModel(DataModel *model)
|
||||
{
|
||||
QTC_ASSERT(!d->m_model->sourceModel() && model, return); // only set once!
|
||||
d->m_model->setSourceModel(model);
|
||||
|
||||
connect(model,
|
||||
SIGNAL(columnsInserted(const QModelIndex&, int, int)),
|
||||
SLOT(populateScene()));
|
||||
connect(model,
|
||||
SIGNAL(columnsMoved(const QModelIndex&, int, int, const QModelIndex&, int)),
|
||||
SLOT(populateScene()));
|
||||
connect(model,
|
||||
SIGNAL(columnsRemoved(const QModelIndex&, int, int)),
|
||||
SLOT(populateScene()));
|
||||
connect(model,
|
||||
SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
|
||||
SLOT(populateScene()));
|
||||
connect(model,
|
||||
SIGNAL(headerDataChanged(Qt::Orientation, int, int)),
|
||||
SLOT(populateScene()));
|
||||
connect(model, SIGNAL(layoutChanged()), SLOT(populateScene()));
|
||||
connect(model, SIGNAL(modelReset()), SLOT(populateScene()));
|
||||
connect(model,
|
||||
SIGNAL(rowsInserted(const QModelIndex&, int, int)),
|
||||
SLOT(populateScene()));
|
||||
connect(model,
|
||||
SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int)),
|
||||
SLOT(populateScene()));
|
||||
connect(model,
|
||||
SIGNAL(rowsRemoved(const QModelIndex&, int, int)),
|
||||
SLOT(populateScene()));
|
||||
|
||||
populateScene();
|
||||
}
|
||||
|
||||
void Visualisation::setText(const QString &message)
|
||||
{
|
||||
d->m_scene.clear();
|
||||
|
||||
QGraphicsSimpleTextItem *textItem = d->m_scene.addSimpleText(message);
|
||||
textItem->setBrush(palette().foreground());
|
||||
textItem->setPos((d->sceneWidth() - textItem->boundingRect().width()) / 2,
|
||||
(d->sceneHeight() - textItem->boundingRect().height()) / 2);
|
||||
textItem->setFlag(QGraphicsItem::ItemIgnoresTransformations);
|
||||
}
|
||||
|
||||
void Visualisation::populateScene()
|
||||
{
|
||||
// reset scene first
|
||||
d->m_scene.clear();
|
||||
|
||||
const qreal sceneWidth = d->sceneWidth();
|
||||
const qreal sceneHeight = d->sceneHeight();
|
||||
|
||||
// cache costs of each element, calculate total costs
|
||||
qreal total = 0;
|
||||
|
||||
typedef QPair<QModelIndex, qreal> Pair;
|
||||
QLinkedList<Pair> costs;
|
||||
for (int row=0; row < d->m_model->rowCount(); ++row)
|
||||
{
|
||||
const QModelIndex index = d->m_model->index(row, DataModel::InclusiveCostColumn);
|
||||
|
||||
bool ok = false;
|
||||
const qreal cost = index.data().toReal(&ok);
|
||||
QTC_ASSERT(ok, continue);
|
||||
costs << QPair<QModelIndex, qreal>(d->m_model->index(row, 0), cost);
|
||||
total += cost;
|
||||
}
|
||||
|
||||
if (!costs.isEmpty() || d->m_model->filterFunction()) {
|
||||
// item showing the current filter function
|
||||
|
||||
QString text;
|
||||
if (d->m_model->filterFunction())
|
||||
text = d->m_model->filterFunction()->name();
|
||||
else {
|
||||
const float ratioPercent = d->m_model->minimumInclusiveCostRatio() * 100;
|
||||
QString ratioPercentString = QString::number(ratioPercent);
|
||||
ratioPercentString.append(QLocale::system().percent());
|
||||
const int hiddenFunctions = d->m_model->sourceModel()->rowCount() - d->m_model->rowCount();
|
||||
text = tr("All functions with an inclusive cost ratio higher than %1 (%2 are hidden)")
|
||||
.arg(ratioPercentString)
|
||||
.arg(hiddenFunctions);
|
||||
}
|
||||
|
||||
const qreal height = sceneHeight* (costs.isEmpty() ? 1.0 : 0.1);
|
||||
FunctionGraphicsItem *item = new FunctionGraphicsItem(text, 0, 0, sceneWidth, height);
|
||||
const QColor background = CallgrindHelper::colorForString(text);
|
||||
item->setBrush(background);
|
||||
item->setData(FunctionGraphicsItem::FunctionCallKey, QVariant::fromValue(d->m_model->filterFunction()));
|
||||
item->textItem()->setRotateText(false);
|
||||
// NOTE: workaround wrong tooltip being show, no idea why...
|
||||
item->setZValue(-1);
|
||||
d->m_scene.addItem(item);
|
||||
}
|
||||
|
||||
// add the canvas elements to the scene
|
||||
qreal used = 0;
|
||||
foreach(const Pair &cost, costs)
|
||||
{
|
||||
const QModelIndex &index = cost.first;
|
||||
const QString text = index.data().toString();
|
||||
|
||||
const qreal width = (sceneWidth * cost.second) / total;
|
||||
const qreal height = sceneHeight * 0.9;
|
||||
|
||||
FunctionGraphicsItem *item = new FunctionGraphicsItem(text, used, sceneHeight - height, width, height);
|
||||
const QColor background = CallgrindHelper::colorForString(text);
|
||||
item->setBrush(background);
|
||||
item->setData(FunctionGraphicsItem::FunctionCallKey, index.data(DataModel::FunctionRole));
|
||||
d->m_scene.addItem(item);
|
||||
used += width;
|
||||
}
|
||||
}
|
||||
|
||||
void Visualisation::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
d->handleMousePressEvent(event, false);
|
||||
|
||||
QGraphicsView::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void Visualisation::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
{
|
||||
d->handleMousePressEvent(event, true);
|
||||
|
||||
QGraphicsView::mouseDoubleClickEvent(event);
|
||||
}
|
||||
|
||||
void Visualisation::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
fitInView(sceneRect());
|
||||
|
||||
QGraphicsView::resizeEvent(event);
|
||||
}
|
||||
|
||||
|
||||
} // Internal
|
||||
} // Callgrind
|
||||
Reference in New Issue
Block a user