QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
LogViewerFieldsPanel.qml
Go to the documentation of this file.
1import QtQuick
2import QtQuick.Controls
3import QtQuick.Layouts
4
5import QGroundControl
6import QGroundControl.Controls
7
8/// Left panel for the Log Viewer charting tab.
9/// Shows log stats and the grouped fields list.
10Rectangle {
11 id: control
12
13 required property var logParser
14 required property var logViewerController
15
16 signal clearSelectedRequested
17
18 property bool xAxisShowLocalTime: QGroundControl.settingsManager.logViewerSettings.xAxisShowLocalTime.rawValue
19
20 color: qgcPal.windowShade
21 radius: ScreenTools.defaultFontPixelWidth * 0.5
22 Layout.preferredWidth: mainLayout.implicitWidth + (mainLayout.anchors.margins * 2)
23
24 // -------------------------------------------------------------------------
25 // Internal state
26 // -------------------------------------------------------------------------
27 property string _fieldSearchText: ""
28 property var _filteredFieldRows: []
29 property real _maxFieldRowWidth: _minFieldRowWidth
30
31 readonly property real _minFieldRowWidth: ScreenTools.defaultFontPixelWidth
32 readonly property bool _isFirmwareLog: logViewerController.sourceType === LogViewerController.Bin
33 || logViewerController.sourceType === LogViewerController.ULog
34
35 QGCPalette { id: qgcPal }
36
37 // -------------------------------------------------------------------------
38 // Public API
39 // -------------------------------------------------------------------------
40
41 /// Called by the parent after a log finishes loading.
42 function rebuildGroupedFields() {
43 _maxFieldRowWidth = _minFieldRowWidth
44 logViewerController.setPlottableFields(logParser.plottableFields)
45 _applyFieldFilter()
46 }
47
48 // -------------------------------------------------------------------------
49 // Internal helpers
50 // -------------------------------------------------------------------------
51
52 function _applyFieldFilter() {
53 const query = String(_fieldSearchText).trim().toLowerCase()
54 if (query.length === 0) {
55 _filteredFieldRows = logViewerController.fieldRows
56 return
57 }
58
59 const groupedMap = {}
60 const fields = logViewerController.plottableFields
61 for (let i = 0; i < fields.length; i++) {
62 const fullName = String(fields[i])
63 const splitIndex = fullName.indexOf(".")
64 const groupName = splitIndex > 0 ? fullName.substring(0, splitIndex) : qsTr("Other")
65 const shortName = splitIndex > 0 ? fullName.substring(splitIndex + 1) : fullName
66 const haystack = (fullName + " " + groupName + " " + shortName).toLowerCase()
67 if (haystack.indexOf(query) === -1) {
68 continue
69 }
70 if (!groupedMap[groupName]) {
71 groupedMap[groupName] = []
72 }
73 groupedMap[groupName].push({ fullName: fullName, shortName: shortName })
74 }
75
76 // Insertion order follows the controller's numeric-aware field order
77 const groups = Object.keys(groupedMap)
78 const rows = []
79 for (let g = 0; g < groups.length; g++) {
80 const groupName = groups[g]
81 rows.push({ rowType: "group", group: groupName })
82 for (let s = 0; s < groupedMap[groupName].length; s++) {
83 rows.push({
84 rowType: "field",
85 group: groupName,
86 fullName: groupedMap[groupName][s].fullName,
87 shortName: groupedMap[groupName][s].shortName
88 })
89 }
90 }
91 _filteredFieldRows = rows
92 }
93
94 function _isGroupExpanded(groupName) {
95 return logViewerController.isGroupExpanded(groupName)
96 }
97
98 function _toggleGroupExpanded(groupName) {
99 if (String(_fieldSearchText).trim().length > 0) {
100 return
101 }
102 logViewerController.toggleGroupExpanded(groupName)
103 _applyFieldFilter()
104 }
105
106 // -------------------------------------------------------------------------
107 // Connections
108 // -------------------------------------------------------------------------
109
110 Connections {
111 target: logViewerController
112 function onFieldRowsChanged() {
113 const savedY = _fieldsListView.contentY
114 _applyFieldFilter()
115 Qt.callLater(() => { _fieldsListView.contentY = savedY })
116 }
117 }
118
119 // -------------------------------------------------------------------------
120 // UI
121 // -------------------------------------------------------------------------
122
123 Component {
124 id: _groupRowComponent
125
126 Item {
127 implicitWidth: _groupLayout.implicitWidth
128 implicitHeight: _groupLayout.implicitHeight
129
130 Component.onCompleted: _maxFieldRowWidth = Math.max(_maxFieldRowWidth, implicitWidth)
131
132 RowLayout {
133 id: _groupLayout
134 spacing: ScreenTools.defaultFontPixelWidth / 2
135
136 QGCColoredImage {
137 Layout.preferredWidth: _groupLabel.height * 0.5
138 Layout.preferredHeight: _groupLabel.height * 0.5
139 source: "/qmlimages/arrow-down.png"
140 color: qgcPal.text
141 fillMode: Image.PreserveAspectFit
142 rotation: (String(control._fieldSearchText).trim().length > 0 || control._isGroupExpanded(rowData.group)) ? 0 : -90
143 }
144
145 QGCLabel {
146 id: _groupLabel
147 text: rowData.group
148 font.bold: true
149 }
150 }
151
152 MouseArea {
153 anchors.fill: parent
154 onClicked: control._toggleGroupExpanded(rowData.group)
155 }
156 }
157 }
158
159 Component {
160 id: _fieldRowComponent
161
162 QGCCheckBoxSlider {
163 id: _fieldSlider
164 checked: logViewerController.selectedFields.indexOf(rowData.fullName) !== -1
165 text: rowData.shortName ? " " + String(rowData.shortName) : ""
166 onClicked: logViewerController.setFieldSelected(rowData.fullName, checked)
167
168 Component.onCompleted: _maxFieldRowWidth = Math.max(_maxFieldRowWidth, implicitWidth)
169 }
170 }
171
172 ColumnLayout {
173 id: mainLayout
174 anchors.top: parent.top
175 anchors.bottom: parent.bottom
176 anchors.left: parent.left
177 anchors.margins: ScreenTools.defaultFontPixelWidth / 2
178 spacing: ScreenTools.defaultFontPixelHeight * 0.5
179
180 QGCLabel {
181 visible: _isFirmwareLog
182 text: qsTr("Fields: %1 Parameters: %2 Events: %3")
183 .arg(logParser.plottableFields.length)
184 .arg(logParser.parameters.length)
185 .arg(logParser.events.length)
186 }
187
188 RowLayout {
189 visible: _isFirmwareLog
190 && logParser.startTime
191 && !isNaN(logParser.startTime.getTime())
192 && logParser.startTime.getTime() > 0
193 spacing: ScreenTools.defaultFontPixelWidth
194
195 QGCLabel { text: qsTr("X axis:") }
196
197 ButtonGroup { id: _xAxisButtonGroup }
198
199 QGCRadioButton {
200 text: qsTr("Elapsed")
201 checked: !control.xAxisShowLocalTime
202 ButtonGroup.group: _xAxisButtonGroup
203 onClicked: QGroundControl.settingsManager.logViewerSettings.xAxisShowLocalTime.rawValue = false
204 }
205
206 QGCRadioButton {
207 text: qsTr("Local time")
208 checked: control.xAxisShowLocalTime
209 ButtonGroup.group: _xAxisButtonGroup
210 onClicked: QGroundControl.settingsManager.logViewerSettings.xAxisShowLocalTime.rawValue = true
211 }
212 }
213
214 RowLayout {
215 visible: _isFirmwareLog
216 spacing: ScreenTools.defaultFontPixelWidth * 0.5
217
218 QGCLabel {
219 text: qsTr("Fields")
220 font.bold: true
221 }
222
223 QGCTextField {
224 id: _fieldSearchField
225 Layout.fillWidth: true
226 textColor: qgcPal.textFieldText
227 placeholderTextColor: Qt.rgba(qgcPal.textFieldText.r, qgcPal.textFieldText.g, qgcPal.textFieldText.b, 0.7)
228 placeholderText: qsTr("Search fields")
229
230 onTextChanged: {
231 control._fieldSearchText = text
232 if (text.trim().length === 0) {
233 _fieldSearchTimer.stop()
234 control._applyFieldFilter()
235 } else {
236 _fieldSearchTimer.restart()
237 }
238 }
239
240 onAccepted: {
241 control._fieldSearchText = text
242 _fieldSearchTimer.stop()
243 control._applyFieldFilter()
244 }
245 }
246
247 QGCButton {
248 text: qsTr("Clear Selected")
249 enabled: logViewerController.selectedFields.length > 0
250
251 onClicked: {
252 logViewerController.clearSelection()
253 control._applyFieldFilter()
254 control.clearSelectedRequested()
255 }
256 }
257 }
258
259 QGCListView {
260 id: _fieldsListView
261 Layout.fillHeight: true
262 Layout.fillWidth: true
263 Layout.preferredWidth: _maxFieldRowWidth + ScreenTools.defaultFontPixelWidth
264 visible: _isFirmwareLog
265 model: _filteredFieldRows
266 spacing: ScreenTools.defaultFontPixelHeight * 0.25
267
268 delegate: Loader {
269 width: _fieldsListView.width - ScreenTools.defaultFontPixelWidth
270 sourceComponent: modelData.rowType === "group" ? _groupRowComponent : _fieldRowComponent
271 property var rowData: modelData
272 }
273 }
274 }
275
276 Timer {
277 id: _fieldSearchTimer
278 interval: 250
279 repeat: false
280 onTriggered: control._applyFieldFilter()
281 }
282}