Skip to content

Commit

Permalink
feat: Add a Qt object tree view in the debug widget.
Browse files Browse the repository at this point in the history
This shows the main widget and all its children as a tree.
  • Loading branch information
iphydf committed Nov 26, 2024
1 parent 69a4f6d commit 3803f99
Show file tree
Hide file tree
Showing 19 changed files with 346 additions and 37 deletions.
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ qt6_wrap_ui(
src/mainwindow.ui
src/widget/about/aboutfriendform.ui
src/widget/form/debug/debuglog.ui
src/widget/form/debug/debugobjecttree.ui
src/widget/form/loadhistorydialog.ui
src/widget/form/profileform.ui
src/widget/form/removechatdialog.ui
Expand Down Expand Up @@ -271,6 +272,8 @@ set(${PROJECT_NAME}_SOURCES
src/model/chat.h
src/model/debug/debuglogmodel.cpp
src/model/debug/debuglogmodel.h
src/model/debug/debugobjecttreemodel.cpp
src/model/debug/debugobjecttreemodel.h
src/model/dialogs/idialogs.cpp
src/model/dialogs/idialogs.h
src/model/dialogs/idialogsmanager.h
Expand Down Expand Up @@ -423,6 +426,8 @@ set(${PROJECT_NAME}_SOURCES
src/widget/form/chatform.h
src/widget/form/debug/debuglog.cpp
src/widget/form/debug/debuglog.h
src/widget/form/debug/debugobjecttree.cpp
src/widget/form/debug/debugobjecttree.h
src/widget/form/debugwidget.cpp
src/widget/form/debugwidget.h
src/widget/form/filesform.cpp
Expand Down
2 changes: 2 additions & 0 deletions src/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ qt_moc(
"model/conference.h",
"model/conferencemessagedispatcher.h",
"model/debug/debuglogmodel.h",
"model/debug/debugobjecttreemodel.h",
"model/friend.h",
"model/friendlist/friendlistmanager.h",
"model/friendmessagedispatcher.h",
Expand Down Expand Up @@ -78,6 +79,7 @@ qt_moc(
"widget/form/conferenceinviteform.h",
"widget/form/conferenceinvitewidget.h",
"widget/form/debug/debuglog.h",
"widget/form/debug/debugobjecttree.h",
"widget/form/debugwidget.h",
"widget/form/filesform.h",
"widget/form/filetransferlist.h",
Expand Down
190 changes: 190 additions & 0 deletions src/model/debug/debugobjecttreemodel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright © 2024 The TokTok team.
*/

#include "debugobjecttreemodel.h"

// explicit DebugObjectTreeModel(QObject* parent = nullptr);
// ~DebugObjectTreeModel() override;

// QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
// QModelIndex parent(const QModelIndex& index) const override;
// int rowCount(const QModelIndex& parent = QModelIndex()) const override;
// int columnCount(const QModelIndex& parent = QModelIndex()) const override;
// QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;

class DebugObjectTreeModel::TreeItem
{
public:
const TreeItem* child(int row)
{
return row >= 0 && row < childCount() ? children.at(row).get() : nullptr;
}

int childCount() const
{
return int(children.size());
}

int columnCount() const
{
return 3;
}

QVariant data(int column) const
{
switch (column) {
case 0:
return name;
case 1:
return type;
case 2:
return QString::number(address, 16);
default:
return QVariant();
}
}

int row() const
{
if (parent == nullptr)
return 0;
const auto it = std::find_if(parent->children.cbegin(), parent->children.cend(),
[this](const std::unique_ptr<TreeItem>& treeItem) {
return treeItem.get() == this;
});

if (it != parent->children.cend())
return std::distance(parent->children.cbegin(), it);
Q_ASSERT(false); // should not happen
return -1;
}

TreeItem* parentItem()
{
return parent;
}

uintptr_t address = 0;
QString name;
QString type;
TreeItem* parent = nullptr;
std::vector<std::unique_ptr<TreeItem>> children;
};

namespace {
std::unique_ptr<DebugObjectTreeModel::TreeItem> buildObjectTree(const QObject* object,
DebugObjectTreeModel::TreeItem* parent)
{
auto tree = std::make_unique<DebugObjectTreeModel::TreeItem>();
tree->address = reinterpret_cast<uintptr_t>(object);
tree->name = object->objectName();
tree->type = QString::fromLatin1(object->metaObject()->className());
tree->parent = parent;

for (auto* child : object->children()) {
tree->children.push_back(buildObjectTree(child, tree.get()));
}

return tree;
}
} // namespace

DebugObjectTreeModel::DebugObjectTreeModel(QObject* parent)
: QAbstractItemModel(parent)
, root_(std::make_unique<TreeItem>())
{
reload();
}

DebugObjectTreeModel::~DebugObjectTreeModel() {}

void DebugObjectTreeModel::reload()
{
// Find the root object
QObject* root = this;
while (root->parent()) {
root = root->parent();
}

auto tree = buildObjectTree(root, root_.get());

beginResetModel();
root_->children.clear();
root_->children.push_back(std::move(tree));
endResetModel();
}

QModelIndex DebugObjectTreeModel::index(int row, int column, const QModelIndex& parent) const
{
if (!hasIndex(row, column, parent))
return {};

TreeItem* parentItem =
parent.isValid() ? static_cast<TreeItem*>(parent.internalPointer()) : root_.get();

if (auto* childItem = parentItem->child(row))
return createIndex(row, column, childItem);
return {};
}

QModelIndex DebugObjectTreeModel::parent(const QModelIndex& index) const
{
if (!index.isValid())
return {};

auto* childItem = static_cast<TreeItem*>(index.internalPointer());
TreeItem* parentItem = childItem->parentItem();

return parentItem != root_.get() ? createIndex(parentItem->row(), 0, parentItem) : QModelIndex{};
}

int DebugObjectTreeModel::rowCount(const QModelIndex& parent) const
{
if (parent.column() > 0)
return 0;

const TreeItem* parentItem =
parent.isValid() ? static_cast<const TreeItem*>(parent.internalPointer()) : root_.get();

return parentItem->childCount();
}

int DebugObjectTreeModel::columnCount(const QModelIndex& parent) const
{
if (parent.isValid())
return static_cast<TreeItem*>(parent.internalPointer())->columnCount();
return root_->columnCount();
}

QVariant DebugObjectTreeModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid() || role != Qt::DisplayRole)
return {};

const auto* item = static_cast<const TreeItem*>(index.internalPointer());
return item->data(index.column());
}

Qt::ItemFlags DebugObjectTreeModel::flags(const QModelIndex& index) const
{
return index.isValid() ? QAbstractItemModel::flags(index) : Qt::ItemFlags(Qt::NoItemFlags);
}

QVariant DebugObjectTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation != Qt::Horizontal || role != Qt::DisplayRole) {
return {};
}

switch (section) {
case 0:
return QStringLiteral("Name");
case 1:
return QStringLiteral("Type");
case 2:
return QStringLiteral("Address");
default:
return {};
}
}
30 changes: 30 additions & 0 deletions src/model/debug/debugobjecttreemodel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright © 2024 The TokTok team.
*/

#pragma once

#include <QAbstractItemModel>

class DebugObjectTreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit DebugObjectTreeModel(QObject* parent = nullptr);
~DebugObjectTreeModel() override;

void reload();

QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex& index) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;

