/* This file is part of KDDockWidgets. SPDX-FileCopyrightText: 2019-2022 Klarälvdalens Datakonsult AB, a KDAB Group company Author: Sérgio Martins SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only Contact KDAB at 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 \ */ #include "ObjectViewer_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #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(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(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(obj)) return; if (m_ignoreShortcuts && qobject_cast(obj)) return; if (m_ignoreToolBars && qobject_cast(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(m_selectedObject)) w->update(); } m_selectedObject = o; if (m_selectedObject) { printProperties(o); m_selectedObject->installEventFilter(this); if (m_highlightsWidget) { if (auto w = qobject_cast(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(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(); return obj; } QWidget *ObjectViewer::selectedWidget() const { return qobject_cast(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(); } QWidget *ObjectViewer::widgetForItem(QStandardItem *item) const { return qobject_cast(objectForItem(item)); } void ObjectViewer::contextMenuEvent(QContextMenuEvent *ev) { m_menu.exec(ev->globalPos()); } bool ObjectViewer::eventFilter(QObject *watched, QEvent *event) { auto widget = static_cast(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; }