feat(MonitoringTool): Inspect arbitrary model found by objectName

Closes: #14971
This commit is contained in:
Michał Cieślak 2024-05-29 13:10:19 +02:00 committed by Michał
parent 2c1806434a
commit 80eaf6ba89
4 changed files with 582 additions and 436 deletions

View File

@ -0,0 +1,265 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Monitoring 1.0
Pane {
property string name
property var model
readonly property var rootModel: model
property bool showControls: true
readonly property var roles: model ? Monitor.modelRoles(model) : []
readonly property var rolesModelContent: roles.map(role => ({
visible: true,
name: role.name,
width: Math.ceil(fontMetrics.advanceWidth(` ${role.name} `))
}))
onRolesModelContentChanged: {
rolesModel.clear()
rolesModel.append(rolesModelContent)
}
property int columnsTotalWidth:
rolesModelContent.reduce((a, x) => a + x.width, 0)
ListModel {
id: rolesModel
Component.onCompleted: {
clear()
append(rolesModelContent)
}
}
Control {
id: helperControl
font.bold: true
}
FontMetrics {
id: fontMetrics
font.bold: true
}
ColumnLayout {
anchors.fill: parent
RowLayout {
Layout.fillWidth: true
visible: showControls
RoundButton {
text: "⬅️"
onClicked: {
inspectionStackView.pop(StackView.Immediate)
}
}
TextInput {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
text: name
font.pixelSize: 20
font.bold: true
selectByMouse: true
readOnly: true
}
}
MenuSeparator {
Layout.fillWidth: true
}
Label {
visible: showControls
text: "Hint: use right/left button click on a column " +
"header to adjust width, press cell content to " +
"see full value"
}
Label {
text: model ? `rows count: ${model.rowCount()}` : ""
font.bold: true
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
ScrollBar.vertical: ScrollBar {}
ScrollBar.horizontal: ScrollBar {}
clip: true
contentWidth: columnsTotalWidth
flickableDirection: Flickable.AutoFlickDirection
model: rootModel
delegate: Rectangle {
implicitWidth: flow.implicitWidth
implicitHeight: flow.implicitHeight
readonly property var topModel: model
Row {
id: flow
Repeater {
model: rolesModel
Label {
id: label
width: model.width
height: implicitHeight * 1.2
text: {
const value = topModel[model.name]
if (value === undefined || value === null)
return ""
const isModel = Monitor.isModel(value)
let text = value.toString()
if (isModel) {
text += " (" + value.rowCount() + ")"
}
return text
}
elide: Text.ElideRight
maximumLineCount: 1
verticalAlignment: Text.AlignVCenter
leftPadding: 2
rightPadding: 1
Rectangle {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: "gray"
}
Rectangle {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
width: 1
color: "gray"
}
MouseArea {
id: labelMouseArea
anchors.fill: parent
onClicked: {
const value = topModel[model.name]
const isModel = Monitor.isModel(value)
if (isModel)
loader.active = true
}
}
Loader {
id: loader
active: false
sourceComponent: ApplicationWindow {
width: 500
height: 400
visible: true
onClosing: loader.active = false
Loader {
anchors.fill: parent
sourceComponent: modelInspectionComponent
Component.onCompleted: {
item.showControls = false
item.model = topModel[model.name]
}
}
}
}
ToolTip.visible: labelMouseArea.pressed
ToolTip.text: label.text
}
}
}
}
headerPositioning: ListView.OverlayHeader
header: Item {
implicitWidth: headerFlow.implicitWidth
implicitHeight: headerFlow.implicitHeight * 1.5
z: 2
Rectangle {
color: "whitesmoke"
anchors.fill: parent
border.color: "gray"
}
Row {
id: headerFlow
anchors.verticalCenter: parent.verticalCenter
Repeater {
model: rolesModel
Label {
text: ` ${model.name} `
font.bold: true
width: model.width
elide: Text.ElideRight
maximumLineCount: 1
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
const factor = 1.5
const oldWidth = model.width
const leftBtn = mouse.button === Qt.LeftButton
const newWidth
= Math.ceil(leftBtn ? oldWidth * factor : oldWidth / factor)
model.width = newWidth
columnsTotalWidth += newWidth - oldWidth
}
}
}
}
}
}
}
}
}

