6import QGroundControl.Controls
7import QGroundControl.AppSettings
12 z: QGroundControl.zOrderTopMost
14 readonly property real _defaultTextHeight: ScreenTools.defaultFontPixelHeight
15 readonly property real _defaultTextWidth: ScreenTools.defaultFontPixelWidth
16 readonly property real _horizontalMargin: _defaultTextWidth / 2
17 readonly property real _verticalMargin: _defaultTextHeight / 2
19 property bool _first: true
20 property bool _commingFromRIDSettings: false
21 property int _selectedPageIndex: -1
22 property int _selectedSectionIndex: -1
23 property var _expandedPages: ({}) // pageIndex -> bool
24 property int _expandedRevision: 0 // bumped to trigger re-evaluation
25 property string _searchQuery: ""
27 function _setExpanded(pageIndex, value) {
28 _expandedPages[pageIndex] = value
32 function _isExpanded(pageIndex) {
33 void _expandedRevision // create binding dependency
34 return !!_expandedPages[pageIndex]
37 // Search: returns array of matching section indices for a page, or empty if no match
38 function _matchingSections(pageIndex) {
39 var query = _searchQuery.toLowerCase().trim()
40 if (query === "") return [] // empty = no filtering
42 var entry = settingsPagesModel.get(pageIndex)
45 // Check English search terms
46 var termsStr = entry.searchTerms
49 if (termsStr && termsStr !== "") {
51 var terms = JSON.parse(termsStr)
52 for (var i = 0; i < terms.length; i++) {
53 if (terms[i].terms.indexOf(query) !== -1) {
54 matched[terms[i].section] = true
55 matches.push(terms[i].section)
58 } catch(e) { console.warn("AppSettings: JSON parse error in searchTerms:", e) }
61 // Check translatable terms (translated at runtime)
62 var trStr = entry.translatableTerms
63 if (trStr && trStr !== "") {
65 var trTerms = JSON.parse(trStr)
66 for (var j = 0; j < trTerms.length; j++) {
67 if (matched[trTerms[j].section]) continue
68 var ctx = trTerms[j].context
69 var tList = trTerms[j].terms
70 for (var k = 0; k < tList.length; k++) {
71 if (qsTranslate(ctx, tList[k]).toLowerCase().indexOf(query) !== -1) {
72 matched[trTerms[j].section] = true
73 matches.push(trTerms[j].section)
78 } catch(e) { console.warn("AppSettings: JSON parse error in translatableTerms:", e) }
84 // Does this page have any search matches? (or is search empty = show all)
85 function _pageMatchesSearch(pageIndex) {
86 if (_searchQuery.trim() === "") return true
87 return _matchingSections(pageIndex).length > 0
90 function _navigateTo(pageIndex, sectionIndex) {
91 var entry = settingsPagesModel.get(pageIndex)
92 if (!entry || entry.name === "Divider") return
95 _selectedSectionIndex = sectionIndex
97 if (_selectedPageIndex !== pageIndex) {
98 _selectedPageIndex = pageIndex
99 rightPanel.source = url
102 // Apply section filter after the page is loaded
103 if (rightPanel.item && typeof rightPanel.item.sectionFilter !== "undefined") {
104 rightPanel.item.sectionFilter = sectionIndex
108 function showSettingsPage(settingsPage) {
109 for (var i = 0; i < settingsPagesModel.count; i++) {
110 var entry = settingsPagesModel.get(i)
111 if (entry && entry.name === settingsPage) {
118 // This need to block click event leakage to underlying map.
123 QGCPalette { id: qgcPal }
125 Component.onCompleted: {
126 // Find and select the default page
127 var targetUrl = globals.commingFromRIDIndicator
128 ? "qrc:/qml/QGroundControl/AppSettings/RemoteIDSettings.qml"
129 : "qrc:/qml/QGroundControl/AppSettings/GeneralSettings.qml"
130 globals.commingFromRIDIndicator = false
132 for (var i = 0; i < settingsPagesModel.count; i++) {
133 var entry = settingsPagesModel.get(i)
134 if (entry && entry.url === targetUrl) {
143 function onLoaded() {
144 if (rightPanel.item && typeof rightPanel.item.sectionFilter !== "undefined") {
145 rightPanel.item.sectionFilter = _selectedSectionIndex
150 SettingsPagesModel { id: settingsPagesModel }
154 width: Math.max(buttonColumn.implicitWidth + _horizontalMargin, ScreenTools.defaultFontPixelWidth * 22)
155 anchors.topMargin: _verticalMargin
156 anchors.top: parent.top
157 anchors.bottom: parent.bottom
158 anchors.leftMargin: _horizontalMargin
159 anchors.left: parent.left
160 spacing: _verticalMargin / 2
164 Layout.fillWidth: true
165 placeholderText: qsTr("Search settings...")
168 settingsView._searchQuery = text
174 Layout.fillWidth: true
175 Layout.fillHeight: true
176 contentHeight: buttonColumn.height + _verticalMargin
177 flickableDirection: Flickable.VerticalFlick
186 model: settingsPagesModel
191 Layout.fillWidth: true
193 required property int index
194 required property var model
196 property string pageName: model.name ?? ""
197 property string pageUrl: model.url ?? ""
198 property string pageIconUrl: model.iconUrl ?? ""
199 property var pageVisible: model.pageVisible ?? function() { return true }
200 property var pageSections: {
202 var trStr = model.translatableTerms
203 if (trStr && trStr !== "") {
204 var trTerms = JSON.parse(trStr)
205 return trTerms.map(function(t) {
206 if (!t.terms || t.terms.length === 0) return ""
207 return qsTranslate(t.context, t.terms[0])
208 }).filter(function(s) { return s !== "" })
210 var s = model.sections
211 return (s && s !== "") ? JSON.parse(s) : []
213 console.warn("AppSettings: JSON parse error in pageSections:", e)
217 property bool isSelected: settingsView._selectedPageIndex === index
218 property bool hasMultipleSections: pageSections.length > 1
219 property bool isSearching: settingsView._searchQuery.trim() !== ""
220 property bool matchesSearch: settingsView._pageMatchesSearch(index)
221 property bool isExpanded: hasMultipleSections && (isSearching ? matchesSearch : settingsView._isExpanded(index))
224 if (pageName === "Divider") return !isSearching
225 if (!pageVisible()) return false
226 if (isSearching) return matchesSearch
232 Layout.fillWidth: true
233 height: ScreenTools.defaultFontPixelHeight / 2
234 visible: pageName === "Divider"
239 Layout.fillWidth: true
241 icon.source: pageIconUrl
242 expandable: hasMultipleSections
244 checked: isSelected && settingsView._selectedSectionIndex === -1
245 visible: pageName !== "Divider" && pageVisible()
248 if (mainWindow.allowViewSwitch()) {
249 settingsView._navigateTo(index, -1)
250 if (hasMultipleSections) {
251 // Toggle expand/collapse when re-clicking the same page
252 if (isSelected && isExpanded) {
253 settingsView._setExpanded(index, false)
254 } else if (!isExpanded) {
255 settingsView._setExpanded(index, true)
262 if (!mainWindow.allowViewSwitch()) {
265 var expanding = !isExpanded
266 settingsView._setExpanded(index, expanding)
267 if (!expanding && isSelected) {
268 settingsView._navigateTo(index, -1)
273 // Section sub-items (indented, shown when page is expanded)
275 model: isExpanded ? pageSections : []
279 Layout.fillWidth: true
280 padding: ScreenTools.defaultFontPixelWidth * 0.75
281 leftPadding: ScreenTools.defaultFontPixelWidth * 3
282 hoverEnabled: !ScreenTools.isMobile
284 property int sectionIndex: index
285 property bool sectionChecked: pageColumn.isSelected && settingsView._selectedSectionIndex === sectionIndex
286 property bool sectionMatchesSearch: {
287 if (!pageColumn.isSearching) return true
288 var matches = settingsView._matchingSections(pageColumn.index)
289 return matches.indexOf(sectionIndex) !== -1
291 property bool sectionContentVisible: {
292 if (!pageColumn.isSelected) return true
293 if (!rightPanel.item) return true
294 if (typeof rightPanel.item.sectionVisible !== "function") return true
295 return rightPanel.item.sectionVisible(sectionIndex)
297 property color textColor: sectionChecked || pressed ? qgcPal.buttonHighlightText : qgcPal.buttonText
298 visible: sectionMatchesSearch && sectionContentVisible
300 background: Rectangle {
301 color: qgcPal.buttonHighlight
302 opacity: sectionBtn.sectionChecked || sectionBtn.pressed ? 1 : sectionBtn.enabled && sectionBtn.hovered ? 0.2 : 0
303 radius: ScreenTools.defaultFontPixelWidth / 2
306 contentItem: QGCLabel {
308 color: sectionBtn.textColor
309 font.pointSize: ScreenTools.defaultFontPointSize * 0.9
310 horizontalAlignment: Text.AlignLeft
314 if (mainWindow.allowViewSwitch()) {
315 settingsView._navigateTo(pageColumn.index, sectionIndex)
328 anchors.topMargin: _verticalMargin
329 anchors.bottomMargin: _verticalMargin
330 anchors.leftMargin: _horizontalMargin
331 anchors.left: leftPanel.right
332 anchors.top: parent.top
333 anchors.bottom: parent.bottom
335 color: qgcPal.windowShade
341 anchors.leftMargin: _horizontalMargin
342 anchors.rightMargin: _horizontalMargin
343 anchors.topMargin: _verticalMargin
344 anchors.bottomMargin: _verticalMargin
345 anchors.left: divider.right
346 anchors.right: parent.right
347 anchors.top: parent.top
348 anchors.bottom: parent.bottom