swift
aircraftcfgparser.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2015 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <atomic>
7 #include <tuple>
8 
9 #include <QDateTime>
10 #include <QDir>
11 #include <QFile>
12 #include <QFileInfo>
13 #include <QFileInfoList>
14 #include <QFlags>
15 #include <QIODevice>
16 #include <QList>
17 #include <QMetaType>
18 #include <QSettings>
19 #include <QStringView>
20 #include <QTextStream>
21 #include <Qt>
22 #include <QtGlobal>
23 
24 #include "config/buildconfig.h"
25 #include "misc/fileutils.h"
26 #include "misc/logmessage.h"
29 #include "misc/statusmessagelist.h"
30 #include "misc/stringutils.h"
31 #include "misc/worker.h"
32 
33 using namespace swift::config;
34 using namespace swift::misc;
35 using namespace swift::misc::simulation;
36 using namespace swift::misc::simulation::fscommon;
37 using namespace swift::misc::network;
38 
39 namespace swift::misc::simulation::fscommon
40 {
41  // response for async. loading
42  using LoaderResponse = std::tuple<CAircraftCfgEntriesList, CAircraftModelList, CStatusMessageList>;
43 
44  CAircraftCfgParser::CAircraftCfgParser(const CSimulatorInfo &simInfo, QObject *parent)
45  : IAircraftModelLoader(simInfo, parent)
46  {}
47 
49  {
50  return new CAircraftCfgParser(simInfo, parent);
51  }
52 
54  {
55  // that should be safe as long as the worker uses deleteLater (which it does)
56  if (m_parserWorker) { m_parserWorker->waitForFinished(); }
57  }
58 
59  void CAircraftCfgParser::startLoadingFromDisk(LoadMode mode, const ModelConsolidationCallback &modelConsolidation,
60  const QStringList &modelDirectories)
61  {
62  static const CStatusMessage statusLoadingOk(this, CStatusMessage::SeverityInfo,
63  u"Aircraft config parser loaded data");
64  static const CStatusMessage statusLoadingError(this, CStatusMessage::SeverityError,
65  u"Aircraft config parser did NOT load data");
66 
67  const CSimulatorInfo simulator = this->getSimulator();
68  const QStringList modelDirs = this->getInitializedModelDirectories(modelDirectories, simulator);
69  const QStringList excludedDirectoryPatterns(
71 
72  if (mode.testFlag(LoadInBackground))
73  {
74  if (m_parserWorker && !m_parserWorker->isFinished()) { return; }
75  emit this->diskLoadingStarted(simulator, mode);
76  m_parserWorker =
77  CWorker::fromTask(this, "CAircraftCfgParser::startLoadingFromDisk",
78  [this, modelDirs, excludedDirectoryPatterns, simulator, modelConsolidation]() {
79  CStatusMessageList msgs;
80  const CAircraftCfgEntriesList aircraftCfgEntriesList =
81  this->performParsing(modelDirs, excludedDirectoryPatterns, msgs);
82  CAircraftModelList models;
83  if (msgs.isSuccess())
84  {
85  models = aircraftCfgEntriesList.toAircraftModelList(simulator, true, msgs);
86  if (modelConsolidation) { modelConsolidation(models, true); }
87  }
88  return std::make_tuple(aircraftCfgEntriesList, models, msgs);
89  });
90  m_parserWorker->thenWithResult<LoaderResponse>(this, [this, simulator](const LoaderResponse &tuple) {
91  m_loadingMessages = std::get<2>(tuple);
93  {
94  m_parsedCfgEntriesList = std::get<0>(tuple);
95  const CAircraftModelList models(std::get<1>(tuple));
96  const bool hasData = !models.isEmpty();
97  if (hasData) { this->setModelsForSimulator(models, this->getSimulator()); }
98  // currently I treat no data as error
99  m_loadingMessages.push_front(hasData ? statusLoadingOk : statusLoadingError);
100  }
102  emit this->loadingFinished(m_loadingMessages, simulator, ParsedData);
103  });
104  }
105  else if (mode == LoadDirectly)
106  {
107  emit this->diskLoadingStarted(simulator, mode);
108 
109  CStatusMessageList msgs;
110  m_parsedCfgEntriesList = this->performParsing(modelDirs, excludedDirectoryPatterns, msgs);
111  const CAircraftModelList models(m_parsedCfgEntriesList.toAircraftModelList(simulator, true, msgs));
112  m_loadingMessages = msgs;
114  const bool hasData = !models.isEmpty();
115  if (hasData) { this->setCachedModels(models, this->getSimulator()); }
116  // currently I treat no data as error
117  emit this->loadingFinished(hasData ? statusLoadingOk : statusLoadingError, simulator, ParsedData);
118  }
119  }
120 
121  bool CAircraftCfgParser::isLoadingFinished() const { return !m_parserWorker || m_parserWorker->isFinished(); }
122 
123  CAircraftCfgEntriesList CAircraftCfgParser::performParsing(const QStringList &directories,
124  const QStringList &excludeDirectories,
125  CStatusMessageList &messages)
126  {
127  CAircraftCfgEntriesList entries;
128  for (const QString &dir : directories)
129  {
130  entries.push_back(this->performParsing(dir, excludeDirectories, messages));
131  }
132  return entries;
133  }
134 
135  CAircraftCfgEntriesList CAircraftCfgParser::performParsing(const QString &directory,
136  const QStringList &excludeDirectories,
137  CStatusMessageList &messages)
138  {
139  //
140  // function has to be threadsafe
141  //
142 
143  if (m_cancelLoading) { return CAircraftCfgEntriesList(); }
144 
145  // excluded?
146  if (CFileUtils::isExcludedDirectory(directory, excludeDirectories) || isExcludedSubDirectory(directory))
147  {
148  const CStatusMessage m = CStatusMessage(this).info(u"Skipping directory '%1' (excluded)") << directory;
149  messages.push_back(m);
150  return CAircraftCfgEntriesList();
151  }
152 
153  // set directory with name filters, get aircraft.cfg and sub directories
154  static const QString NoNameFilter;
155  QDir dir(directory, NoNameFilter, QDir::Name, QDir::Files | QDir::AllDirs | QDir::NoDotAndDotDot);
156  // TODO TZ: still have to figure out how msfs2024 handles this
157  // for MSFS2020 we only need aircraft.cfg
158  // MSFS2024 has aircraft.cfg only in communityfolder
159  // a solution for the aircraft from the marketplace may be prepared by ASOBO
160  dir.setNameFilters(fileNameFilters(getSimulator().isMSFS(), getSimulator().isMSFS2024()));
161  if (!dir.exists())
162  {
163  return CAircraftCfgEntriesList(); // can happen if there are shortcuts or linked dirs not available
164  }
165 
166  const QString currentDir = dir.absolutePath();
168  emit this->loadingProgress(this->getSimulator(), QStringLiteral("Parsing '%1'").arg(currentDir), -1);
169 
170  // Dirs last is crucial, since I will break recursion on "aircraft.cfg" level
171  // with T514 this behaviour has been changed
172  const QFileInfoList files =
174 
175  // the sim.cfg/aircraft.cfg file should have an *.air file sibling
176  // if not we assume these files can be ignored
177  const QDir dirForAir(directory, CFsDirectories::airFileFilter(), QDir::Name,
179  const int airFilesCount = dirForAir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::DirsLast).size();
180  const bool hasAirFiles = airFilesCount > 0;
181 
182  if (getSimulator().isP3D() && !hasAirFiles)
183  {
184  const CStatusMessage m = CStatusMessage(this).warning(u"No \"air\" files in '%1'") << currentDir;
185  messages.push_back(m);
186  }
187 
188  for (const auto &fileInfo : files)
189  {
190  if (m_cancelLoading) { return CAircraftCfgEntriesList(); }
191  if (fileInfo.isDir())
192  {
193  const QString nextDir = fileInfo.absoluteFilePath();
194  if (currentDir.startsWith(nextDir, Qt::CaseInsensitive)) { continue; } // do not go up
195  if (dir == currentDir) { continue; } // do not recursively call same directory
196 
197  const CAircraftCfgEntriesList subList(performParsing(nextDir, excludeDirectories, messages));
198  if (messages.isSuccess()) { result.push_back(subList); }
199  else
200  {
201  const CStatusMessage m = CStatusMessage(this).warning(u"Parsing failed for '%1'") << nextDir;
202  messages.push_back(m);
203  }
204  }
205  else
206  {
207  // Enforce air files only for P3D
208  if (getSimulator().isP3D() && !hasAirFiles) { continue; }
209 
210  // due to the filter we expect only "aircraft.cfg"/"sim.cfg" here
211  // remark: in a 1st version I have used QSettings to parse to file as ini file
212  // unfortunately some files are malformed which could end up in wrong data
213 
214  const QString fileName = fileInfo.absoluteFilePath(); // full path and name
215  bool fileOk = false;
216  CStatusMessageList fileMsgs;
217  const CAircraftCfgEntriesList fileResults =
218  CAircraftCfgParser::performParsingOfSingleFile(fileName, fileOk, fileMsgs);
219  if (!fileOk)
220  {
221  const CStatusMessage m = CStatusMessage(this).warning(u"Parsing of '%1' failed") << fileName;
222  messages.push_back(fileMsgs);
223  continue;
224  }
225 
226  result.push_back(fileResults);
227 
228  // With T514 we do not skip not anymore
229  // return result; // do not go any deeper in file tree, we found aircraft.cfg
230  }
231  }
232 
233  // all files finished,
234  // normally reached when no aircraft.cfg is found
235  return result;
236  }
237 
239  CStatusMessageList &msgs)
240  {
241  // due to the filter we expect only "aircraft.cfg" files here
242  // remark: in a 1st version I have used QSettings to parse to file as ini file
243  // unfortunately some files are malformed which could end up in wrong data
244 
245  ok = false;
246  const QString fnFixed = CFileUtils::fixWindowsUncPath(fileName);
247  QFile file(fnFixed); // includes path
248  if (!file.open(QFile::ReadOnly | QFile::Text))
249  {
250  const CStatusMessage m =
251  CStatusMessage(static_cast<CAircraftCfgParser *>(nullptr)).warning(u"Unable to read file '%1'")
252  << fnFixed;
253  msgs.push_back(m);
254  return CAircraftCfgEntriesList();
255  }
256 
257  QTextStream in(&file);
258  QList<CAircraftCfgEntries> tempEntries;
259 
260  // parse through the file
261  QString atcType;
262  QString atcModel;
263  QString fltSection("[FLTSIM.0]");
264  static const QString fltSectionStr("[FLTSIM.%1]");
265 
266  int fltsimCounter = 0;
267  FileSection currentSection = Unknown;
268  const bool isRotorcraftPath = fileName.contains("rotorcraft", Qt::CaseInsensitive);
269 
270  while (!in.atEnd())
271  {
272  const QString lineFixed(in.readLine().trimmed());
273  if (lineFixed.isEmpty()) { continue; }
274  if (lineFixed.startsWith("["))
275  {
276  if (lineFixed.startsWith("[GENERAL]", Qt::CaseInsensitive))
277  {
278  currentSection = General;
279  continue;
280  }
281  if (lineFixed.startsWith(fltSection, Qt::CaseInsensitive))
282  {
283  CAircraftCfgEntries e(fileName, fltsimCounter);
284  if (isRotorcraftPath) { e.setRotorcraft(true); }
285  tempEntries.append(e);
286  currentSection = Fltsim;
287  fltSection = fltSectionStr.arg(++fltsimCounter);
288  continue;
289  }
290  currentSection = Unknown;
291  continue;
292  }
293  switch (currentSection)
294  {
295  case General:
296  {
297  if (lineFixed.startsWith("//")) { break; }
298  if (atcType.isEmpty() || atcModel.isEmpty())
299  {
300  const QString c = getFixedIniLineContent(lineFixed);
301  if (lineFixed.startsWith("atc_type", Qt::CaseInsensitive)) { atcType = c; }
302  /*else if (lineFixed.startsWith("atc_model", Qt::CaseInsensitive))
303  {
304  atcModel = c;
305  }*/
306  else if (lineFixed.startsWith("icao_type_designator", Qt::CaseInsensitive)) { atcModel = c; }
307  }
308  }
309  break;
310  case Fltsim:
311  {
312  if (lineFixed.startsWith("//")) { break; }
313  CAircraftCfgEntries &e = tempEntries[tempEntries.size() - 1];
314  if (lineFixed.startsWith("atc_", Qt::CaseInsensitive))
315  {
316  if (lineFixed.startsWith("atc_parking_codes", Qt::CaseInsensitive))
317  {
318  e.setAtcParkingCode(getFixedIniLineContent(lineFixed));
319  }
320  else if (lineFixed.startsWith("atc_airline", Qt::CaseInsensitive))
321  {
322  e.setAtcAirline(getFixedIniLineContent(lineFixed));
323  }
324  else if (lineFixed.startsWith("atc_id_color", Qt::CaseInsensitive))
325  {
326  e.setAtcIdColor(getFixedIniLineContent(lineFixed));
327  }
328  }
329  else if (lineFixed.startsWith("ui_", Qt::CaseInsensitive))
330  {
331  if (lineFixed.startsWith("ui_manufacturer", Qt::CaseInsensitive))
332  {
333  e.setUiManufacturer(getFixedIniLineContent(lineFixed));
334  }
335  else if (lineFixed.startsWith("ui_typerole", Qt::CaseInsensitive))
336  {
337  bool r = getFixedIniLineContent(lineFixed).toLower().contains("rotor");
338  e.setRotorcraft(r);
339  }
340  else if (lineFixed.startsWith("ui_type", Qt::CaseInsensitive))
341  {
342  e.setUiType(getFixedIniLineContent(lineFixed));
343  }
344  else if (lineFixed.startsWith("ui_variation", Qt::CaseInsensitive))
345  {
346  e.setUiVariation(getFixedIniLineContent(lineFixed));
347  }
348  }
349  else if (lineFixed.startsWith("description", Qt::CaseInsensitive))
350  {
351  e.setDescription(getFixedIniLineContent(lineFixed));
352  }
353  else if (lineFixed.startsWith("texture", Qt::CaseInsensitive))
354  {
355  e.setTexture(getFixedIniLineContent(lineFixed));
356  }
357  else if (lineFixed.startsWith("createdBy", Qt::CaseInsensitive))
358  {
359  e.setCreatedBy(getFixedIniLineContent(lineFixed));
360  }
361  else if (lineFixed.startsWith("sim", Qt::CaseInsensitive))
362  {
363  e.setSimName(getFixedIniLineContent(lineFixed));
364  }
365  else if (lineFixed.startsWith("title", Qt::CaseInsensitive))
366  {
367  e.setTitle(getFixedIniLineContent(lineFixed));
368  }
369  }
370  break;
371  default:
372  case Unknown: break;
373  }
374  } // all lines
375  file.close();
376 
377  // store all entries
378  const QFileInfo fileInfo(fnFixed);
379  QDateTime fileTimestamp(fileInfo.lastModified());
380  if (!fileTimestamp.isValid() || fileInfo.birthTime() > fileTimestamp) { fileTimestamp = fileInfo.birthTime(); }
381  Q_ASSERT_X(fileTimestamp.isValid(), Q_FUNC_INFO, "Missing file timestamp");
382 
384  for (const CAircraftCfgEntries &e : std::as_const(tempEntries))
385  {
386  if (e.getTitle().isEmpty())
387  {
388  const CStatusMessage m = CStatusMessage(static_cast<CAircraftCfgParser *>(nullptr))
389  .info(u"FS model in %1, index %2 has no title")
390  << fileName << e.getIndex();
391  msgs.push_back(m);
392  continue;
393  }
394  CAircraftCfgEntries newEntries(e);
395  newEntries.setAtcModel(atcModel);
396  newEntries.setAtcType(atcType);
397  newEntries.setUtcTimestamp(fileTimestamp);
398  result.push_back(newEntries);
399  }
400  ok = true;
401  return result; // do not go any deeper in file tree, we found aircraft.cfg
402  }
403 
404  QString CAircraftCfgParser::fixedStringContent(const QSettings &settings, const QString &key)
405  {
406  return fixedStringContent(settings.value(key));
407  }
408 
409  QString CAircraftCfgParser::fixedStringContent(const QVariant &qv)
410  {
411  if (qv.isNull() || !qv.isValid())
412  {
413  return {}; // normal when there is no settings value
414  }
415  if (static_cast<QMetaType::Type>(qv.typeId()) == QMetaType::QStringList)
416  {
417  const QStringList l = qv.toStringList();
418  return l.join(",").trimmed();
419  }
420  if (static_cast<QMetaType::Type>(qv.typeId()) == QMetaType::QString) { return qv.toString().trimmed(); }
421  Q_ASSERT(false);
422  return {};
423  }
424 
425  QString CAircraftCfgParser::getFixedIniLineContent(const QString &line)
426  {
427  if (line.isEmpty()) { return {}; }
428 
429  // Remove inline comments starting with ;
430  const int indexComment = line.indexOf(';');
431  QString content = QStringView { line }.left(indexComment - 1).trimmed().toString();
432 
433  const int index = line.indexOf('=');
434  if (index < 0) { return {}; }
435  if (line.length() < index + 1) { return {}; }
436 
437  content = QStringView { content }.mid(index + 1).trimmed().toString();
438 
439  // fix "" strings, some are malformed and just contain " at beginning, not at the end
440  if (hasBalancedQuotes(content, '"'))
441  {
442  // seems to be OK
443  // ex: title=B767-300ER - Condor "Retro Jet"
444 
445  if (content.size() > 2 && content.startsWith('"') && content.endsWith('"'))
446  {
447  // completly in quotes, example title="B767-300ER - Condor Retro Jet"
448  // we assume the quotes shall be removed
449  content.remove(0, 1);
450  content.chop(1);
451  }
452  }
453  else
454  {
455  // UNBALANCED
456 
457  // could be OK, example title=B767-300ER - Condor Retro Jet"
458  // if (content.endsWith('"')) { content.remove(content.size() - 1, 1); }
459 
460  // Unlikely, title="B767-300ER - Condor "Retro Jet
461  if (content.startsWith('"')) { content.remove(0, 1); }
462  }
463 
464  // fix C style linebreaks
465  content.replace("\\n", " ");
466  content.replace("\\t", " ");
467  return content;
468  }
469 
470  // TODO TZ: MSFS2024 currently has aircraft.cfg only in the community folder
471  const QStringList &CAircraftCfgParser::fileNameFilters(bool isMSFS, bool isMSFS2024)
472  {
473  if (CBuildConfig::buildWordSize() == 32 || isMSFS || isMSFS2024)
474  {
475  static const QStringList f({ "aircraft.cfg" });
476  return f;
477  }
478  else
479  {
480  static const QStringList f({ "aircraft.cfg", "sim.cfg" });
481  return f;
482  }
483  }
484 
485  bool CAircraftCfgParser::isExcludedSubDirectory(const QString &checkDirectory)
486  {
487  if (checkDirectory.isEmpty()) { return false; }
488  const QString dir = CFileUtils::lastPathSegment(checkDirectory).toLower();
489  if (dir == u"texture" || dir.startsWith("texture.")) { return true; }
490  if (dir == u"sound" || dir == "soundai") { return true; }
491  if (dir == u"panel") { return true; }
492  if (dir == u"model") { return true; }
493  return false;
494  }
495 } // namespace swift::misc::simulation::fscommon
496 
497 Q_DECLARE_METATYPE(swift::misc::simulation::fscommon::LoaderResponse)
static QString lastPathSegment(const QString &path)
Last path segment a/b/c => c.
Definition: fileutils.cpp:151
static QString fixWindowsUncPath(const QString &filePath)
UNC file paths on Qt start with "/", but UNC file paths only work when they start with "//".
Definition: fileutils.cpp:444
static bool isExcludedDirectory(const QDir &directory, const QStringList &excludeDirectories, Qt::CaseSensitivity cs=osFileNameCaseSensitivity())
Directory to be excluded?
Definition: fileutils.cpp:242
Derived & warning(const char16_t(&format)[N])
Set the severity to warning, providing a format string.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition: sequence.h:305
void push_front(const T &value)
Insert as first element.
Definition: sequence.h:308
bool isEmpty() const
Synonym for empty.
Definition: sequence.h:285
Streamable status message, e.g.
constexpr static auto SeverityError
Status severities.
constexpr static auto SeverityInfo
Status severities.
Status messages, e.g. from Core -> GUI.
bool isSuccess() const
All messages are marked as success.
static CWorker * fromTask(QObject *owner, const QString &name, F &&task)
Returns a new worker object which lives in a new thread.
Definition: worker.h:225
void freezeOrder()
Current order of list will be new order values.
void setUtcTimestamp(const QDateTime &timestamp)
Set timestamp.
Value object encapsulating a list of aircraft models.
Simple hardcoded info about the corresponding simulator.
Definition: simulatorinfo.h:41
Load the aircraft for a simulator.
void loadingProgress(const CSimulatorInfo &simulator, const QString &message, int progressPercentage)
Loading progress, normally from disk.
std::function< int(swift::misc::simulation::CAircraftModelList &, bool)> ModelConsolidationCallback
Callback to consolidate data, normally with DB data.
std::atomic< bool > m_cancelLoading
flag, requesting to cancel loading
const CSimulatorInfo & getSimulator() const
Simulator.
QStringList getInitializedModelDirectories(const QStringList &modelDirectories, const CSimulatorInfo &simulator) const
Get model directories from settings if empty, otherwise checked and UNC path fixed.
void loadingFinished(const CStatusMessageList &status, const CSimulatorInfo &simulator, IAircraftModelLoader::LoadFinishedInfo info)
Parsing is finished or cache has been loaded.
void diskLoadingStarted(const CSimulatorInfo &simulator, IAircraftModelLoader::LoadMode loadMode)
Disk loading started.
settings::CMultiSimulatorSettings m_settings
settings
CStatusMessageList m_loadingMessages
loading messages
@ LoadInBackground
load in background, asyncronously
@ LoadDirectly
load synchronously (blocking), normally for testing
CStatusMessage setCachedModels(const CAircraftModelList &models, const CSimulatorInfo &simulator)
Look like IMultiSimulatorModelCaches interface.
Definition: modelcaches.h:550
void setModelsForSimulator(const CAircraftModelList &models, const CSimulatorInfo &simulator)
Set models.
Definition: modelcaches.h:597
Set of aircraft.cfg entries representing an aircraft (FSX)
void setSimName(const QString &simName)
Simulator name.
void setDescription(const QString &description)
Description.
void setCreatedBy(const QString &createdBy)
Created by.
void setAtcModel(const QString &atcModel)
ATC model.
void setUiType(const QString &type)
UI type (e.g. A321-231 IAE)
void setRotorcraft(bool isRotorcraft)
Is Rotorcraft?
void setUiVariation(const QString &variation)
UI variation (e.g. White,Green)
void setAtcIdColor(const QString &color)
ATC color (e.g. 0xffffffff)
void setUiManufacturer(const QString &manufacturer)
UI manufacturer (e.g. Airbus)
void setAtcParkingCode(const QString &parkingCode)
Parking code.
swift::misc::simulation::CAircraftModelList toAircraftModelList(bool ignoreDuplicatesAndEmptyModelStrings, CStatusMessageList &msgs) const
As aircraft models.
Utility, parsing the aircraft.cfg files.
virtual bool isLoadingFinished() const
Loading finished?
CAircraftCfgParser(const CSimulatorInfo &simInfo, QObject *parent=nullptr)
Constructor.
static CAircraftCfgParser * createModelLoader(const CSimulatorInfo &simInfo, QObject *parent=nullptr)
Create an parser object for given simulator.
static CAircraftCfgEntriesList performParsingOfSingleFile(const QString &fileName, bool &ok, CStatusMessageList &msgs)
Parse a single file.
virtual void startLoadingFromDisk(LoadMode mode, const ModelConsolidationCallback &modelConsolidation, const QStringList &modelDirectories)
Start the loading process from disk.
static const QString & airFileFilter()
.air file filter
QStringList getModelExcludeDirectoryPatternsOrDefault(const CSimulatorInfo &simulator) const
Model exclude patterns per simulator.
Free functions in swift::misc.
SWIFT_MISC_EXPORT bool hasBalancedQuotes(const QString &in, char quote='"')
Has balanced quotes.
bool isValid() const const
bool open(FILE *fh, QIODeviceBase::OpenMode mode, QFileDevice::FileHandleFlags handleFlags)
virtual void close() override
QDateTime birthTime() const const
QDateTime lastModified() const const
void append(QList< T > &&value)
qsizetype size() const const
QObject * parent() const const
QVariant value(QAnyStringView key) const const
QString arg(Args &&... args) const const
void chop(qsizetype n)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString left(qsizetype n) &&
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) &&
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString toLower() const const
QString trimmed() const const
QString join(QChar separator) const const
CaseInsensitive
bool atEnd() const const
QString readLine(qint64 maxlen)
bool isNull() const const
bool isValid() const const
QString toString() const const
QStringList toStringList() const const
int typeId() const const