class TreeItem;

private:
std::unique_ptr<TreeItem> root_;
};
5 changes: 3 additions & 2 deletions src/widget/form/chatform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,10 @@ ChatForm::ChatForm(Profile& profile_, Friend* chatFriend, IChatLog& chatLog_,
SmileyPack& smileyPack_, CameraSource& cameraSource_, Settings& settings_,
Style& style_, IMessageBoxManager& messageBoxManager,
ContentDialogManager& contentDialogManager_, FriendList& friendList_,
ConferenceList& conferenceList_)
ConferenceList& conferenceList_, QWidget* parent_)
: GenericChatForm(profile_.getCore(), chatFriend, chatLog_, messageDispatcher_, documentCache_,
smileyPack_, settings_, style_, messageBoxManager, friendList_, conferenceList_)
smileyPack_, settings_, style_, messageBoxManager, friendList_,
conferenceList_, parent_)
, core{profile_.getCore()}
, f(chatFriend)
, isTyping{false}
Expand Down
2 changes: 1 addition & 1 deletion src/widget/form/chatform.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class ChatForm : public GenericChatForm
IMessageDispatcher& messageDispatcher_, DocumentCache& documentCache,
SmileyPack& smileyPack, CameraSource& cameraSource, Settings& settings, Style& style,
IMessageBoxManager& messageBoxManager, ContentDialogManager& contentDialogManager,
FriendList& friendList, ConferenceList& conferenceList);
FriendList& friendList, ConferenceList& conferenceList, QWidget* parent = nullptr);
~ChatForm() override;
void setStatusMessage(const QString& newMessage);

