feat(MonitoringTool): Inspect arbitrary model found by objectName
Closes: #14971
This commit is contained in:
parent
2c1806434a
commit
80eaf6ba89
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,462 +1,97 @@
|
|||
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 {
|
||||
SplitView {
|
||||
id: root
|
||||
|
||||
ColumnLayout {
|
||||
SplitView.fillHeight: true
|
||||
SplitView.preferredWidth: 450
|
||||
ColumnLayout {
|
||||
|
||||
spacing: 5
|
||||
spacing: 0
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 5
|
||||
|
||||
text: "Context properties:"
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
ListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.margins: 5
|
||||
|
||||
model: Monitor.contexPropertiesModel
|
||||
|
||||
clip: true
|
||||
spacing: 5
|
||||
|
||||
delegate: Item {
|
||||
implicitWidth: delegateRow.implicitWidth
|
||||
implicitHeight: delegateRow.implicitHeight
|
||||
|
||||
readonly property var contextPropertyValue:
|
||||
MonitorUtils.contextPropertyBindingHelper(name, root).value
|
||||
|
||||
Row {
|
||||
id: delegateRow
|
||||
|
||||
Label {
|
||||
text: name
|
||||
}
|
||||
|
||||
Label {
|
||||
text: ` [${MonitorUtils.typeName(contextPropertyValue)}]`
|
||||
color: "darkgreen"
|
||||
}
|
||||
|
||||
Label {
|
||||
text: ` (${MonitorUtils.valueToString(contextPropertyValue)})`
|
||||
color: "darkred"
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
onClicked: {
|
||||
inspectionStackView.clear()
|
||||
|
||||
const props = {
|
||||
name: name,
|
||||
objectForInspection: contextPropertyValue
|
||||
}
|
||||
|
||||
inspectionStackView.push(inspectionList, props)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Settings {
|
||||
property alias tabIndex: tabBar.currentIndex
|
||||
property alias modelObjectName: objectNameTextFiled.text
|
||||
property alias modelObjectRootName: rootTextField.text
|
||||
}
|
||||
|
||||
Component {
|
||||
id: modelInspectionComponent
|
||||
TabBar {
|
||||
id: tabBar
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: 10
|
||||
|
||||
Pane {
|
||||
property string name
|
||||
property var model
|
||||
readonly property var rootModel: model
|
||||
TabButton {
|
||||
text: "Context properties inspection"
|
||||
}
|
||||
TabButton {
|
||||
text: "Models inspection"
|
||||
}
|
||||
|
||||
property bool showControls: true
|
||||
currentIndex: swipeView.currentIndex
|
||||
}
|
||||
|
||||
readonly property var roles: Monitor.modelRoles(model)
|
||||
StackLayout {
|
||||
id: swipeView
|
||||
|
||||
readonly property var rolesModelContent: roles.map(role => ({
|
||||
visible: true,
|
||||
name: role.name,
|
||||
width: Math.ceil(fontMetrics.advanceWidth(` ${role.name} `))
|
||||
}))
|
||||
currentIndex: tabBar.currentIndex
|
||||
//anchors.fill: parent
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
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
|
||||
}
|
||||
SplitView {
|
||||
id: root
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
SplitView.fillHeight: true
|
||||
SplitView.preferredWidth: 450
|
||||
|
||||
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
|
||||
}
|
||||
spacing: 5
|
||||
|
||||
Label {
|
||||
visible: showControls
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 5
|
||||
|
||||
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()}`
|
||||
text: "Context properties:"
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
ListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.margins: 5
|
||||
|
||||
model: Monitor.contexPropertiesModel
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: inspectionList
|
||||
|
||||
Pane {
|
||||
id: inspectionPanel
|
||||
|
||||
property var objectForInspection
|
||||
property string name
|
||||
|
||||
onObjectForInspectionChanged: {
|
||||
inspectionModel.clear()
|
||||
|
||||
if (!objectForInspection)
|
||||
return
|
||||
|
||||
const items = []
|
||||
|
||||
for (const property in objectForInspection) {
|
||||
const type = typeof objectForInspection[property]
|
||||
|
||||
if (type === "function") {
|
||||
items.push({
|
||||
name: property,
|
||||
category: "functions",
|
||||
isModel: false,
|
||||
type: type
|
||||
})
|
||||
} else {
|
||||
const value = objectForInspection[property]
|
||||
const detailedType = MonitorUtils.typeName(value)
|
||||
const isModel = Monitor.isModel(value)
|
||||
|
||||
items.push({
|
||||
name: property,
|
||||
type: detailedType,
|
||||
category: isModel? "models" : "properties",
|
||||
isModel: isModel
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
items.sort((a, b) => {
|
||||
const nameA = a.category
|
||||
const nameB = b.category
|
||||
|
||||
if (nameA === nameB)
|
||||
return 0
|
||||
|
||||
if (nameA === "models")
|
||||
return -1
|
||||
|
||||
if (nameB === "models")
|
||||
return 1
|
||||
|
||||
if (nameA < nameB)
|
||||
return -1
|
||||
|
||||
if (nameA > nameB)
|
||||
return 1
|
||||
})
|
||||
|
||||
inspectionModel.append(items)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 5
|
||||
|
||||
Label {
|
||||
text: name
|
||||
font.pixelSize: 20
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
MenuSeparator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
spacing: 5
|
||||
clip: true
|
||||
|
||||
model: ListModel {
|
||||
id: inspectionModel
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
implicitWidth: delegateRow.implicitWidth
|
||||
implicitHeight: delegateRow.implicitHeight
|
||||
|
||||
readonly property var contextPropertyValue:
|
||||
MonitorUtils.contextPropertyBindingHelper(name, root).value
|
||||
|
||||
Row {
|
||||
id: delegateRow
|
||||
|
||||
readonly property var object: objectForInspection[name]
|
||||
|
||||
Label {
|
||||
text: name
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: type !== "function"
|
||||
sourceComponent: Label {
|
||||
text: ` [${type}]`
|
||||
color: "darkgreen"
|
||||
}
|
||||
Label {
|
||||
text: ` [${MonitorUtils.typeName(contextPropertyValue)}]`
|
||||
color: "darkgreen"
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: type !== "function"
|
||||
sourceComponent: Label {
|
||||
text: ` (${MonitorUtils.valueToString(delegateRow.object)})`
|
||||
color: "darkred"
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: isModel
|
||||
sourceComponent: Label {
|
||||
text: `, ${delegateRow.object.rowCount()} items`
|
||||
color: "darkred"
|
||||
font.bold: true
|
||||
}
|
||||
Label {
|
||||
text: ` (${MonitorUtils.valueToString(contextPropertyValue)})`
|
||||
color: "darkred"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -464,39 +99,277 @@ Component {
|
|||
anchors.fill: parent
|
||||
|
||||
onClicked: {
|
||||
if (!isModel)
|
||||
return
|
||||
inspectionStackView.clear()
|
||||
|
||||
const props = {
|
||||
name: name,
|
||||
model: objectForInspection[name]
|
||||
objectForInspection: contextPropertyValue
|
||||
}
|
||||
|
||||
inspectionStackView.push(modelInspectionComponent,
|
||||
props, StackView.Immediate)
|
||||
inspectionStackView.push(inspectionList, props)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section.property: "category"
|
||||
section.delegate: Pane {
|
||||
leftPadding: 0
|
||||
|
||||
Label {
|
||||
text: section
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: modelInspectionComponent
|
||||
|
||||
ModelInspectionPane {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: inspectionList
|
||||
|
||||
Pane {
|
||||
id: inspectionPanel
|
||||
|
||||
property var objectForInspection
|
||||
property string name
|
||||
|
||||
onObjectForInspectionChanged: {
|
||||
inspectionModel.clear()
|
||||
|
||||
if (!objectForInspection)
|
||||
return
|
||||
|
||||
const items = []
|
||||
|
||||
for (const property in objectForInspection) {
|
||||
const type = typeof objectForInspection[property]
|
||||
|
||||
if (type === "function") {
|
||||
items.push({
|
||||
name: property,
|
||||
category: "functions",
|
||||
isModel: false,
|
||||
type: type
|
||||
})
|
||||
} else {
|
||||
const value = objectForInspection[property]
|
||||
const detailedType = MonitorUtils.typeName(value)
|
||||
const isModel = Monitor.isModel(value)
|
||||
|
||||
items.push({
|
||||
name: property,
|
||||
type: detailedType,
|
||||
category: isModel? "models" : "properties",
|
||||
isModel: isModel
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
items.sort((a, b) => {
|
||||
const nameA = a.category
|
||||
const nameB = b.category
|
||||
|
||||
if (nameA === nameB)
|
||||
return 0
|
||||
|
||||
if (nameA === "models")
|
||||
return -1
|
||||
|
||||
if (nameB === "models")
|
||||
return 1
|
||||
|
||||
if (nameA < nameB)
|
||||
return -1
|
||||
|
||||
if (nameA > nameB)
|
||||
return 1
|
||||
})
|
||||
|
||||
inspectionModel.append(items)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 5
|
||||
|
||||
Label {
|
||||
text: name
|
||||
font.pixelSize: 20
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
MenuSeparator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
spacing: 5
|
||||
clip: true
|
||||
|
||||
model: ListModel {
|
||||
id: inspectionModel
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
implicitWidth: delegateRow.implicitWidth
|
||||
implicitHeight: delegateRow.implicitHeight
|
||||
|
||||
Row {
|
||||
id: delegateRow
|
||||
|
||||
readonly property var object: objectForInspection[name]
|
||||
|
||||
Label {
|
||||
text: name
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: type !== "function"
|
||||
sourceComponent: Label {
|
||||
text: ` [${type}]`
|
||||
color: "darkgreen"
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: type !== "function"
|
||||
sourceComponent: Label {
|
||||
text: ` (${MonitorUtils.valueToString(delegateRow.object)})`
|
||||
color: "darkred"
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: isModel
|
||||
sourceComponent: Label {
|
||||
text: `, ${delegateRow.object.rowCount()} items`
|
||||
color: "darkred"
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
onClicked: {
|
||||
if (!isModel)
|
||||
return
|
||||
|
||||
const props = {
|
||||
name: name,
|
||||
model: objectForInspection[name]
|
||||
}
|
||||
|
||||
inspectionStackView.push(modelInspectionComponent,
|
||||
props, StackView.Immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section.property: "category"
|
||||
section.delegate: Pane {
|
||||
leftPadding: 0
|
||||
|
||||
Label {
|
||||
text: section
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StackView {
|
||||
id: inspectionStackView
|
||||
|
||||
SplitView.fillHeight: true
|
||||
SplitView.minimumWidth: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StackView {
|
||||
id: inspectionStackView
|
||||
Item {
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
SplitView.fillHeight: true
|
||||
SplitView.minimumWidth: 100
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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*>())
|
||||
|
|
Loading…
Reference in New Issue