swift
Loading...
Searching...
No Matches
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"
30#include "misc/stringutils.h"
31#include "misc/worker.h"
32
33using namespace swift::config;
34using namespace swift::misc;
35using namespace swift::misc::simulation;
36using namespace swift::misc::simulation::fscommon;
37using namespace swift::misc::network;
38
39namespace swift::misc::simulation::fscommon
40{
41 // response for async. loading
42 using LoaderResponse = std::tuple<CAircraftCfgEntriesList, CAircraftModelList, CStatusMessageList>;
43
47
52
54 {
55 // that should be safe as long as the worker uses deleteLater (which it does)
56 if (m_parserWorker)
57 {
58 m_parserWorker->waitForFinished();
59 }
60 }
61
62 void CAircraftCfgParser::startLoadingFromDisk(LoadMode mode, const ModelConsolidationCallback &modelConsolidation,
63 const QStringList &modelDirectories)
64 {
65 static const CStatusMessage statusLoadingOk(this, CStatusMessage::SeverityInfo,
66 u"Aircraft config parser loaded data");
67 static const CStatusMessage statusLoadingError(this, CStatusMessage::SeverityError,
68 u"Aircraft config parser did NOT load data");
69
70 const CSimulatorInfo simulator = this->getSimulator();
71 const QStringList modelDirs = this->getInitializedModelDirectories(modelDirectories, simulator);
72 const QStringList excludedDirectoryPatterns(
73 m_settings.getModelExcludeDirectoryPatternsOrDefault(simulator)); // copy
74
75 if (mode.testFlag(LoadInBackground))
76 {
77 if (m_parserWorker && !m_parserWorker->isFinished())
78 {
79 return;
80 }
81 emit this->diskLoadingStarted(simulator, mode);
82 m_parserWorker =
83 CWorker::fromTask(this, "CAircraftCfgParser::startLoadingFromDisk",
84 [this, modelDirs, excludedDirectoryPatterns, simulator, modelConsolidation]() {
86 const CAircraftCfgEntriesList aircraftCfgEntriesList =
87 this->performParsing(modelDirs, excludedDirectoryPatterns, msgs);
88 CAircraftModelList models;
89 if (msgs.isSuccess())
90 {
91 models = aircraftCfgEntriesList.toAircraftModelList(simulator, true, msgs);
92 if (modelConsolidation)
93 {
94 modelConsolidation(models, true);
95 }
96 }
97 return std::make_tuple(aircraftCfgEntriesList, models, msgs);
98 });
99 m_parserWorker->thenWithResult<LoaderResponse>(this, [this, simulator](const LoaderResponse &tuple) {
100 m_loadingMessages = std::get<2>(tuple);
101 if (m_loadingMessages.isSuccess())
102 {
103 m_parsedCfgEntriesList = std::get<0>(tuple);
104 const CAircraftModelList models(std::get<1>(tuple));
105 const bool hasData = !models.isEmpty();
106 if (hasData)
107 {
108 this->setModelsForSimulator(models, this->getSimulator());
109 }
110 // currently I treat no data as error
111 m_loadingMessages.push_front(hasData ? statusLoadingOk : statusLoadingError);
112 }
113 m_loadingMessages.freezeOrder();
114 emit this->loadingFinished(m_loadingMessages, simulator, ParsedData);
115 });
116 }
117 else if (mode == LoadDirectly)
118 {
119 emit this->diskLoadingStarted(simulator, mode);
120
122 m_parsedCfgEntriesList = this->performParsing(modelDirs, excludedDirectoryPatterns, msgs);
123 const CAircraftModelList models(m_parsedCfgEntriesList.toAircraftModelList(simulator, true, msgs));
124 m_loadingMessages = msgs;
125 m_loadingMessages.freezeOrder();
126 const bool hasData = !models.isEmpty();
127 if (hasData)
128 {
129 this->setCachedModels(models, this->getSimulator());
130 }
131 // currently I treat no data as error
132 emit this->loadingFinished(hasData ? statusLoadingOk : statusLoadingError, simulator, ParsedData);
133 }
134 }
135
136 bool CAircraftCfgParser::isLoadingFinished() const { return !m_parserWorker || m_parserWorker->isFinished(); }
137
138 CAircraftCfgEntriesList CAircraftCfgParser::performParsing(const QStringList &directories,
139 const QStringList &excludeDirectories,
140 CStatusMessageList &messages)
141 {
143 for (const QString &dir : directories)
144 {
145 entries.push_back(this->performParsing(dir, excludeDirectories, messages));
146 }
147 return entries;
148 }
149
150 CAircraftCfgEntriesList CAircraftCfgParser::performParsing(const QString &directory,
151 const QStringList &excludeDirectories,
152 CStatusMessageList &messages)
153 {
154 //
155 // function has to be threadsafe
156 //
157
158 if (m_cancelLoading)
159 {
160 return {};
161 }
162
163 // excluded?
164 if (CFileUtils::isExcludedDirectory(directory, excludeDirectories) || isExcludedSubDirectory(directory))
165 {
166 const CStatusMessage m = CStatusMessage(this).info(u"Skipping directory '%1' (excluded)") << directory;
167 messages.push_back(m);
168 return {};
169 }
170
171 // set directory with name filters, get aircraft.cfg and sub directories
172 static const QString NoNameFilter;
173 QDir dir(directory, NoNameFilter, QDir::Name, QDir::Files | QDir::AllDirs | QDir::NoDotAndDotDot);
174 // TODO TZ: still have to figure out how msfs2024 handles this
175 // for MSFS2020 we only need aircraft.cfg
176 // MSFS2024 has aircraft.cfg only in communityfolder
177 // a solution for the aircraft from the marketplace may be prepared by ASOBO
178 dir.setNameFilters(fileNameFilters(getSimulator().isMSFS(), getSimulator().isMSFS2024()));
179 if (!dir.exists())
180 {
181 return {}; // can happen if there are shortcuts or linked dirs not available
182 }
183
184 const QString currentDir = dir.absolutePath();
185 CAircraftCfgEntriesList result;
186 emit this->loadingProgress(this->getSimulator(), QStringLiteral("Parsing '%1'").arg(currentDir), -1);
187
188 // Dirs last is crucial, since I will break recursion on "aircraft.cfg" level
189 // with T514 this behaviour has been changed
190 const QFileInfoList files =
192
193 // the sim.cfg/aircraft.cfg file should have an *.air file sibling
194 // if not we assume these files can be ignored
195 const QDir dirForAir(directory, CFsDirectories::airFileFilter(), QDir::Name,
197 const int airFilesCount = dirForAir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::DirsLast).size();
198 const bool hasAirFiles = airFilesCount > 0;
199
200 if (getSimulator().isP3D() && !hasAirFiles)
201 {
202 const CStatusMessage m = CStatusMessage(this).warning(u"No \"air\" files in '%1'") << currentDir;
203 messages.push_back(m);
204 }
205
206 for (const auto &fileInfo : files)
207 {
208 if (m_cancelLoading)
209 {
210 return {};
211 }
212 if (fileInfo.isDir())
213 {
214 const QString nextDir = fileInfo.absoluteFilePath();
215 if (currentDir.startsWith(nextDir, Qt::CaseInsensitive))
216 {
217 continue;
218 } // do not go up
219 if (dir == currentDir)
220 {
221 continue;
222 } // do not recursively call same directory
223
224 const CAircraftCfgEntriesList subList(performParsing(nextDir, excludeDirectories, messages));
225 if (messages.isSuccess())
226 {
227 result.push_back(subList);
228 }
229 else
230 {
231 const CStatusMessage m = CStatusMessage(this).warning(u"Parsing failed for '%1'") << nextDir;
232 messages.push_back(m);
233 }
234 }
235 else
236 {
237 // Enforce air files only for P3D
238 if (getSimulator().isP3D() && !hasAirFiles)
239 {
240 continue;
241 }
242
243 // due to the filter we expect only "aircraft.cfg"/"sim.cfg" here
244 // remark: in a 1st version I have used QSettings to parse to file as ini file
245 // unfortunately some files are malformed which could end up in wrong data
246
247 const QString fileName = fileInfo.absoluteFilePath(); // full path and name
248 bool fileOk = false;
249 CStatusMessageList fileMsgs;
250 const CAircraftCfgEntriesList fileResults =
251 CAircraftCfgParser::performParsingOfSingleFile(fileName, fileOk, fileMsgs);
252 if (!fileOk)
253 {
254 const CStatusMessage m = CStatusMessage(this).warning(u"Parsing of '%1' failed") << fileName;
255 messages.push_back(fileMsgs);
256 continue;
257 }
258
259 result.push_back(fileResults);
260
261 // With T514 we do not skip not anymore
262 // return result; // do not go any deeper in file tree, we found aircraft.cfg
263 }
264 }
265
266 // all files finished,
267 // normally reached when no aircraft.cfg is found
268 return result;
269 }
270
272 CStatusMessageList &msgs)
273 {
274 // due to the filter we expect only "aircraft.cfg" files here
275 // remark: in a 1st version I have used QSettings to parse to file as ini file
276 // unfortunately some files are malformed which could end up in wrong data
277
278 ok = false;
279 const QString fnFixed = CFileUtils::fixWindowsUncPath(fileName);
280 QFile file(fnFixed); // includes path
281 if (!file.open(QFile::ReadOnly | QFile::Text))
282 {
283 const CStatusMessage m =
284 CStatusMessage(static_cast<CAircraftCfgParser *>(nullptr)).warning(u"Unable to read file '%1'")
285 << fnFixed;
286 msgs.push_back(m);
287 return {};
288 }
289
290 QTextStream in(&file);
291 QList<CAircraftCfgEntries> tempEntries;
292
293 // parse through the file
294 QString atcType;
295 QString atcModel;
296 QString fltSection("[FLTSIM.0]");
297 static const QString fltSectionStr("[FLTSIM.%1]");
298
299 int fltsimCounter = 0;
300 FileSection currentSection = Unknown;
301 const bool isRotorcraftPath = fileName.contains("rotorcraft", Qt::CaseInsensitive);
302
303 while (!in.atEnd())
304 {
305 const QString lineFixed(in.readLine().trimmed());
306 if (lineFixed.isEmpty())
307 {
308 continue;
309 }
310 if (lineFixed.startsWith("["))
311 {
312 if (lineFixed.startsWith("[GENERAL]", Qt::CaseInsensitive))
313 {
314 currentSection = General;
315 continue;
316 }
317 if (lineFixed.startsWith(fltSection, Qt::CaseInsensitive))
318 {
319 CAircraftCfgEntries e(fileName, fltsimCounter);
320 if (isRotorcraftPath)
321 {
322 e.setRotorcraft(true);
323 }
324 tempEntries.append(e);
325 currentSection = Fltsim;
326 fltSection = fltSectionStr.arg(++fltsimCounter);
327 continue;
328 }
329 currentSection = Unknown;
330 continue;
331 }
332 switch (currentSection)
333 {
334 case General:
335 {
336 if (lineFixed.startsWith("//"))
337 {
338 break;
339 }
340 if (atcType.isEmpty() || atcModel.isEmpty())
341 {
342 const QString c = getFixedIniLineContent(lineFixed);
343 if (lineFixed.startsWith("atc_type", Qt::CaseInsensitive))
344 {
345 atcType = c;
346 }
347 /*else if (lineFixed.startsWith("atc_model", Qt::CaseInsensitive))
348 {
349 atcModel = c;
350 }*/
351 else if (lineFixed.startsWith("icao_type_designator", Qt::CaseInsensitive))
352 {
353 atcModel = c;
354 }
355 }
356 }
357 break;
358 case Fltsim:
359 {
360 if (lineFixed.startsWith("//"))
361 {
362 break;
363 }
364 CAircraftCfgEntries &e = tempEntries[tempEntries.size() - 1];
365 if (lineFixed.startsWith("atc_", Qt::CaseInsensitive))
366 {
367 if (lineFixed.startsWith("atc_parking_codes", Qt::CaseInsensitive))
368 {
369 e.setAtcParkingCode(getFixedIniLineContent(lineFixed));
370 }
371 else if (lineFixed.startsWith("atc_airline", Qt::CaseInsensitive))
372 {
373 e.setAtcAirline(getFixedIniLineContent(lineFixed));
374 }
375 else if (lineFixed.startsWith("atc_id_color", Qt::CaseInsensitive))
376 {
377 e.setAtcIdColor(getFixedIniLineContent(lineFixed));
378 }
379 }
380 else if (lineFixed.startsWith("ui_", Qt::CaseInsensitive))
381 {
382 if (lineFixed.startsWith("ui_manufacturer", Qt::CaseInsensitive))
383 {
384 e.setUiManufacturer(getFixedIniLineContent(lineFixed));
385 }
386 else if (lineFixed.startsWith("ui_typerole", Qt::CaseInsensitive))
387 {
388 bool r = getFixedIniLineContent(lineFixed).toLower().contains("rotor");
389 e.setRotorcraft(r);
390 }
391 else if (lineFixed.startsWith("ui_type", Qt::CaseInsensitive))
392 {
393 e.setUiType(getFixedIniLineContent(lineFixed));
394 }
395 else if (lineFixed.startsWith("ui_variation", Qt::CaseInsensitive))
396 {
397 e.setUiVariation(getFixedIniLineContent(lineFixed));
398 }
399 }
400 else if (lineFixed.startsWith("description", Qt::CaseInsensitive))
401 {
402 e.setDescription(getFixedIniLineContent(lineFixed));
403 }
404 else if (lineFixed.startsWith("texture", Qt::CaseInsensitive))
405 {
406 e.setTexture(getFixedIniLineContent(lineFixed));
407 }
408 else if (lineFixed.startsWith("createdBy", Qt::CaseInsensitive))
409 {
410 e.setCreatedBy(getFixedIniLineContent(lineFixed));
411 }
412 else if (lineFixed.startsWith("sim", Qt::CaseInsensitive))
413 {
414 e.setSimName(getFixedIniLineContent(lineFixed));
415 }
416 else if (lineFixed.startsWith("title", Qt::CaseInsensitive))
417 {
418 e.setTitle(getFixedIniLineContent(lineFixed));
419 }
420 }
421 break;
422 default:
423 case Unknown: break;
424 }
425 } // all lines
426 file.close();
427
428 // store all entries
429 const QFileInfo fileInfo(fnFixed);
430 QDateTime fileTimestamp(fileInfo.lastModified());
431 if (!fileTimestamp.isValid() || fileInfo.birthTime() > fileTimestamp)
432 {
433 fileTimestamp = fileInfo.birthTime();
434 }
435 Q_ASSERT_X(fileTimestamp.isValid(), Q_FUNC_INFO, "Missing file timestamp");
436
438 for (const CAircraftCfgEntries &e : std::as_const(tempEntries))
439 {
440 if (e.getTitle().isEmpty())
441 {
442 const CStatusMessage m = CStatusMessage(static_cast<CAircraftCfgParser *>(nullptr))
443 .info(u"FS model in %1, index %2 has no title")
444 << fileName << e.getIndex();
445 msgs.push_back(m);
446 continue;
447 }
448 CAircraftCfgEntries newEntries(e);
449 newEntries.setAtcModel(atcModel);
450 newEntries.setAtcType(atcType);
451 newEntries.setUtcTimestamp(fileTimestamp);
452 result.push_back(newEntries);
453 }
454 ok = true;
455 return result; // do not go any deeper in file tree, we found aircraft.cfg
456 }
457
458 QString CAircraftCfgParser::fixedStringContent(const QSettings &settings, const QString &key)
459 {
460 return fixedStringContent(settings.value(key));
461 }
462
463 QString CAircraftCfgParser::fixedStringContent(const QVariant &qv)
464 {
465 if (qv.isNull() || !qv.isValid())
466 {
467 return {}; // normal when there is no settings value
468 }
469 if (static_cast<QMetaType::Type>(qv.typeId()) == QMetaType::QStringList)
470 {
471 const QStringList l = qv.toStringList();
472 return l.join(",").trimmed();
473 }
474 if (static_cast<QMetaType::Type>(qv.typeId()) == QMetaType::QString)
475 {
476 return qv.toString().trimmed();
477 }
478 Q_ASSERT(false);
479 return {};
480 }
481
482 QString CAircraftCfgParser::getFixedIniLineContent(const QString &line)
483 {
484 if (line.isEmpty())
485 {
486 return {};
487 }
488
489 // Remove inline comments starting with ;
490 const int indexComment = line.indexOf(';');
491 QString content = QStringView { line }.left(indexComment - 1).trimmed().toString();
492
493 const int index = line.indexOf('=');
494 if (index < 0)
495 {
496 return {};
497 }
498 if (line.length() < index + 1)
499 {
500 return {};
501 }
502
503 content = QStringView { content }.mid(index + 1).trimmed().toString();
504
505 // fix "" strings, some are malformed and just contain " at beginning, not at the end
506 if (hasBalancedQuotes(content, '"'))
507 {
508 // seems to be OK
509 // ex: title=B767-300ER - Condor "Retro Jet"
510
511 if (content.size() > 2 && content.startsWith('"') && content.endsWith('"'))
512 {
513 // completly in quotes, example title="B767-300ER - Condor Retro Jet"
514 // we assume the quotes shall be removed
515 content.remove(0, 1);
516 content.chop(1);
517 }
518 }
519 else
520 {
521 // UNBALANCED
522
523 // could be OK, example title=B767-300ER - Condor Retro Jet"
524 // if (content.endsWith('"')) { content.remove(content.size() - 1, 1); }
525
526 // Unlikely, title="B767-300ER - Condor "Retro Jet
527 if (content.startsWith('"'))
528 {
529 content.remove(0, 1);
530 }
531 }
532
533 // fix C style linebreaks
534 content.replace("\\n", " ");
535 content.replace("\\t", " ");
536 return content;
537 }
538
539 // TODO TZ: MSFS2024 currently has aircraft.cfg only in the community folder
540 const QStringList &CAircraftCfgParser::fileNameFilters(bool isMSFS, bool isMSFS2024)
541 {
542 if (CBuildConfig::buildWordSize() == 32 || isMSFS || isMSFS2024)
543 {
544 static const QStringList f({ "aircraft.cfg" });
545 return f;
546 }
547 else
548 {
549 static const QStringList f({ "aircraft.cfg", "sim.cfg" });
550 return f;
551 }
552 }
553
554 bool CAircraftCfgParser::isExcludedSubDirectory(const QString &checkDirectory)
555 {
556 if (checkDirectory.isEmpty())
557 {
558 return false;
559 }
560 const QString dir = CFileUtils::lastPathSegment(checkDirectory).toLower();
561 if (dir == u"texture" || dir.startsWith("texture."))
562 {
563 return true;
564 }
565 if (dir == u"sound" || dir == "soundai")
566 {
567 return true;
568 }
569 if (dir == u"panel")
570 {
571 return true;
572 }
573 if (dir == u"model")
574 {
575 return true;
576 }
577 return false;
578 }
579} // namespace swift::misc::simulation::fscommon
580
581Q_DECLARE_METATYPE(swift::misc::simulation::fscommon::LoaderResponse)
static int buildWordSize()
Whether this swift application is build as 32 or 64bit application.
static QString lastPathSegment(const QString &path)
Last path segment a/b/c => c.
static QString fixWindowsUncPath(const QString &filePath)
UNC file paths on Qt start with "/", but UNC file paths only work when they start with "//".
static bool isExcludedDirectory(const QDir &directory, const QStringList &excludeDirectories, Qt::CaseSensitivity cs=osFileNameCaseSensitivity())
Directory to be excluded?
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:308
bool isEmpty() const
Synonym for empty.
Definition sequence.h:285
Streamable status message, e.g.
static constexpr auto SeverityInfo
Status severities.
static constexpr auto SeverityError
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:243
void thenWithResult(F functor)
Connects to a functor to which will be passed the result when the task is finished.
Definition worker.h:264
void setUtcTimestamp(const QDateTime &timestamp)
Set timestamp.
Value object encapsulating a list of aircraft models.
Simple hardcoded info about the corresponding 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
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.
const CSimulatorInfo & getSimulator() const
Simulator.
void diskLoadingStarted(const CSimulatorInfo &simulator, IAircraftModelLoader::LoadMode loadMode)
Disk loading started.
settings::CMultiSimulatorSettings m_settings
settings
IAircraftModelLoader(const CSimulatorInfo &simulator, QObject *parent=nullptr)
Constructor.
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.
void setModelsForSimulator(const CAircraftModelList &models, const CSimulatorInfo &simulator)
Set models.
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.
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.
void startLoadingFromDisk(LoadMode mode, const ModelConsolidationCallback &modelConsolidation, const QStringList &modelDirectories)
Start the loading process from disk.
static const QString & airFileFilter()
.air file filter
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(QObject *parent)
QObject * parent() 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
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