Files
KDDockWidgets/src/private/ObjectViewer.cpp
2022-07-12 11:28:05 +01:00

301 lines
7.8 KiB
C++

/*
This file is part of KDDockWidgets.
SPDX-FileCopyrightText: 2019-2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
Author: Sérgio Martins <sergio.martins@kdab.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
Contact KDAB at <info@kdab.com> for commercial licensing options.
*/
/**
* @file
* @brief Tree Widget to show the object tree. Used for debugging only, for apps that don't support GammaRay.
*
* @author Sérgio Martins \<sergio.martins@kdab.com\>
*/
#include "ObjectViewer_p.h"
#include <QStandardItem>
#include <QApplication>
#include <QHBoxLayout>
#include <QMenu>
#include <QContextMenuEvent>
#include <QItemSelectionModel>
#include <QPainter>
#include <QDebug>
#include <QMetaProperty>
#include <QWindow>
#include <QToolBar>
#include <QShortcut>
#include <QDir>
#ifdef Q_OS_WIN
#include <windows.h>
#endif
using namespace KDDockWidgets::Debug;
enum Role {
ObjRole = Qt::UserRole
};
ObjectViewer::ObjectViewer(QWidget *parent)
: QWidget(parent)
{
resize(600, 600);
auto lay = new QHBoxLayout(this);
lay->addWidget(&m_treeView);
m_treeView.setModel(&m_model);
connect(m_treeView.selectionModel(), &QItemSelectionModel::selectionChanged,
this, &ObjectViewer::onSelectionChanged);
QAction *action = m_menu.addAction(QStringLiteral("Refresh"));
connect(action, &QAction::triggered, this, &ObjectViewer::refresh);
action = m_menu.addAction(QStringLiteral("Dump Windows"));
connect(action, &QAction::triggered, this, &ObjectViewer::dumpWindows);
action = m_menu.addAction(QStringLiteral("Update"));
connect(action, &QAction::triggered, this, &ObjectViewer::updateSelectedWidget);
action = m_menu.addAction(QStringLiteral("Print to png"));
connect(action, &QAction::triggered, this, &ObjectViewer::dumpSelectedWidgetToPng);
action = m_menu.addAction(QStringLiteral("Toggle Visible"));
connect(action, &QAction::triggered, this, &ObjectViewer::toggleVisible);
#ifdef Q_OS_WIN
action = m_menu.addAction(QStringLiteral("Send WM_NCHITTEST"));
connect(action, &QAction::triggered, this, &ObjectViewer::sendHitTest);
#endif
refresh();
setWindowTitle(QStringLiteral("ObjectViewer"));
}
void ObjectViewer::refresh()
{
const auto hashCopy = m_itemMap;
for (auto it = hashCopy.cbegin(), e = hashCopy.cend(); it != e; ++it)
remove(it.key());
m_model.clear();
const auto &topLevelWidgets = qApp->topLevelWidgets();
for (QWidget *window : topLevelWidgets) {
add(window, m_model.invisibleRootItem());
}
}
void ObjectViewer::dumpSelectedWidgetToPng()
{
if (auto w = selectedWidget()) {
QPixmap px(w->size());
w->render(&px);
px.save(QStringLiteral("px.png"));
qDebug() << QDir::currentPath();
}
}
void ObjectViewer::updateSelectedWidget()
{
if (auto w = selectedWidget())
w->update();
}
void ObjectViewer::toggleVisible()
{
if (auto w = selectedWidget())
w->setVisible(!w->isVisible());
}
#ifdef Q_OS_WIN
void ObjectViewer::sendHitTest()
{
if (auto w = selectedWidget()) {
qDebug() << "Sending hit test to" << w;
::SendMessage(HWND(w->winId()), WM_NCHITTEST, 0, 0);
}
}
#endif
void ObjectViewer::dumpWindows()
{
qDebug() << "Top Level QWidgets:";
const auto &topLevelWidgets = qApp->topLevelWidgets();
for (QWidget *w : topLevelWidgets) {
if (qobject_cast<QMenu *>(w))
continue;
qDebug() << " QWidget=" << w;
}
qDebug() << "Top Level Windows:";
const auto &topLevelWindows = qApp->topLevelWindows();
for (QWindow *w : topLevelWindows) {
qDebug() << " QWindow=" << w << "; parent=" << w->parent() << "; transientParent=" << w->transientParent() << "; hwnd=" << w->winId();
}
}
QString ObjectViewer::nameForObj(QObject *o) const
{
QString name = QString::fromLatin1(o->metaObject()->className());
if (!o->objectName().isEmpty())
name += QStringLiteral("(%1)").arg(o->objectName());
if (auto w = qobject_cast<QWidget *>(o)) {
name += QStringLiteral(" - %1,%2 %3x%4").arg(w->x()).arg(w->y()).arg(w->width()).arg(w->height());
if (w->isWindow())
name += QStringLiteral(" ;W");
if (w->windowHandle() != nullptr)
name += QStringLiteral(" ;N");
}
return name;
}
void ObjectViewer::add(QObject *obj, QStandardItem *parent)
{
if (obj == this || obj == &m_menu || obj == parentWidget() || !obj) // Ignore our stuff
return;
if (m_ignoreMenus && qobject_cast<QMenu *>(obj))
return;
if (m_ignoreShortcuts && qobject_cast<QShortcut *>(obj))
return;
if (m_ignoreToolBars && qobject_cast<QToolBar *>(obj))
return;
connect(obj, &QObject::destroyed, this, &ObjectViewer::remove);
obj->installEventFilter(this);
auto item = new QStandardItem(nameForObj(obj));
item->setData(QVariant::fromValue(obj), ObjRole);
m_itemMap.insert(obj, item);
parent->appendRow(item);
updateItemAppearence(item);
for (auto child : obj->children())
add(child, item);
}
void ObjectViewer::remove(QObject *obj)
{
Q_ASSERT(obj);
obj->removeEventFilter(this);
m_itemMap.remove(obj);
}
void ObjectViewer::onSelectionChanged()
{
QObject *o = selectedObject();
if (m_selectedObject == o)
return;
if (m_selectedObject) {
m_selectedObject->removeEventFilter(this);
if (auto w = qobject_cast<QWidget *>(m_selectedObject))
w->update();
}
m_selectedObject = o;
if (m_selectedObject) {
printProperties(o);
m_selectedObject->installEventFilter(this);
if (m_highlightsWidget) {
if (auto w = qobject_cast<QWidget *>(o))
w->update();
}
}
}
void ObjectViewer::printProperties(QObject *obj) const
{
qDebug() << "Printing properties for" << obj;
auto mo = obj->metaObject();
const int count = mo->propertyCount();
for (int i = 0; i < count; ++i) {
QMetaProperty prop = mo->property(i);
qDebug() << " " << prop.name() << prop.read(obj);
}
if (auto w = qobject_cast<QWidget *>(obj)) {
qDebug() << "Is a widget!";
qDebug() << "Window=" << w->window();
qDebug() << "flags=" << w->windowFlags();
qDebug() << "is native?" << (w->windowHandle() != nullptr);
}
}
QObject *ObjectViewer::selectedObject() const
{
auto indexes = m_treeView.selectionModel()->selectedIndexes();
if (indexes.isEmpty())
return nullptr;
QModelIndex index = indexes.first();
QObject *obj = index.data(ObjRole).value<QObject *>();
return obj;
}
QWidget *ObjectViewer::selectedWidget() const
{
return qobject_cast<QWidget *>(selectedObject());
}
void ObjectViewer::updateItemAppearence(QStandardItem *item)
{
Q_ASSERT(item);
QWidget *widget = widgetForItem(item);
if (!widget)
return;
if (widget->isVisible()) {
item->setForeground(Qt::black);
} else {
item->setForeground(Qt::gray);
}
}
QObject *ObjectViewer::objectForItem(QStandardItem *item) const
{
return item->data(ObjRole).value<QObject *>();
}
QWidget *ObjectViewer::widgetForItem(QStandardItem *item) const
{
return qobject_cast<QWidget *>(objectForItem(item));
}
void ObjectViewer::contextMenuEvent(QContextMenuEvent *ev)
{
m_menu.exec(ev->globalPos());
}
bool ObjectViewer::eventFilter(QObject *watched, QEvent *event)
{
auto widget = static_cast<QWidget *>(watched);
if (event->type() == QEvent::Show || event->type() == QEvent::Hide) {
updateItemAppearence(m_itemMap.value(watched));
return false;
}
if (m_selectedObject != watched)
return false;
if (event->type() != QEvent::Paint || !m_highlightsWidget)
return false;
QPainter p(widget);
p.fillRect(widget->rect(), QBrush(QColor(0, 0, 255, 128)));
return true;
}