Expand Down
4 changes: 2 additions & 2 deletions src/widget/form/debug/debuglog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ QStringList loadLogs(Paths& paths)
}
} // namespace

DebugLogForm::DebugLogForm(Paths& paths, Style& style)
: GenericForm{QPixmap(":/img/settings/general.png"), style}
DebugLogForm::DebugLogForm(Paths& paths, Style& style, QWidget* parent)
: GenericForm{QPixmap(":/img/settings/general.png"), style, parent}
, ui_{std::make_unique<Ui::DebugLog>()}
, debugLogModel_{std::make_unique<DebugLogModel>(loadLogs(paths), this)}
, reloadTimer_{std::make_unique<QTimer>(this)}
Expand Down
2 changes: 1 addition & 1 deletion src/widget/form/debug/debuglog.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class DebugLogForm : public GenericForm
{
Q_OBJECT
public:
DebugLogForm(Paths& paths, Style& style);
DebugLogForm(Paths& paths, Style& style, QWidget* parent);
~DebugLogForm();
QString getFormName() final
{
Expand Down
13 changes: 0 additions & 13 deletions src/widget/form/debug/debuglog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="VerticalOnlyScroller" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
Expand Down Expand Up @@ -76,18 +72,9 @@
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>VerticalOnlyScroller</class>
<extends>QScrollArea</extends>
<header>src/widget/form/settings/verticalonlyscroller.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
19 changes: 19 additions & 0 deletions src/widget/form/debug/debugobjecttree.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include "debugobjecttree.h"
#include "ui_debugobjecttree.h"

#include "src/model/debug/debugobjecttreemodel.h"

DebugObjectTree::DebugObjectTree(Style& style, QWidget* parent)
: GenericForm{QPixmap(":/img/settings/general.png"), style, parent}
, ui_(std::make_unique<Ui::DebugObjectTree>())
, model_(new DebugObjectTreeModel(this))
{
ui_->setupUi(this);

ui_->objectTree->setModel(model_);
ui_->objectTree->header()->setSectionResizeMode(QHeaderView::ResizeToContents);

connect(ui_->btnReload, &QPushButton::clicked, model_, &DebugObjectTreeModel::reload);
}

DebugObjectTree::~DebugObjectTree() {}
34 changes: 34 additions & 0 deletions src/widget/form/debug/debugobjecttree.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#ifndef DEBUGOBJECTTREE_H
#define DEBUGOBJECTTREE_H

#include "src/widget/form/settings/genericsettings.h"

#include <memory>

class DebugObjectTreeModel;
class Style;

namespace Ui {
class DebugObjectTree;
}

class DebugObjectTree : public GenericForm
{
Q_OBJECT

public:
explicit DebugObjectTree(Style& style, QWidget* parent = nullptr);
~DebugObjectTree();

QString getFormName() final
{
// Not translated (for now). Debugging only.
return QStringLiteral("Object Tree");
}

private:
std::unique_ptr<Ui::DebugObjectTree> ui_;
DebugObjectTreeModel* model_;
};

#endif // DEBUGOBJECTTREE_H
Loading

0 comments on commit 3803f99

Please sign in to comment.