/* This file is part of the KDE project.
Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
This library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2.1 or 3 of the License.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QByteArray>
#include <QDebug>
#include <QHash>
#include <QTextStream>
#include <QWidget>
#include "objectdump.h"
#include "objecttree.h"
QT_BEGIN_NAMESPACE
namespace ObjectDump
{
//-----------------------------------------------------------------------------
// QObjectAnnotator
//-----------------------------------------------------------------------------
QAnnotator::~QAnnotator()
{
}
//-----------------------------------------------------------------------------
// Annotators
//-----------------------------------------------------------------------------
QList<QByteArray> QAnnotatorBasic::annotation(const QObject& object)
{
QList<QByteArray> result;
QByteArray array;
QTextStream stream(&array);
stream << '[' << &object << ']';
stream << ' ';
stream << object.metaObject()->className();
if (object.objectName() != "")
stream << " \"" << object.objectName() << '"';
if (object.isWidgetType())
stream << " isWidget";
stream.flush();
result.append(array);
return result;
}
QList<QByteArray> QAnnotatorWidget::annotation(const QObject& object)
{
QList<QByteArray> result;
const QWidget* widget = qobject_cast<const QWidget*>(&object);
if (widget) {
QByteArray array;
QTextStream stream(&array);
stream << "widget: ";
if (widget->isVisible())
stream << "visible ";
else
stream << "invisible ";
stream << widget->x() << ',' << widget->y() << ' ';
stream << widget->size().width() << 'x'<< widget->size().height() << ' ';
stream << "hint " << widget->sizeHint().width() << 'x' << widget->sizeHint().height();
stream.flush();
result.append(array);
}
return result;
}
//-----------------------------------------------------------------------------
// Base class for QDumperPrivate, QVisitorPrivate
//-----------------------------------------------------------------------------
class QDumperBase
{
public:
QDumperBase();
~QDumperBase();
void setPrefix(const QString& prefix);
void addAnnotator(QAnnotator* annotator);
protected:
QByteArray m_prefix;
QList<QAnnotator*> m_annotators;
};
QDumperBase::QDumperBase()
{
}
QDumperBase::~QDumperBase()
{
QAnnotator* annotator;
foreach(annotator, m_annotators)
delete annotator;
}
void QDumperBase::setPrefix(const QString& prefix)
{
m_prefix = prefix.count()
? (prefix + " ").toAscii()
: prefix.toAscii();
}
void QDumperBase::addAnnotator(QAnnotator* annotator)
{
// Protect against an exception occurring during QList::append
QScopedPointer<QAnnotator> holder(annotator);
m_annotators.append(annotator);
holder.take();
}
//-----------------------------------------------------------------------------
// QDumper
//-----------------------------------------------------------------------------
class QDumperPrivate : public QDumperBase
{
public:
QDumperPrivate();
~QDumperPrivate();
void dumpObject(const QObject& object);
};
QDumperPrivate::QDumperPrivate()
{
}
QDumperPrivate::~QDumperPrivate()
{
}
void QDumperPrivate::dumpObject(const QObject& object)
{
QAnnotator* annotator;
foreach(annotator, m_annotators) {
const QList<QByteArray> annotations = annotator->annotation(object);
QByteArray annotation;
foreach(annotation, annotations) {
QByteArray buffer(m_prefix);
buffer.append(annotation);
qDebug() << buffer.constData();
}
}
}
QDumper::QDumper()
: d_ptr(new QDumperPrivate)
{
}
QDumper::~QDumper()
{
}
void QDumper::setPrefix(const QString& prefix)
{
d_func()->setPrefix(prefix);
}
void QDumper::addAnnotator(QAnnotator* annotator)
{
d_func()->addAnnotator(annotator);
}
void QDumper::dumpObject(const QObject& object)
{
d_func()->dumpObject(object);
}
//-----------------------------------------------------------------------------
// QVisitor
//-----------------------------------------------------------------------------
class QVisitorPrivate : public QDumperBase
{
public:
QVisitorPrivate();
~QVisitorPrivate();
void setIndent(unsigned indent);
void visitNode(const QObject& object);
void visitComplete();
private:
class Node
{
public:
Node();
~Node();
QList<QByteArray> m_annotation;
QList<Node*> m_children;
typedef QList<Node*>::const_iterator child_iterator;
};
private:
Node* findNode(const QObject* object) const;
QByteArray branchBuffer(const QList<bool>& branches, bool isNodeLine, bool isLastChild) const;
void dumpRecursive(const Node& node, QList<bool> branches, bool isLastChild);
void dumpNode(const Node& node, const QList<bool>& branches, bool isLastChild);
private:
unsigned m_indent;
QScopedPointer<Node> m_root;
// Hash table used to associate internal nodes with QObjects
typedef QHash<const QObject*, Node*> Hash;
Hash m_hash;
};
static const unsigned DefaultIndent = 2;
QVisitorPrivate::QVisitorPrivate()
: m_indent(DefaultIndent)
{
}
QVisitorPrivate::~QVisitorPrivate()
{
}
void QVisitorPrivate::setIndent(unsigned indent)
{
m_indent = indent;
}
// Builds up a mirror of the object tree, rooted in m_root, with each node
// storing annotations generated by
void QVisitorPrivate::visitNode(const QObject& object)
{
QObject* const objectParent = object.parent();
Node* const nodeParent = objectParent ? findNode(objectParent) : 0;
// Create a new node and store in scoped pointer for exception safety
Node* node = new Node;
QScopedPointer<Node> nodePtr(node);
// Associate node with QObject
m_hash.insert(&object, node);
// Insert node into internal tree
if (nodeParent)
{
nodeParent->m_children.append(nodePtr.take());
}
else
{
Q_ASSERT(m_root.isNull());
m_root.reset(nodePtr.take());
}
// Generate and store annotations
QAnnotator* annotator;
foreach(annotator, m_annotators)
node->m_annotation.append( annotator->annotation(object) );
}
void QVisitorPrivate::visitComplete()
{
QList<bool> branches;
static const bool isLastChild = true;
dumpRecursive(*m_root, branches, isLastChild);
m_root.reset(0);
}
QVisitorPrivate::Node* QVisitorPrivate::findNode(const QObject* object) const
{
Hash::const_iterator i = m_hash.find(object);
return (m_hash.end() == i) ? 0 : *i;
}
QByteArray QVisitorPrivate::branchBuffer
(const QList<bool>& branches, bool isNodeLine, bool isLastChild) const
{
const int depth = branches.count();
const QByteArray indent(m_indent, ' ');
const QByteArray horiz(m_indent, '-');
QByteArray buffer;
QTextStream stream(&buffer);
for (int i=0; i<depth-1; ++i) {
if (branches[i])
stream << '|';
else
stream << ' ';
stream << indent;
}
if (depth) {
if (isNodeLine)
stream << '+' << horiz;
else {
if (!isLastChild)
stream << '|';
else
stream << ' ';
stream << indent;
}
}
stream.flush();
buffer.push_front(m_prefix);
return buffer;
}
void QVisitorPrivate::dumpRecursive
(const Node& node, QList<bool> branches, bool isLastChild)
{
dumpNode(node, branches, isLastChild);
// Recurse down tree
const Node::child_iterator begin = node.m_children.begin();
const Node::child_iterator end = node.m_children.end();
for (Node::child_iterator i = begin; end != i; ++i) {
isLastChild = (end == i + 1);
if (begin == i)
branches.push_back(!isLastChild);
else
branches.back() = !isLastChild;
static const bool isNodeLine = false;
const QByteArray buffer = branchBuffer(branches, isNodeLine, false);
qDebug() << buffer.constData();
dumpRecursive(**i, branches, isLastChild);
}
}
void QVisitorPrivate::dumpNode
(const Node& node, const QList<bool>& branches, bool isLastChild)
{
const QList<QByteArray>::const_iterator
begin = node.m_annotation.begin(), end = node.m_annotation.end();
if (begin == end) {
// No annotations - just dump the object pointer
const bool isNodeLine = true;
QByteArray buffer = branchBuffer(branches, isNodeLine, isLastChild);
qDebug() << 0;
}
else {
// Dump annotations
for (QList<QByteArray>::const_iterator i = begin; end != i; ++i) {
const bool isNodeLine = (begin == i);
QByteArray buffer = branchBuffer(branches, isNodeLine, isLastChild);
buffer.append(*i);
qDebug() << buffer.constData();
}
}
}
// QVisitorPrivate::Node
QVisitorPrivate::Node::Node()
{
}
QVisitorPrivate::Node::~Node()
{
Node* child;
foreach(child, m_children)
delete child;
}
// QVisitor
QVisitor::QVisitor()
: d_ptr(new QVisitorPrivate)
{
}
QVisitor::~QVisitor()
{
}
void QVisitor::setPrefix(const QString& prefix)
{
d_func()->setPrefix(prefix);
}
void QVisitor::setIndent(unsigned indent)
{
d_func()->setIndent(indent);
}
void QVisitor::addAnnotator(QAnnotator* annotator)
{
d_func()->addAnnotator(annotator);
}
void QVisitor::visitPrepare()
{
// Do nothing
}
void QVisitor::visitNode(const QObject& object)
{
d_func()->visitNode(object);
}
void QVisitor::visitComplete()
{
d_func()->visitComplete();
}
//-----------------------------------------------------------------------------
// Utility functions
//-----------------------------------------------------------------------------
void addDefaultAnnotators_sys(QDumper& visitor);
void addDefaultAnnotators_sys(QVisitor& visitor);
void addDefaultAnnotators(QDumper& dumper)
{
dumper.addAnnotator(new QAnnotatorBasic);
dumper.addAnnotator(new QAnnotatorWidget);
// Add platform-specific annotators
addDefaultAnnotators_sys(dumper);
}
void addDefaultAnnotators(QVisitor& visitor)
{
visitor.addAnnotator(new QAnnotatorBasic);
visitor.addAnnotator(new QAnnotatorWidget);
// Add platform-specific annotators
addDefaultAnnotators_sys(visitor);
}
void dumpTreeFromRoot(const QObject& root, QVisitor& visitor)
{
// Set up iteration range
ObjectTree::DepthFirstConstIterator begin(root), end;
// Invoke generic visitor algorithm
ObjectTree::visit(begin, end, visitor);
}
void dumpTreeFromLeaf(const QObject& leaf, QVisitor& visitor)
{
// Walk up to root
const QObject* root = &leaf;
while(root->parent())
{
root = root->parent();
}
dumpTreeFromRoot(*root, visitor);
}
void dumpAncestors(const QObject& leaf, QVisitor& visitor)
{
// Set up iteration range
ObjectTree::AncestorConstIterator begin(leaf), end;
// Invoke generic visitor algorithm
ObjectTree::visit(begin, end, visitor);
}
} // namespace ObjectDump
QT_END_NAMESPACE