View File

@ -1,11 +1,48 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Monitoring 1.0
import Qt.labs.settings 1.0
import AppLayouts.Wallet.stores 1.0 as WalletStores
import shared.stores 1.0 as SharedStores
Component {
ColumnLayout {
spacing: 0
Settings {
property alias tabIndex: tabBar.currentIndex
property alias modelObjectName: objectNameTextFiled.text
property alias modelObjectRootName: rootTextField.text
}
TabBar {
id: tabBar
Layout.fillWidth: true
Layout.bottomMargin: 10
TabButton {
text: "Context properties inspection"
}
TabButton {
text: "Models inspection"
}
currentIndex: swipeView.currentIndex
}
StackLayout {
id: swipeView
currentIndex: tabBar.currentIndex
//anchors.fill: parent
Layout.fillWidth: true
Layout.fillHeight: true
SplitView {
id: root
@ -79,257 +116,7 @@ Component {
Component {
id: modelInspectionComponent
Pane {
property string name
property var model
readonly property var rootModel: model
property bool showControls: true
readonly property var roles: Monitor.modelRoles(model)
readonly property var rolesModelContent: roles.map(role => ({
visible: true,
name: role.name,
width: Math.ceil(fontMetrics.advanceWidth(` ${role.name} `))
}))
onRolesModelContentChanged: {
rolesModel.clear()
rolesModel.append(rolesModelContent)
}
property int columnsTotalWidth:
rolesModelContent.reduce((a, x) => a + x.width, 0)
ListModel {
id: rolesModel
Component.onCompleted: {
clear()
append(rolesModelContent)
}
}
Control {
id: helperControl
font.bold: true
}
FontMetrics {
id: fontMetrics
font.bold: true
}
ColumnLayout {
anchors.fill: parent
RowLayout {
Layout.fillWidth: true
visible: showControls
RoundButton {
text: "⬅️"
onClicked: {
inspectionStackView.pop(StackView.Immediate)
}
}
TextInput {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
text: name
font.pixelSize: 20
font.bold: true
selectByMouse: true
readOnly: true
}
}
MenuSeparator {
Layout.fillWidth: true
}
Label {
visible: showControls
text: "Hint: use right/left button click on a column " +
"header to ajust width, press cell content to " +
"see full value"
}
Label {
text: `rows count: ${model.rowCount()}`
font.bold: true
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
contentWidth: columnsTotalWidth
flickableDirection: Flickable.AutoFlickDirection
model: rootModel
delegate: Rectangle {
implicitWidth: flow.implicitWidth
implicitHeight: flow.implicitHeight
readonly property var topModel: model
Row {
id: flow
Repeater {
model: rolesModel
Label {
id: label
width: model.width
height: implicitHeight * 1.2
text: {
const value = topModel[model.name]
const isModel = Monitor.isModel(value)
let text = value.toString()
if (isModel) {
text += " (" + value.rowCount() + ")"
}
return text
}
elide: Text.ElideRight
maximumLineCount: 1
verticalAlignment: Text.AlignVCenter
leftPadding: 2
rightPadding: 1
Rectangle {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: "gray"
}
Rectangle {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
width: 1
color: "gray"
}
MouseArea {
id: labelMouseArea
anchors.fill: parent
onClicked: {
const value = topModel[model.name]
const isModel = Monitor.isModel(value)
if (isModel)
loader.active = true
}
}
Loader {
id: loader
active: false
sourceComponent: ApplicationWindow {
width: 500
height: 400
visible: true
onClosing: loader.active = false
Loader {
anchors.fill: parent
sourceComponent: modelInspectionComponent
Component.onCompleted: {
item.showControls = false
item.model = topModel[model.name]
}
}
}
}
ToolTip.visible: labelMouseArea.pressed
ToolTip.text: label.text
}
}
}
}
headerPositioning: ListView.OverlayHeader
header: Item {
implicitWidth: headerFlow.implicitWidth
implicitHeight: headerFlow.implicitHeight * 1.5
z: 2
Rectangle {
color: "whitesmoke"
anchors.fill: parent
border.color: "gray"
}
Row {
id: headerFlow
anchors.verticalCenter: parent.verticalCenter
Repeater {
model: rolesModel
Label {
text: ` ${model.name} `
font.bold: true
width: model.width
elide: Text.ElideRight
maximumLineCount: 1
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
const factor = 1.5
const oldWidth = model.width
const leftBtn = mouse.button === Qt.LeftButton
const newWidth
= Math.ceil(leftBtn ? oldWidth * factor : oldWidth / factor)
model.width = newWidth
columnsTotalWidth += newWidth - oldWidth
}
}
}
}
}
}
}
}
}
ModelInspectionPane {}
}
Component {
@ -499,4 +286,90 @@ Component {
SplitView.minimumWidth: 100
}
}
Item {
ColumnLayout {
anchors.fill: parent
Label {
Layout.fillWidth: true
wrapMode: Text.Wrap
text: "Note: 'applicationWindow' is good root object in"
+ " most cases. 'WalletStores.RootStore' and"
+ " `SharedStores.RootStore` are also exposed for"
+ " convenience for models created within those singletons."
}
RowLayout {
Layout.fillHeight: false
Layout.fillWidth: true
Label {
text: "Model's object name:"
}
TextField {
id: objectNameTextFiled
selectByMouse: true
}
Label {
text: "Root:"
}
TextField {
id: rootTextField
text: "applicationWindow"
selectByMouse: true
}
Button {
text: "Search"
onClicked: {
let rootObj = null
try {
rootObj = eval(rootTextField.text)
} catch (error) {
objLabel.objStr = "Root object not found!"
return
}
const obj = Monitor.findChild(
rootObj, objectNameTextFiled.text)
objLabel.objStr = obj && Monitor.isModel(obj)
? obj.toString() : "Model not found!"
rolesModelContent.model = obj
}
}
}
Label {
id: objLabel
property string objStr
Layout.fillWidth: true
visible: objStr !== ""
text: "Object: " + objStr
}
ModelInspectionPane {
id: rolesModelContent
showControls: false
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}
}
}
}

View File

@ -26,6 +26,8 @@ public:
Q_INVOKABLE QString typeName(const QVariant &obj) const;
Q_INVOKABLE QJSValue modelRoles(QAbstractItemModel *model) const;
Q_INVOKABLE QObject* findChild(QObject* obj, const QString& name) const;
static Monitor& instance();
static QObject* qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine);

View File

@ -6,7 +6,8 @@
#include <QQmlComponent>
#include <QQuickWindow>
void Monitor::initialize(QQmlApplicationEngine* engine) {
void Monitor::initialize(QQmlApplicationEngine* engine)
{
QObject::connect(engine, &QQmlApplicationEngine::objectCreated, this,
[engine](QObject *obj, const QUrl &objUrl) {
if (!obj) {
@ -46,6 +47,11 @@ bool Monitor::isModel(const QVariant &obj) const
return qobject_cast<QAbstractItemModel*>(obj.value<QObject*>()) != nullptr;
}
QObject* Monitor::findChild(QObject* obj, const QString& name) const
{
return obj == nullptr ? nullptr : obj->findChild<QObject*>(name);
}
QString Monitor::typeName(const QVariant &obj) const
{
if (obj.canConvert<QObject*>())