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 = logParser.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 const groups = Object.keys(groupedMap).sort()
77 const rows = []
78 for (let g = 0; g < groups.length; g++) {
79 const groupName = groups[g]
80 rows.push({ rowType: "group", group: groupName })
81 groupedMap[groupName].sort((a, b) => String(a.shortName).localeCompare(String(b.shortName)))
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 width: _maxFieldRowWidth
128 implicitWidth: _groupLayout.implicitWidth
129 implicitHeight: _groupLayout.implicitHeight
130
131 Component.onCompleted: _maxFieldRowWidth = Math.max(_maxFieldRowWidth, implicitWidth)
132
133 RowLayout {
134 id: _groupLayout
135 spacing: ScreenTools.defaultFontPixelWidth / 2
136
137 QGCColoredImage {
138 Layout.preferredWidth: _groupLabel.height * 0.5
139 Layout.preferredHeight: _groupLabel.height * 0.5
140 source: "/qmlimages/arrow-down.png"
141 color: qgcPal.text
142 fillMode: Image.PreserveAspectFit
143 rotation: (String(control._fieldSearchText).trim().length > 0 || control._isGroupExpanded(rowData.group)) ? 0 : -90
144 }
145
146 QGCLabel {
147 id: _groupLabel
148 text: rowData.group
149 font.bold: true
150 }
151 }
152
153 MouseArea {
154 anchors.fill: parent
155 onClicked: control._toggleGroupExpanded(rowData.group)
156 }
157 }
158 }
159
160 Component {
161 id: _fieldRowComponent
162
163 QGCCheckBoxSlider {
164 id: _fieldSlider
165 width: _maxFieldRowWidth
166 checked: logViewerController.selectedFields.indexOf(rowData.fullName) !== -1
167 text: rowData.shortName ? " " + String(rowData.shortName) : ""
168 onClicked: logViewerController.setFieldSelected(rowData.fullName, checked)
169
170 Component.onCompleted: _maxFieldRowWidth = Math.max(_maxFieldRowWidth, implicitWidth)
171 }
172 }
173
174 ColumnLayout {
175 id: mainLayout
176 anchors.top: parent.top
177 anchors.bottom: parent.bottom
178 anchors.left: parent.left
179 anchors.margins: ScreenTools.defaultFontPixelWidth / 2
180 spacing: ScreenTools.defaultFontPixelHeight * 0.5
181
182 QGCLabel {
183 visible: _isFirmwareLog
184 text: qsTr("Fields: %1 Parameters: %2 Events: %3")
185 .arg(logParser.plottableFields.length)
186 .arg(logParser.parameters.length)
187 .arg(logParser.events.length)
188 }
189
190 RowLayout {
191 visible: _isFirmwareLog
192 && logParser.startTime
193 && !isNaN(logParser.startTime.getTime())
194 && logParser.startTime.getTime() > 0
195 spacing: ScreenTools.defaultFontPixelWidth
196
197 QGCLabel { text: qsTr("X axis:") }
198
199 ButtonGroup { id: _xAxisButtonGroup }
200
201 QGCRadioButton {
202 text: qsTr("Elapsed")
203 checked: !control.xAxisShowLocalTime
204 ButtonGroup.group: _xAxisButtonGroup
205 onClicked: QGroundControl.settingsManager.logViewerSettings.xAxisShowLocalTime.rawValue = false
206 }
207
208 QGCRadioButton {
209 text: qsTr("Local time")
210 checked: control.xAxisShowLocalTime
211 ButtonGroup.group: _xAxisButtonGroup
212 onClicked: QGroundControl.settingsManager.logViewerSettings.xAxisShowLocalTime.rawValue = true
213 }
214 }
215
216 RowLayout {
217 visible: _isFirmwareLog
218 spacing: ScreenTools.defaultFontPixelWidth * 0.5
219
220 QGCLabel {
221 text: qsTr("Fields")
222 font.bold: true
223 }
224
225 QGCTextField {
226 id: _fieldSearchField
227 Layout.fillWidth: true
228 textColor: qgcPal.textFieldText
229 placeholderTextColor: Qt.rgba(qgcPal.textFieldText.r, qgcPal.textFieldText.g, qgcPal.textFieldText.b, 0.7)
230 placeholderText: qsTr("Search fields")
231
232 onTextChanged: {
233 control._fieldSearchText = text
234 if (text.trim().length === 0) {
235 _fieldSearchTimer.stop()
236 control._applyFieldFilter()
237 } else {
238 _fieldSearchTimer.restart()
239 }
240 }
241
242 onAccepted: {
243 control._fieldSearchText = text
244 _fieldSearchTimer.stop()
245 control._applyFieldFilter()
246 }
247 }
248
249 QGCButton {
250 text: qsTr("Clear Selected")
251 enabled: logViewerController.selectedFields.length > 0
252
253 onClicked: {
254 logViewerController.clearSelection()
255 control._applyFieldFilter()
256 control.clearSelectedRequested()
257 }
258 }
259 }
260
261 QGCListView {
262 id: _fieldsListView
263 Layout.fillHeight: true
264 Layout.preferredWidth: _maxFieldRowWidth + ScreenTools.defaultFontPixelWidth
265 visible: _isFirmwareLog
266 model: _filteredFieldRows
267 spacing: ScreenTools.defaultFontPixelHeight * 0.25
268
269 delegate: Loader {
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}