QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
AppSettings.cc
Go to the documentation of this file.
1#include "AppSettings.h"
2#include "QGCFileHelper.h"
3#include "QGCPalette.h"
4#include "AppMessages.h"
6#include "QGCApplication.h"
8#include "QGCMAVLink.h"
9#include "LinkManager.h"
10
11#ifdef Q_OS_ANDROID
12#include "AndroidInterface.h"
13#endif
14
15#include <QtCore/QStandardPaths>
16#include <QtCore/QDir>
17#include <QtCore/QSettings>
18
19QGC_LOGGING_CATEGORY(AppSettingsLog, "Settings.AppSettings")
20
21// Release languages are 90%+ complete
22QList<QLocale::Language> AppSettings::_rgReleaseLanguages = {
23 QLocale::English,
24 QLocale::Azerbaijani,
25 QLocale::Chinese,
26 QLocale::Japanese,
27 QLocale::Korean,
28 QLocale::Portuguese,
29 QLocale::Russian,
30};
31
32// Partial languages are 40%+ complete
33QList<QLocale::Language> AppSettings::_rgPartialLanguages = {
34 QLocale::Ukrainian,
35};
36
37AppSettings::LanguageInfo_t AppSettings::_rgLanguageInfo[] = {
38 { QLocale::AnyLanguage, "System" }, // Must be first
39 { QLocale::Azerbaijani, "Azerbaijani (Azerbaijani)" },
40 { QLocale::Bulgarian, "български (Bulgarian)" },
41 { QLocale::Chinese, "中文 (Chinese)" },
42 { QLocale::Dutch, "Nederlands (Dutch)" },
43 { QLocale::English, "English" },
44 { QLocale::Finnish, "Suomi (Finnish)" },
45 { QLocale::French, "Français (French)" },
46 { QLocale::German, "Deutsche (German)" },
47 { QLocale::Greek, "Ελληνικά (Greek)" },
48 { QLocale::Hebrew, "עברית (Hebrew)" },
49 { QLocale::Italian, "Italiano (Italian)" },
50 { QLocale::Japanese, "日本語 (Japanese)" },
51 { QLocale::Korean, "한국어 (Korean)" },
52 { QLocale::NorwegianBokmal, "Norsk (Norwegian)" },
53 { QLocale::Polish, "Polskie (Polish)" },
54 { QLocale::Portuguese, "Português (Portuguese)" },
55 { QLocale::Russian, "Pусский (Russian)" },
56 { QLocale::Spanish, "Español (Spanish)" },
57 { QLocale::Swedish, "Svenska (Swedish)" },
58 { QLocale::Turkish, "Türk (Turkish)" }
59};
60
62{
63 // Don't offer firmware classes whose plugin factory is not registered in this build
64 const QList<QGCMAVLink::FirmwareClass_t> supportedFirmwareClasses = FirmwarePluginManager::instance()->supportedFirmwareClasses();
65 const auto isSupported = [&supportedFirmwareClasses](const QVariant &value) {
66 return supportedFirmwareClasses.contains(static_cast<QGCMAVLink::FirmwareClass_t>(value.toUInt()));
67 };
68 for (const char *factName : { preferredFirmwareClassName, offlineEditingFirmwareClassName }) {
69 FactMetaData *const metaData = _nameToMetaDataMap.value(factName);
70 if (!metaData) {
71 qCWarning(AppSettingsLog) << "Missing metadata for fact" << factName;
72 continue;
73 }
74 const QVariantList enumValues = metaData->enumValues();
75 for (const QVariant &enumValue : enumValues) {
76 if (!isSupported(enumValue)) {
77 metaData->removeEnumInfo(enumValue);
78 }
79 }
80 }
81
82 // A previously stored value (or even the default) may no longer be supported by this build
83 for (Fact *const fact : { preferredFirmwareClass(), offlineEditingFirmwareClass() }) {
84 if (!isSupported(fact->rawValue())) {
85 if (isSupported(fact->rawDefaultValue())) {
86 fact->setRawValue(fact->rawDefaultValue());
87 } else if (!fact->enumValues().isEmpty()) {
88 fact->setRawValue(fact->enumValues().constFirst());
89 }
90 }
91 }
92
93 QGCPalette::setGlobalTheme(indoorPalette()->rawValue().toBool() ? QGCPalette::Dark : QGCPalette::Light);
94
95 // Instantiate savePath so we can check for override and setup default path if needed
96
97 SettingsFact* savePathFact = qobject_cast<SettingsFact*>(savePath());
98 QString appName = QCoreApplication::applicationName();
99#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
100 // Mobile builds always use the runtime generated location for savePath.
101 bool userHasModifiedSavePath = false;
102#else
103 bool userHasModifiedSavePath = !savePathFact->rawValue().toString().isEmpty() || !_nameToMetaDataMap[savePathName]->rawDefaultValue().toString().isEmpty();
104#endif
105
106 if (!userHasModifiedSavePath) {
107#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
108 #ifdef Q_OS_IOS
109 // This will expose the directories directly to the File iOs app
110 QDir rootDir = QDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
111 savePathFact->setRawValue(rootDir.absolutePath());
112 #else
113 QString rootDirPath;
114 #ifdef Q_OS_ANDROID
115 if (!androidDontSaveToSDCard()->rawValue().toBool()) {
116 rootDirPath = AndroidInterface::getSDCardPath();
117 qCDebug(AppSettingsLog) << "AndroidInterface::getSDCardPath()" << rootDirPath;
118 if (rootDirPath.isEmpty() || !QDir(rootDirPath).exists()) {
119 rootDirPath.clear();
120 qCWarning(AppSettingsLog) << "Save to SD card specified for application data. But no SD card present or permissions not granted. Using internal storage.";
121 } else if (!QFileInfo(rootDirPath).isWritable()) {
122 rootDirPath.clear();
123 QGC::showAppMessage(AppSettings::tr("Save to SD card specified for application data. But SD card is write protected. Using internal storage."));
124 }
125 }
126 #endif
127 if (rootDirPath.isEmpty()) {
128 rootDirPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
129 }
130 savePathFact->setRawValue(QDir(rootDirPath).filePath(appName));
131 #endif
132 savePathFact->setUserVisible(false);
133#else
134 QDir rootDir;
135 if (QGC::runningUnitTests() || qgcApp()->simpleBootTest()) {
136 rootDir = QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
137 } else {
138 rootDir = QDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
139 }
140 savePathFact->setRawValue(rootDir.filePath(appName));
141#endif
142 }
143
144 connect(savePathFact, &Fact::rawValueChanged, this, &AppSettings::savePathsChanged);
145 connect(savePathFact, &Fact::rawValueChanged, this, &AppSettings::_checkSavePathDirectories);
146
147#ifdef Q_OS_ANDROID
148 // React to runtime toggling of the SD card setting
149 connect(androidDontSaveToSDCard(), &Fact::rawValueChanged, this, [this, appName](QVariant value) {
150 Fact* const fact = savePath();
151 if (value.toBool()) {
152 const QString internalBasePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
153 fact->setRawValue(QDir(internalBasePath).filePath(appName));
154 } else {
155 // If permission is already granted this returns the SD card root and we set the path here.
156 // If not, it returns empty and launches the system permission UI. The result comes back through
157 // jniStoragePermissionsResult, which sets the SD card save path (or reverts this setting on denial).
158 const QString sdRootPath = AndroidInterface::getSDCardPath();
159 if (!sdRootPath.isEmpty() && QDir(sdRootPath).exists() && QFileInfo(sdRootPath).isWritable()) {
160 fact->setRawValue(QDir(sdRootPath).filePath(appName));
161 }
162 }
163 });
164#endif
165
166 _checkSavePathDirectories();
167
168 // When a specific preferred firmware/vehicle is chosen, keep the offline editing settings in sync
169 connect(preferredFirmwareClass(), &Fact::rawValueChanged, this, [this](QVariant value) {
170 if (value.toUInt() != 0) {
171 offlineEditingFirmwareClass()->setRawValue(value);
172 }
173 });
174 connect(preferredVehicleClass(), &Fact::rawValueChanged, this, [this](QVariant value) {
175 if (value.toUInt() != 0) {
176 offlineEditingVehicleClass()->setRawValue(value);
177 }
178 });
179}
180
181DECLARE_SETTINGSFACT(AppSettings, preferredFirmwareClass)
182DECLARE_SETTINGSFACT(AppSettings, preferredVehicleClass)
183DECLARE_SETTINGSFACT(AppSettings, offlineEditingFirmwareClass)
184DECLARE_SETTINGSFACT(AppSettings, offlineEditingVehicleClass)
185DECLARE_SETTINGSFACT(AppSettings, offlineEditingCruiseSpeed)
186DECLARE_SETTINGSFACT(AppSettings, offlineEditingHoverSpeed)
187DECLARE_SETTINGSFACT(AppSettings, offlineEditingAscentSpeed)
188DECLARE_SETTINGSFACT(AppSettings, offlineEditingDescentSpeed)
189DECLARE_SETTINGSFACT(AppSettings, batteryPercentRemainingAnnounce)
190DECLARE_SETTINGSFACT(AppSettings, defaultMissionItemAltitude)
193DECLARE_SETTINGSFACT(AppSettings, virtualJoystick)
194DECLARE_SETTINGSFACT(AppSettings, virtualJoystickAutoCenterThrottle)
195DECLARE_SETTINGSFACT(AppSettings, virtualJoystickLeftHandedMode)
196DECLARE_SETTINGSFACT(AppSettings, uiScalePercent)
198DECLARE_SETTINGSFACT(AppSettings, androidDontSaveToSDCard)
199DECLARE_SETTINGSFACT(AppSettings, androidUsePosixSerial)
201DECLARE_SETTINGSFACT(AppSettings, enforceChecklist)
202DECLARE_SETTINGSFACT(AppSettings, enableMultiVehiclePanel)
203DECLARE_SETTINGSFACT(AppSettings, tiandituToken)
205DECLARE_SETTINGSFACT(AppSettings, mapboxAccount)
211DECLARE_SETTINGSFACT(AppSettings, gstDebugLevel)
213DECLARE_SETTINGSFACT(AppSettings, clearSettingsNextBoot)
214DECLARE_SETTINGSFACT(AppSettings, disableAllPersistence)
215DECLARE_SETTINGSFACT(AppSettings, firstRunPromptIdsShown)
216DECLARE_SETTINGSFACT(AppSettings, favoriteParameters)
217DECLARE_SETTINGSFACT(AppSettings, showAppLogTimestampAsElapsedTime)
218
220{
221 if (!_indoorPaletteFact) {
222 _indoorPaletteFact = _createSettingsFact(indoorPaletteName);
223 connect(_indoorPaletteFact, &Fact::rawValueChanged, this, &AppSettings::_indoorPaletteChanged);
224 }
225 return _indoorPaletteFact;
226}
227
229{
230 if (!_qLocaleLanguageFact) {
231 _qLocaleLanguageFact = _createSettingsFact(qLocaleLanguageName);
232 connect(_qLocaleLanguageFact, &Fact::rawValueChanged, this, &AppSettings::_qLocaleLanguageChanged);
233
234 FactMetaData* metaData = _qLocaleLanguageFact->metaData();
235 QStringList rgEnumStrings;
236 QVariantList rgEnumValues;
237
238 // System is always an available selection
239 rgEnumStrings.append(_rgLanguageInfo[0].languageName);
240 rgEnumValues.append(_rgLanguageInfo[0].languageId);
241
242 for (const auto& languageInfo: _rgLanguageInfo) {
243 if (_rgReleaseLanguages.contains(languageInfo.languageId)) {
244 rgEnumStrings.append(languageInfo.languageName);
245 rgEnumValues.append(languageInfo.languageId);
246 }
247 }
248 for (const auto& languageInfo: _rgLanguageInfo) {
249 if (_rgPartialLanguages.contains(languageInfo.languageId)) {
250 rgEnumStrings.append(QString(languageInfo.languageName) + AppSettings::tr(" (Partial)"));
251 rgEnumValues.append(languageInfo.languageId);
252 }
253 }
254#ifdef QGC_DAILY_BUILD
255 // Only daily builds include full set of languages for testing purposes
256 for (const auto& languageInfo: _rgLanguageInfo) {
257 if (!_rgReleaseLanguages.contains(languageInfo.languageId) && !_rgPartialLanguages.contains(languageInfo.languageId)) {
258 rgEnumStrings.append(QString(languageInfo.languageName) + AppSettings::tr(" (Test Only)"));
259 rgEnumValues.append(languageInfo.languageId);
260 }
261 }
262#endif
263#ifdef QT_DEBUG
264 // Debug builds include pseudo-localization for UI layout testing
265 rgEnumStrings.append(AppSettings::tr("Pseudo Localization (Test Only)"));
266 rgEnumValues.append(QLocale::Esperanto);
267#endif
268 metaData->setEnumInfo(rgEnumStrings, rgEnumValues);
269
270 if (_qLocaleLanguageFact->enumIndex() == -1) {
271 _qLocaleLanguageFact->setRawValue(QLocale::AnyLanguage);
272 }
273 }
274 return _qLocaleLanguageFact;
275}
276
277void AppSettings::_qLocaleLanguageChanged()
278{
279 qgcApp()->setLanguage();
280}
281
282void AppSettings::_checkSavePathDirectories(void)
283{
284 const QString savePath = this->savePath()->rawValue().toString();
286 // Create all subdirectories
296 }
297}
298
299QString AppSettings::_childSavePath(const char* directory)
300{
301 const QString rootPath = savePath()->rawValue().toString();
302 if (rootPath.isEmpty()) {
303 return QString();
304 }
305
306 const QDir rootDir(rootPath);
307 if (!rootDir.exists()) {
308 return QString();
309 }
310
311 return rootDir.filePath(directory);
312}
313
314void AppSettings::_indoorPaletteChanged(void)
315{
316 QGCPalette::setGlobalTheme(indoorPalette()->rawValue().toBool() ? QGCPalette::Dark : QGCPalette::Light);
317}
318
320{
321 return _childSavePath(missionDirectory);
322}
323
325{
326 return _childSavePath(parameterDirectory);
327}
328
330{
331 return _childSavePath(telemetryDirectory);
332}
333
335{
336 return _childSavePath(logDirectory);
337}
338
340{
341 return _childSavePath(videoDirectory);
342}
343
345{
346 return _childSavePath(photoDirectory);
347}
348
350{
351 return _childSavePath(crashDirectory);
352}
353
355{
356 return _childSavePath(mavlinkActionsDirectory);
357}
358
360{
361 return _childSavePath(settingsDirectory);
362}
363
364QList<int> AppSettings::firstRunPromptsIdsVariantToList(const QVariant& firstRunPromptIds)
365{
366 QList<int> rgIds;
367
368 QStringList strIdList = firstRunPromptIds.toString().split(",", Qt::SkipEmptyParts);
369
370 for (const QString& strId: strIdList) {
371 rgIds.append(strId.toInt());
372 }
373 return rgIds;
374}
375
376QVariant AppSettings::firstRunPromptsIdsListToVariant(const QList<int>& rgIds)
377{
378 QStringList strList;
379 for (int id: rgIds) {
380 strList.append(QString::number(id));
381 }
382 return QVariant(strList.join(","));
383}
384
386{
387 QList<int> rgIds = firstRunPromptsIdsVariantToList(firstRunPromptIdsShown()->rawValue());
388 if (!rgIds.contains(id)) {
389 rgIds.append(id);
390 firstRunPromptIdsShown()->setRawValue(firstRunPromptsIdsListToVariant(rgIds));
391 }
392}
393
399QLocale::Language AppSettings::_qLocaleLanguageEarlyAccess(void)
400{
401 QSettings settings;
402
403 // Note that the AppSettings group has no group name
404 QLocale::Language localeLanguage = static_cast<QLocale::Language>(settings.value(qLocaleLanguageName).toInt());
405 for (auto& languageInfo: _rgLanguageInfo) {
406 if (languageInfo.languageId == localeLanguage) {
407 return localeLanguage;
408 }
409 }
410
411#ifdef QT_DEBUG
412 if (localeLanguage == QLocale::Esperanto) {
413 return localeLanguage;
414 }
415#endif
416
417 localeLanguage = QLocale::AnyLanguage;
418 settings.setValue(qLocaleLanguageName, localeLanguage);
419
420 return localeLanguage;
421}
#define qgcApp()
#define QGC_LOGGING_CATEGORY(name, categoryStr)
#define DECLARE_SETTINGSFACT_NO_FUNC(CLASS, NAME)
#define DECLARE_SETTINGSFACT(CLASS, NAME)
#define DECLARE_SETTINGGROUP(NAME, GROUP)
Application Settings.
Definition AppSettings.h:10
static QVariant firstRunPromptsIdsListToVariant(const QList< int > &rgIds)
QString videoSavePath()
QString logSavePath()
QString telemetrySavePath()
QString crashSavePath()
static constexpr const char * settingsDirectory
QString parameterSavePath()
static constexpr const char * photoDirectory
static constexpr const char * telemetryDirectory
Q_INVOKABLE void firstRunPromptIdsMarkIdAsShown(int id)
static constexpr const char * logDirectory
void savePathsChanged()
QString settingsSavePath()
static constexpr const char * videoDirectory
static QList< int > firstRunPromptsIdsVariantToList(const QVariant &firstRunPromptIds)
static constexpr const char * missionDirectory
static constexpr const char * mavlinkActionsDirectory
QString photoSavePath()
QString missionSavePath()
static constexpr const char * crashDirectory
QString mavlinkActionsSavePath()
static constexpr const char * parameterDirectory
Holds the meta data associated with a Fact.
QVariantList enumValues() const
void setEnumInfo(const QStringList &strings, const QVariantList &values)
void removeEnumInfo(const QVariant &value)
Used to remove values from the enum lists after the meta data has been loaded.
A Fact is used to hold a single value within the system.
Definition Fact.h:17
void rawValueChanged(const QVariant &value)
void setRawValue(const QVariant &value)
Definition Fact.cc:134
QList< QGCMAVLink::FirmwareClass_t > supportedFirmwareClasses()
Returns list of firmwares which are supported by the system.
static FirmwarePluginManager * instance()
QGCPalette is used in QML ui to expose color properties for the QGC palette.
Definition QGCPalette.h:83
static void setGlobalTheme(Theme newTheme)
A SettingsFact is Fact which holds a QSettings value.
bool ensureDirectoryExists(const QString &path)
QString joinPath(const QString &dir, const QString &name)
bool runningUnitTests()
void showAppMessage(const QString &message, const QString &title)
Modal application message. Queued if the UI isn't ready yet.
Definition AppMessages.cc:9