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, lastNotifiedStableVersion)
217DECLARE_SETTINGSFACT(AppSettings, favoriteParameters)
218DECLARE_SETTINGSFACT(AppSettings, showAppLogTimestampAsElapsedTime)
219
221{
222 if (!_indoorPaletteFact) {
223 _indoorPaletteFact = _createSettingsFact(indoorPaletteName);
224 connect(_indoorPaletteFact, &Fact::rawValueChanged, this, &AppSettings::_indoorPaletteChanged);
225 }
226 return _indoorPaletteFact;
227}
228
230{
231 if (!_qLocaleLanguageFact) {
232 _qLocaleLanguageFact = _createSettingsFact(qLocaleLanguageName);
233 connect(_qLocaleLanguageFact, &Fact::rawValueChanged, this, &AppSettings::_qLocaleLanguageChanged);
234
235 FactMetaData* metaData = _qLocaleLanguageFact->metaData();
236 QStringList rgEnumStrings;
237 QVariantList rgEnumValues;
238
239 // System is always an available selection
240 rgEnumStrings.append(_rgLanguageInfo[0].languageName);
241 rgEnumValues.append(_rgLanguageInfo[0].languageId);
242
243 for (const auto& languageInfo: _rgLanguageInfo) {
244 if (_rgReleaseLanguages.contains(languageInfo.languageId)) {
245 rgEnumStrings.append(languageInfo.languageName);
246 rgEnumValues.append(languageInfo.languageId);
247 }
248 }
249 for (const auto& languageInfo: _rgLanguageInfo) {
250 if (_rgPartialLanguages.contains(languageInfo.languageId)) {
251 rgEnumStrings.append(QString(languageInfo.languageName) + AppSettings::tr(" (Partial)"));
252 rgEnumValues.append(languageInfo.languageId);
253 }
254 }
255#ifdef QGC_DAILY_BUILD
256 // Only daily builds include full set of languages for testing purposes
257 for (const auto& languageInfo: _rgLanguageInfo) {
258 if (!_rgReleaseLanguages.contains(languageInfo.languageId) && !_rgPartialLanguages.contains(languageInfo.languageId)) {
259 rgEnumStrings.append(QString(languageInfo.languageName) + AppSettings::tr(" (Test Only)"));
260 rgEnumValues.append(languageInfo.languageId);
261 }
262 }
263#endif
264#ifdef QT_DEBUG
265 // Debug builds include pseudo-localization for UI layout testing
266 rgEnumStrings.append(AppSettings::tr("Pseudo Localization (Test Only)"));
267 rgEnumValues.append(QLocale::Esperanto);
268#endif
269 metaData->setEnumInfo(rgEnumStrings, rgEnumValues);
270
271 if (_qLocaleLanguageFact->enumIndex() == -1) {
272 _qLocaleLanguageFact->setRawValue(QLocale::AnyLanguage);
273 }
274 }
275 return _qLocaleLanguageFact;
276}
277
278void AppSettings::_qLocaleLanguageChanged()
279{
280 qgcApp()->setLanguage();
281}
282
283void AppSettings::_checkSavePathDirectories(void)
284{
285 const QString savePath = this->savePath()->rawValue().toString();
287 // Create all subdirectories
297 }
298}
299
300QString AppSettings::_childSavePath(const char* directory)
301{
302 const QString rootPath = savePath()->rawValue().toString();
303 if (rootPath.isEmpty()) {
304 return QString();
305 }
306
307 const QDir rootDir(rootPath);
308 if (!rootDir.exists()) {
309 return QString();
310 }
311
312 return rootDir.filePath(directory);
313}
314
315void AppSettings::_indoorPaletteChanged(void)
316{
317 QGCPalette::setGlobalTheme(indoorPalette()->rawValue().toBool() ? QGCPalette::Dark : QGCPalette::Light);
318}
319
321{
322 return _childSavePath(missionDirectory);
323}
324
326{
327 return _childSavePath(parameterDirectory);
328}
329
331{
332 return _childSavePath(telemetryDirectory);
333}
334
336{
337 return _childSavePath(logDirectory);
338}
339
341{
342 return _childSavePath(videoDirectory);
343}
344
346{
347 return _childSavePath(photoDirectory);
348}
349
351{
352 return _childSavePath(crashDirectory);
353}
354
356{
357 return _childSavePath(mavlinkActionsDirectory);
358}
359
361{
362 return _childSavePath(settingsDirectory);
363}
364
365QList<int> AppSettings::firstRunPromptsIdsVariantToList(const QVariant& firstRunPromptIds)
366{
367 QList<int> rgIds;
368
369 QStringList strIdList = firstRunPromptIds.toString().split(",", Qt::SkipEmptyParts);
370
371 for (const QString& strId: strIdList) {
372 rgIds.append(strId.toInt());
373 }
374 return rgIds;
375}
376
377QVariant AppSettings::firstRunPromptsIdsListToVariant(const QList<int>& rgIds)
378{
379 QStringList strList;
380 for (int id: rgIds) {
381 strList.append(QString::number(id));
382 }
383 return QVariant(strList.join(","));
384}
385
387{
388 QList<int> rgIds = firstRunPromptsIdsVariantToList(firstRunPromptIdsShown()->rawValue());
389 if (!rgIds.contains(id)) {
390 rgIds.append(id);
391 firstRunPromptIdsShown()->setRawValue(firstRunPromptsIdsListToVariant(rgIds));
392 }
393}
394
400QLocale::Language AppSettings::_qLocaleLanguageEarlyAccess(void)
401{
402 QSettings settings;
403
404 // Note that the AppSettings group has no group name
405 QLocale::Language localeLanguage = static_cast<QLocale::Language>(settings.value(qLocaleLanguageName).toInt());
406 for (auto& languageInfo: _rgLanguageInfo) {
407 if (languageInfo.languageId == localeLanguage) {
408 return localeLanguage;
409 }
410 }
411
412#ifdef QT_DEBUG
413 if (localeLanguage == QLocale::Esperanto) {
414 return localeLanguage;
415 }
416#endif
417
418 localeLanguage = QLocale::AnyLanguage;
419 settings.setValue(qLocaleLanguageName, localeLanguage);
420
421 return localeLanguage;
422}
#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:15
void rawValueChanged(const QVariant &value)
void setRawValue(const QVariant &value)
Definition Fact.cc:135
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