• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Stellarium / stellarium / 17068063291

19 Aug 2025 11:22AM UTC coverage: 11.766%. Remained the same
17068063291

push

github

alex-w
Reformatting

14706 of 124990 relevant lines covered (11.77%)

18303.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

20.14
/src/core/StelSkyCultureMgr.cpp
1
/*
2
 * Stellarium
3
 * Copyright (C) 2006 Fabien Chereau
4
 * 
5
 * This program is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU General Public License
7
 * as published by the Free Software Foundation; either version 2
8
 * of the License, or (at your option) any later version.
9
 * 
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 * 
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
18
 */
19

20
#include "StelSkyCultureMgr.hpp"
21
#include "StelFileMgr.hpp"
22
#include "StelTranslator.hpp"
23
#include "StelLocaleMgr.hpp"
24
#include "StelApp.hpp"
25

26
#include <md4c-html.h>
27

28
#include <QSettings>
29
#include <QString>
30
#include <QStringList>
31
#include <QVariant>
32
#include <QDebug>
33
#include <QMap>
34
#include <QMapIterator>
35
#include <QDir>
36
#include <QJsonObject>
37
#include <QJsonDocument>
38
#include <QRegularExpression>
39

40
namespace
41
{
42

43
#if (QT_VERSION>=QT_VERSION_CHECK(5, 14, 0))
44
constexpr auto SkipEmptyParts = Qt::SkipEmptyParts;
45
#else
46
constexpr auto SkipEmptyParts = QString::SkipEmptyParts;
47
#endif
48

49
void applyStyleToMarkdown(QString& string)
×
50
{
51
        static const auto pPattern = []{
×
52
                // Locates any paragraph. This will make sure we get the smallest paragraphs. If
53
                // we look for the styled ones from the beginning, we'll get too greedy matches.
54
                QRegularExpression p("<p>.+?</p>", QRegularExpression::DotMatchesEverythingOption);
×
55
                p.optimize();
×
56
                return p;
×
57
        }();
×
58
        static const auto pStyledPattern = []{
×
59
                // This is for the actual paragraphs we are interested in.
60
                QRegularExpression sp(R"regex(<p>(.+?){:\s*\.(\S+?)\s*}\s*</p>)regex",
61
                                      QRegularExpression::DotMatchesEverythingOption);
×
62
                sp.optimize();
×
63
                return sp;
×
64
        }();
×
65
        bool replaced;
66
        do
×
67
        {
68
                replaced = false;
×
69
                for(auto matches = pPattern.globalMatch(string); matches.hasNext(); )
×
70
                {
71
                        const auto& match = matches.next();
×
72
                        const auto substr = match.captured(0);
×
73
                        if(substr.contains(pStyledPattern))
×
74
                        {
75
                                // Don't replace the original string directly: otherwise
76
                                // we'll again have greedy matches.
77
                                auto newSubstr = substr;
×
78
                                newSubstr.replace(pStyledPattern, R"(<p class="\2">\1</p>)");
×
79

80
                                // Also force the caption to be below the image,
81
                                // rather than to the right of it.
82
                                static const QRegularExpression re("(<img [^>]+>)");
×
83
                                newSubstr.replace(re, "\\1<br>");
×
84

85
                                string.replace(substr, newSubstr);
×
86

87
                                replaced = true;
×
88
                                break;
×
89
                        }
×
90
                }
×
91
        } while(replaced);
92
}
×
93

94
QString markdownToHTML(QString input)
×
95
{
96
        const auto inputUTF8 = input.toStdString();
×
97

98
        std::string outputUTF8;
×
99
        ::md_html(inputUTF8.data(), inputUTF8.size(),
×
100
                  [](const char* html, const MD_SIZE size, void* output)
×
101
                  { static_cast<std::string*>(output)->append(html, size); },
×
102
                  &outputUTF8, MD_DIALECT_GITHUB, 0);
103

104
        auto result = QString::fromStdString(outputUTF8);
×
105
        applyStyleToMarkdown(result);
×
106
        return result;
×
107
}
×
108

109
QString convertReferenceLinks(QString text)
×
110
{
111
        static const QRegularExpression re(" ?\\[#([0-9]+)\\]", QRegularExpression::MultilineOption);
×
112
        text.replace(re,
×
113
                     "<sup><a href=\"#cite_\\1\">[\\1]</a></sup>");
114
        return text;
×
115
}
116

117
}
118

119
QString StelSkyCultureMgr::getSkyCultureEnglishName(const QString& idFromJSON) const
57✔
120
{
121
        const auto skyCultureId = idFromJSON;
57✔
122
        const QString descPath = StelFileMgr::findFile("skycultures/" + skyCultureId + "/description.md");
57✔
123
        if (descPath.isEmpty())
57✔
124
        {
125
                qWarning() << "Can't find description for skyculture" << skyCultureId;
×
126
                return idFromJSON;
×
127
        }
128

129
        QFile f(descPath);
57✔
130
        if (!f.open(QIODevice::ReadOnly))
57✔
131
        {
132
                qWarning().nospace() << "Failed to open sky culture description file " << descPath << ": " << f.errorString();
×
133
                return idFromJSON;
×
134
        }
135

136
        for (int lineNum = 1;; ++lineNum)
57✔
137
        {
138
                const auto line = QString::fromUtf8(f.readLine()).trimmed();
57✔
139
                if (line.isEmpty()) continue;
57✔
140
                if (!line.startsWith("#"))
57✔
141
                {
142
                        qWarning().nospace() << "Sky culture description file " << descPath << " at line "
×
143
                                             << lineNum << " has wrong format (expected a top-level header, got " << line;
×
144
                        return idFromJSON;
×
145
                }
146
                return line.mid(1).trimmed();
57✔
147
        }
57✔
148

149
        qWarning() << "Failed to find sky culture name in" << descPath;
150
        return idFromJSON;
151
}
57✔
152

153
StelSkyCultureMgr::StelSkyCultureMgr(): flagOverrideUseCommonNames(false), flagUseAbbreviatedNames(false)
1✔
154
{
155
        setObjectName("StelSkyCultureMgr");
1✔
156
        if (StelApp::isInitialized()) // allow unit test...
1✔
157
        {
158
                QSettings *conf=StelApp::getInstance().getSettings();
×
159
                setFlagUseAbbreviatedNames(conf->value("viewing/flag_constellation_abbreviations", false).toBool());
×
160
        }
161
        makeCulturesList(); // First load needed for testing only.
1✔
162
}
1✔
163

164
StelSkyCultureMgr::~StelSkyCultureMgr()
1✔
165
{
166
}
1✔
167

168
void StelSkyCultureMgr::makeCulturesList()
1✔
169
{
170
        QSet<QString> cultureDirNames = StelFileMgr::listContents("skycultures",StelFileMgr::Directory);
1✔
171
        for (const auto& dir : std::as_const(cultureDirNames))
58✔
172
        {
173
                constexpr char indexFileName[] = "/index.json";
57✔
174
                const QString filePath = StelFileMgr::findFile("skycultures/" + dir + indexFileName);
57✔
175
                if (filePath.isEmpty())
57✔
176
                {
177
                        qCritical() << "Failed to find" << indexFileName << "file in sky culture directory" << QDir::toNativeSeparators(dir);
×
178
                        continue;
×
179
                }
180
                QFile file(filePath);
57✔
181
                if (!file.open(QFile::ReadOnly))
57✔
182
                {
183
                        qCritical() << "Failed to open" << indexFileName << "file in sky culture directory" << QDir::toNativeSeparators(dir);
×
184
                        continue;
×
185
                }
186
                const auto jsonText = file.readAll();
57✔
187
                if (jsonText.isEmpty())
57✔
188
                {
189
                        qCritical() << "Failed to read data from" << indexFileName << "file in sky culture directory"
×
190
                                    << QDir::toNativeSeparators(dir);
×
191
                        continue;
×
192
                }
193
                QJsonParseError error;
57✔
194
                const auto jsonDoc = QJsonDocument::fromJson(jsonText, &error);
57✔
195
                if (error.error != QJsonParseError::NoError)
57✔
196
                {
197
                        qCritical().nospace() << "Failed to parse " << indexFileName << " from sky culture directory "
×
198
                                              << QDir::toNativeSeparators(dir) << ": " << error.errorString();
×
199
                        continue;
×
200
                }
201
                if (!jsonDoc.isObject())
57✔
202
                {
203
                        qCritical() << "Failed to find the expected JSON structure in" << indexFileName << " from sky culture directory"
×
204
                                    << QDir::toNativeSeparators(dir);
×
205
                        continue;
×
206
                }
207
                const auto data = jsonDoc.object();
57✔
208

209
                auto& culture = dirToNameEnglish[dir];
57✔
210
                culture.path = StelFileMgr::dirName(filePath);
57✔
211
                const auto id = data["id"].toString();
57✔
212
                if(id != dir)
57✔
213
                        qWarning() << "Sky culture id" << id << "doesn't match directory name" << dir;
×
214
                culture.id = id;
57✔
215
                culture.englishName = getSkyCultureEnglishName(dir);
57✔
216
                culture.region = data["region"].toString();
57✔
217
                if (culture.region.length()==0)
57✔
218
                {
219
                        qWarning() << "No geographic region declared in skyculture" << id << ". setting \"World\"";
×
220
                        culture.region = "World";
×
221
                }
222
                if (data["constellations"].isArray())
57✔
223
                {
224
                        culture.constellations = data["constellations"].toArray();
57✔
225
                }
226
                else
227
                {
228
                        qWarning() << "No \"constellations\" array found in JSON data in sky culture directory"
×
229
                                   << QDir::toNativeSeparators(dir);
×
230
                }
231

232
                culture.asterisms = data["asterisms"].toArray();
57✔
233
                culture.langsUseNativeNames = data["langs_use_native_names"].toArray();
57✔
234

235
                culture.boundariesType = StelSkyCulture::BoundariesType::None; // default value if not specified in the JSON file
57✔
236
                if (data.contains("edges") && data.contains("edges_type"))
57✔
237
                {
238
                        const QString type = data["edges_type"].toString();
11✔
239
                        const QString typeSimp = type.simplified().toUpper();
11✔
240
                        static const QMap<QString, StelSkyCulture::BoundariesType> map={
241
                                {"IAU", StelSkyCulture::BoundariesType::IAU},
×
242
                                {"OWN", StelSkyCulture::BoundariesType::Own},
×
243
                                {"NONE", StelSkyCulture::BoundariesType::None}
×
244
                        };
15✔
245
                        if (!map.contains(typeSimp))
11✔
246
                                qWarning().nospace() << "Unexpected edges_type value in sky culture " << dir
×
247
                                                     << ": " << type << ". Will resort to Own.";
×
248
                        culture.boundariesType = map.value(typeSimp, StelSkyCulture::BoundariesType::Own);
11✔
249
                }
11✔
250
                culture.boundaries = data["edges"].toArray();
57✔
251
                culture.boundariesEpoch = data["edges_epoch"].toString("J2000");
57✔
252
                culture.fallbackToInternationalNames = (flagOverrideUseCommonNames || data["fallback_to_international_names"].toBool());
57✔
253
                culture.names = data["common_names"].toObject();
57✔
254

255
                if (data.contains("zodiac"))
57✔
256
                        culture.zodiac = data["zodiac"].toObject();
3✔
257
                if (data.contains("lunar_system"))
57✔
258
                        culture.lunarSystem = data["lunar_system"].toObject();
6✔
259

260
                const auto classifications = data["classification"].toArray();
57✔
261
                if (classifications.isEmpty())
57✔
262
                {
263
                        culture.classification = StelSkyCulture::INCOMPLETE;
×
264
                }
265
                else
266
                {
267
                        static const QMap <QString, StelSkyCulture::CLASSIFICATION>classificationMap={
268
                                { "traditional",  StelSkyCulture::TRADITIONAL},
×
269
                                { "historical",   StelSkyCulture::HISTORICAL},
×
270
                                { "ethnographic", StelSkyCulture::ETHNOGRAPHIC},
×
271
                                { "single",       StelSkyCulture::SINGLE},
×
272
                                { "comparative",  StelSkyCulture::COMPARATIVE},
×
273
                                { "personal",     StelSkyCulture::PERSONAL},
×
274
                                { "incomplete",   StelSkyCulture::INCOMPLETE},
×
275
                        };
65✔
276
                        const auto classificationStr = classifications[0].toString(); // We'll take only the first item for now.
57✔
277
                        const auto classification=classificationMap.value(classificationStr.toLower(), StelSkyCulture::INCOMPLETE);
57✔
278
                        if (!classificationMap.contains(classificationStr.toLower()))
57✔
279
                        {
280
                                qInfo() << "Skyculture " << dir << "has UNKNOWN classification: " << classificationStr;
×
281
                                qInfo() << "Please edit index.json and change to a supported value. For now, this equals 'incomplete'";
×
282
                        }
283
                        culture.classification = classification;
57✔
284
                }
57✔
285
        }
57✔
286
}
3✔
287

288
//! Init itself from a config file.
289
void StelSkyCultureMgr::init()
×
290
{
291
        QSettings* settings = StelApp::getInstance().getSettings();
×
292
        Q_ASSERT(settings);
×
293
        setFlagOverrideUseCommonNames(settings->value("viewing/flag_skyculture_always_fallback_to_international_names", false).toBool());
×
294

295
        makeCulturesList(); // Reload after setting this flag!
×
296
        defaultSkyCultureID = StelApp::getInstance().getSettings()->value("localization/sky_culture", "modern").toString();
×
297
        if (defaultSkyCultureID=="western") // switch to new Sky Culture ID
×
298
                defaultSkyCultureID = "modern";
×
299
        setCurrentSkyCultureID(defaultSkyCultureID);
×
300
}
×
301

302
void StelSkyCultureMgr::reloadSkyCulture()
×
303
{
304
        const QString currentID=currentSkyCulture.id;
×
305
        makeCulturesList();
×
306
        setCurrentSkyCultureID(currentID);
×
307
}
×
308

309
//! Set the current sky culture from the passed directory
310
bool StelSkyCultureMgr::setCurrentSkyCultureID(const QString& cultureDir)
2✔
311
{
312
        QString scID = cultureDir;
2✔
313
        bool result = true;
2✔
314
        // make sure culture definition exists before attempting or will die
315
        if (directoryToSkyCultureEnglish(cultureDir) == "")
2✔
316
        {
317
                qWarning() << "Invalid sky culture directory: " << QDir::toNativeSeparators(cultureDir);
×
318
                scID = "modern";
×
319
                result = false;
×
320
        }
321

322
        currentSkyCulture = dirToNameEnglish[scID];
2✔
323

324
        // Lookup culture Style!
325
        setScreenLabelStyle(getScreenLabelStyle());
2✔
326
        setInfoLabelStyle(getInfoLabelStyle());
2✔
327

328
        emit currentSkyCultureChanged(currentSkyCulture);
2✔
329
        emit currentSkyCultureIDChanged(currentSkyCulture.id);
2✔
330
        return result;
2✔
331
}
2✔
332

333
// Set the default sky culture from the ID.
334
bool StelSkyCultureMgr::setDefaultSkyCultureID(const QString& id)
1✔
335
{
336
        // make sure culture definition exists before attempting or will die
337
        if (directoryToSkyCultureEnglish(id) == "")
1✔
338
        {
339
                qWarning() << "Invalid sky culture ID: " << id;
1✔
340
                return false;
1✔
341
        }
342
        defaultSkyCultureID = id;
×
343
        QSettings* conf = StelApp::getInstance().getSettings();
×
344
        Q_ASSERT(conf);
×
345
        conf->setValue("localization/sky_culture", id);
×
346

347
        emit defaultSkyCultureIDChanged(id);
×
348
        return true;
×
349
}
350

351
QString StelSkyCultureMgr::getCurrentSkyCultureNameI18() const
×
352
{
353
        const StelTranslator& trans = StelApp::getInstance().getLocaleMgr().getSkyCultureDescriptionsTranslator();
×
354
        return trans.qtranslate(currentSkyCulture.englishName, "sky culture");
×
355
}
356

357
QString StelSkyCultureMgr::getCurrentSkyCultureEnglishName() const
1✔
358
{
359
        return currentSkyCulture.englishName;
1✔
360
}
361

362
StelSkyCulture::BoundariesType StelSkyCultureMgr::getCurrentSkyCultureBoundariesType() const
1✔
363
{
364
        return currentSkyCulture.boundariesType;
1✔
365
}
366

367
StelSkyCulture::CLASSIFICATION StelSkyCultureMgr::getCurrentSkyCultureClassificationIdx() const
1✔
368
{
369
        return currentSkyCulture.classification;
1✔
370
}
371

372
QString StelSkyCultureMgr::getCurrentSkyCultureHtmlClassification() const
×
373
{
374
        QString classification, description, color;
×
375
        switch (currentSkyCulture.classification)
×
376
        {
377
                case StelSkyCulture::ETHNOGRAPHIC:
×
378
                        color = "#33ff33"; // "green" area
×
379
                        classification = qc_("ethnographic", "sky culture classification");
×
380
                        description = q_("Provided by ethnographic researchers based on interviews of indigenous people.");
×
381
                        break;
×
382
                case StelSkyCulture::HISTORICAL:
×
383
                        color = "#33ff33"; // "green" area
×
384
                        classification = qc_("historical", "sky culture classification");
×
385
                        description = q_("Provided by historians based on historical written sources from a (usually short) period of the past.");
×
386
                        break;
×
387
                case StelSkyCulture::SINGLE:
×
388
                        color = "#33ff33"; // "green" area
×
389
                        classification = qc_("single", "sky culture classification");
×
390
                        description = q_("Represents a single source like a historical atlas, or publications of a single author.");
×
391
                        break;
×
392
                case StelSkyCulture::COMPARATIVE:
×
393
                        color = "#2090ff"; // "blue" area
×
394
                        classification = qc_("comparative", "sky culture classification");
×
395
                        description = q_("Compares and confronts elements from at least two sky cultures with each other.");
×
396
                        break;
×
397
                case StelSkyCulture::TRADITIONAL:
×
398
                        color = "#33ff33"; // "green" area
×
399
                        classification = qc_("traditional", "sky culture classification");
×
400
                        description = q_("Content represents 'common' knowledge by several members of an ethnic community, and the sky culture has been developed by members of such community.");
×
401
                        break;
×
402
                case StelSkyCulture::PERSONAL:
×
403
                        color = "#ffff00"; // "yellow" area
×
404
                        classification = qc_("personal", "sky culture classification");
×
405
                        description = q_("This is a personally developed sky culture which is not founded in published historical or ethnological research. Stellarium may include it when it is 'pretty enough' without really approving its contents.");
×
406
                        break;
×
407
                case StelSkyCulture::INCOMPLETE:
×
408
                        color = "#ff6633"; // "red" area
×
409
                        classification = qc_("incomplete", "sky culture classification");
×
410
                        description = q_("The accuracy of the sky culture description cannot be given, although it looks like it is built on a solid background. More work would be needed.");
×
411
                        break;
×
412
                default: // undefined
×
413
                        color = "#ff00cc";
×
414
                        classification = qc_("undefined", "sky culture classification");
×
415
                        description = QString();
×
416
                        break;
×
417
        }
418

419
        QString html = QString();
×
420
        if (!description.isEmpty()) // additional info for sky culture (metainfo): let's use italic
×
421
                html = QString("<dl><dt><span style='color:%4;'>%5</span> <strong>%1: %2</strong></dt><dd><em>%3</em></dd></dl>").arg(q_("Classification"), classification, description, color, QChar(0x25CF));
×
422

423
        return html;
×
424
}
×
425

426

427
std::pair<QString/*color*/,QString/*info*/> StelSkyCultureMgr::getLicenseDescription(const QString& license, const bool singleLicenseForAll) const
×
428
{
429
        QString color, description;
×
430

431
        if (license.isEmpty())
×
432
        {
433
                color = "#2090ff"; // "blue" area
×
434
                description = q_("This sky culture is provided under unknown license. Please ask authors for details about license for this sky culture.");
×
435
        }
436
        else if (license.contains("GPL", Qt::CaseSensitive))
×
437
        {
438
                color = "#33ff33"; // "green" area; free license
×
439
                if (singleLicenseForAll)
×
440
                        description = q_("This sky culture is provided under GNU General Public License. You can use it for commercial "
×
441
                                         "and non-commercial purposes, freely adapt it and share adapted work.");
442
                else
443
                        description = q_("You can use it for commercial and non-commercial purposes, freely adapt it and share adapted work.");
×
444
        }
445
        else if (license.contains("MIT", Qt::CaseSensitive))
×
446
        {
447
                color = "#33ff33"; // "green" area; free license
×
448
                if (singleLicenseForAll)
×
449
                        description = q_("This sky culture is provided under MIT License. You can use it for commercial and non-commercial "
×
450
                                         "purposes, freely adapt it and share adapted work.");
451
                else
452
                        description = q_("You can use it for commercial and non-commercial purposes, freely adapt it and share adapted work.");
×
453
        }
454
        else if (license.contains("Public Domain"))
×
455
        {
456
                color = "#33ff33"; // "green" area; free license
×
457
                if (singleLicenseForAll)
×
458
                        description = q_("This sky culture is distributed as public domain.");
×
459
                else
460
                        description = q_("This is distributed as public domain.");
×
461
        }
462
        else if (license.startsWith("CC", Qt::CaseSensitive) || license.contains("Creative Commons", Qt::CaseInsensitive))
×
463
        {
464
                if (singleLicenseForAll)
×
465
                        description = q_("This sky culture is provided under Creative Commons License.");
×
466

467
                QStringList details = license.split(" ", SkipEmptyParts);
×
468

469
                const QMap<QString, QString>options = {
470
                        { "BY",       q_("You may distribute, remix, adapt, and build upon this sky culture, even commercially, as long "
×
471
                                         "as you credit authors for the original creation.") },
472
                        { "BY-SA",    q_("You may remix, adapt, and build upon this sky culture even for commercial purposes, as long as "
×
473
                                         "you credit authors and license the new creations under the identical terms. This license is often "
474
                                         "compared to “copyleft” free and open source software licenses.") },
475
                        { "BY-ND",    q_("You may reuse this sky culture for any purpose, including commercially; however, adapted work "
×
476
                                         "cannot be shared with others, and credit must be provided by you.") },
477
                        { "BY-NC",    q_("You may remix, adapt, and build upon this sky culture non-commercially, and although your new works "
×
478
                                         "must also acknowledge authors and be non-commercial, you don’t have to license your derivative works "
479
                                         "on the same terms.") },
480
                        { "BY-NC-SA", q_("You may remix, adapt, and build upon this sky culture non-commercially, as long as you credit "
×
481
                                         "authors and license your new creations under the identical terms.") },
482
                        { "BY-NC-ND", q_("You may use this sky culture and share them with others as long as you credit authors, but you can’t "
×
483
                                         "change it in any way or use it commercially.") },
484
                };
×
485

486
                color = "#33ff33"; // "green" area; free license
×
487
                if (license.contains("ND", Qt::CaseSensitive))
×
488
                        color = "#ffff00"; // "yellow" area; nonfree license - weak restrictions
×
489
                if (license.contains("NC", Qt::CaseSensitive))
×
490
                        color = "#ff6633"; // "red" area; nonfree license - strong restrictions
×
491

492
                if (!details.at(0).startsWith("CC0", Qt::CaseInsensitive)) // Not public domain!
×
493
                        description.append(QString(" %1").arg(options.value(details.at(1), "")));
×
494
                else
495
                        description = q_("This sky culture is distributed as public domain.");
×
496
        }
×
497
        else if (license.contains("FAL", Qt::CaseSensitive) || license.contains("Free Art License", Qt::CaseSensitive))
×
498
        {
499
                color = "#33ff33"; // "green" area; free license
×
500
                description.append(QString(" %1").arg(q_("Free Art License grants the right to freely copy, distribute, and transform.")));
×
501
        }
502

503
        return std::make_pair(color, description);
×
504
}
×
505

506
QString StelSkyCultureMgr::getCurrentSkyCultureHtmlLicense() const
×
507
{
508
        static const QRegularExpression licRe("\\s*\n+\\s*");
×
509
        const auto lines = currentSkyCulture.license.split(licRe, SkipEmptyParts);
×
510
        if (lines.isEmpty()) return "";
×
511

512
        if (lines.size() == 1)
×
513
        {
514
                const auto parts = lines[0].split(":", SkipEmptyParts);
×
515
                const auto licenseName = convertReferenceLinks(parts.size() == 1 ? parts[0] : parts[1]);
×
516
                const auto [color, description] = getLicenseDescription(licenseName, true);
×
517
                if (!description.isEmpty())
×
518
                {
519
                        return QString("<dl><dt><span style='color:%4;'>%5</span> <strong>%1: %2</strong></dt><dd><em>%3</em></dd></dl>")
×
520
                                .arg(q_("License"),
×
521
                                     currentSkyCulture.license.isEmpty() ? q_("unknown") : licenseName,
×
522
                                     description, color, QChar(0x25CF));
×
523
                }
524
                else
525
                {
526
                        return QString("<dl><dt><span style='color:%3;'>%4</span> <strong>%1: %2</strong></dt></dl>")
×
527
                                .arg(q_("License"),
×
528
                                     currentSkyCulture.license.isEmpty() ? q_("unknown") : licenseName, color, QChar(0x25CF));
×
529
                }
530
                return QString{};
531
        }
×
532
        else
533
        {
534
                QString html = "<h1>" + q_("License") + "</h1>\n";
×
535
                QString addendum;
×
536
                for (const auto& line : lines)
×
537
                {
538
                        static const QRegularExpression re("\\s*:\\s*");
×
539
                        const auto parts = line.split(re, SkipEmptyParts);
×
540
                        if (parts.size() == 1)
×
541
                        {
542
                                addendum += line + "<br>\n";
×
543
                                continue;
×
544
                        }
545
                        const auto [color, description] = getLicenseDescription(parts[1], false);
×
546
                        if (description.isEmpty())
×
547
                        {
548
                                html += QString("<dl><dt><span style='color:%2;'>%3</span> <strong>%1</strong></dt></dl>")
×
549
                                               .arg(convertReferenceLinks(line), color, QChar(0x25CF));
×
550
                        }
551
                        else
552
                        {
553
                                html += QString("<dl><dt><span style='color:%3;'>%4</span> <strong>%1</strong></dt><dd><em>%2</em></dd></dl>")
×
554
                                               .arg(convertReferenceLinks(line), description, color, QChar(0x25CF));
×
555
                        }
556
                }
×
557
                return html + addendum;
×
558
        }
×
559
}
×
560

561
QString StelSkyCultureMgr::getCurrentSkyCultureHtmlRegion() const
×
562
{
563
        QString html = "", region = currentSkyCulture.region.trimmed();
×
564
        QString description = q_("The region indicates the geographical area of origin of a given sky culture.");
×
565

566
        // special case: modern sky culture
567
        if (getCurrentSkyCultureID().contains("modern", Qt::CaseInsensitive))
×
568
        {
569
                // TRANSLATIONS: This is the name of a geographical "pseudo-region" on Earth
570
                region = N_("World");
×
571
                description = q_("All 'modern' sky cultures are based on the IAU-approved 88 constellations with standardized boundaries and are used worldwide. The origins of these constellations are pan-European.");
×
572
        }
573

574
        if (!region.isEmpty()) // Region marker is always 'green'
×
575
                html = QString("<dl><dt><span style='color:#33ff33;'>%4</span> <strong>%1 %2</strong></dt><dd><em>%3</em></dd></dl>").arg(q_("Region:"), q_(region), description, QChar(0x25CF));
×
576

577
        return html;
×
578
}
×
579

580
bool StelSkyCultureMgr::setCurrentSkyCultureNameI18(const QString& cultureName)
×
581
{
582
        return setCurrentSkyCultureID(skyCultureI18ToDirectory(cultureName));
×
583
}
584

585
// returns list of human readable culture names in english
586
QStringList StelSkyCultureMgr::getSkyCultureListEnglish(void) const
1✔
587
{
588
        QStringList cultures;
1✔
589
        QMapIterator<QString, StelSkyCulture> i(dirToNameEnglish);
1✔
590
        while(i.hasNext())
58✔
591
        {
592
                i.next();
57✔
593
                cultures << i.value().englishName;
57✔
594
        }
595
        return cultures;
2✔
596
}
1✔
597

598
//! returns newline delimited list of human readable culture names translated to current locale
599
QStringList StelSkyCultureMgr::getSkyCultureListI18(void) const
×
600
{
601
        const StelTranslator& trans = StelApp::getInstance().getLocaleMgr().getSkyCultureDescriptionsTranslator();
×
602
        QStringList cultures;
×
603
        QMapIterator<QString, StelSkyCulture> i(dirToNameEnglish);
×
604
        while (i.hasNext())
×
605
        {
606
                i.next();
×
607
                cultures += trans.qtranslate(i.value().englishName, "sky culture");
×
608
        }
609
        // Sort for GUI use.
610
        std::sort(cultures.begin(), cultures.end(), [](const QString& a, const QString& b)
×
611
                  { return a.localeAwareCompare(b) < 0; });
×
612
        return cultures;
×
613
}
×
614

615
QStringList StelSkyCultureMgr::getSkyCultureListIDs(void) const
1✔
616
{
617
        return dirToNameEnglish.keys();
1✔
618
}
619

620
QString StelSkyCultureMgr::convertMarkdownLevel2Section(const QString& markdown, const QString& sectionName,
×
621
                                                        const qsizetype bodyStartPos, const qsizetype bodyEndPos,
622
                                                        const StelTranslator& trans)
623
{
624
        auto text = markdown.mid(bodyStartPos, bodyEndPos - bodyStartPos);
×
625
        static const QRegularExpression re("^\n*|\n*$");
×
626
        text.replace(re, "");
×
627
        text = trans.qtranslate(text);
×
628

629
        if (sectionName.trimmed() == "References")
×
630
        {
631
                static const QRegularExpression refRe("^ *- \\[#([0-9]+)\\]: (.*)$", QRegularExpression::MultilineOption);
×
632
                text.replace(refRe, "\\1. <span id=\"cite_\\1\">\\2</span>");
×
633
        }
634
        else
635
        {
636
                text = convertReferenceLinks(text);
×
637
        }
638

639
        if (sectionName.trimmed() == "License")
×
640
        {
641
                currentSkyCulture.license = text;
×
642
                return "";
×
643
        }
644

645
        return markdownToHTML(text);
×
646
}
×
647

648
QString StelSkyCultureMgr::descriptionMarkdownToHTML(const QString& markdownInput, const QString& descrPath)
×
649
{
650
        // Section names should be available for translation
651
        (void)NC_("Extras"      , "Name of a section in sky culture description");
652
        (void)NC_("References"  , "Name of a section in sky culture description");
653
        (void)NC_("Authors"     , "Name of a section in sky culture description");
654
        const StelTranslator& trans = StelApp::getInstance().getLocaleMgr().getSkyCultureDescriptionsTranslator();
×
655

656
        // Strip comments before translating
657
        static const QRegularExpression commentPat("<!--.*?-->");
×
658
        const auto markdown = QString(markdownInput).replace(commentPat, "");
×
659

660
        static const QRegularExpression headerPat("^# +(.+)$", QRegularExpression::MultilineOption);
×
661
        const auto match = headerPat.match(markdown);
×
662
        QString name;
×
663
        if (match.isValid())
×
664
        {
665
                name = match.captured(1);
×
666
        }
667
        else
668
        {
669
                qCritical().nospace() << "Failed to get sky culture name in file " << descrPath
×
670
                                      << ": got " << match.lastCapturedIndex() << " matches instead of 1";
×
671
                name = "Unknown";
×
672
        }
673

674
        QString text = "<h1>" + trans.qtranslate(name, "sky culture") + "</h1>";
×
675
        static const QRegularExpression sectionNamePat("^## +(.+)$", QRegularExpression::MultilineOption);
×
676
        QString prevSectionName;
×
677
        qsizetype prevBodyStartPos = -1;
×
678
        for (auto it = sectionNamePat.globalMatch(markdown); it.hasNext(); )
×
679
        {
680
                const auto match = it.next();
×
681
                const auto sectionName = match.captured(1);
×
682
                const auto nameStartPos = match.capturedStart(0);
×
683
                const auto bodyStartPos = match.capturedEnd(0);
×
684
                if (!prevSectionName.isEmpty())
×
685
                {
686
                        const auto sectionText = convertMarkdownLevel2Section(markdown, prevSectionName, prevBodyStartPos, nameStartPos, trans);
×
687
                        if(prevSectionName != "Introduction" && prevSectionName != "Description")
×
688
                                text += "<h2>" + qc_(prevSectionName, "Name of a section in sky culture description") + "</h2>\n";
×
689
                        if (!sectionText.isEmpty())
×
690
                        {
691
                                text += sectionText + "\n";
×
692
                        }
693
                }
×
694
                prevBodyStartPos = bodyStartPos;
×
695
                prevSectionName = sectionName;
×
696
        }
×
697
        if (prevBodyStartPos >= 0)
×
698
        {
699
                const auto sectionText = convertMarkdownLevel2Section(markdown, prevSectionName, prevBodyStartPos, markdown.size(), trans);
×
700
                if (!sectionText.isEmpty())
×
701
                {
702
                        text += "<h2>" + qc_(prevSectionName, "Name of a section in sky culture description") + "</h2>\n";
×
703
                        text += sectionText;
×
704
                }
705
        }
×
706

707
        return text;
×
708
}
×
709

710
QString StelSkyCultureMgr::getCurrentSkyCultureHtmlDescription()
×
711
{
712
        QString lang = StelApp::getInstance().getLocaleMgr().getAppLanguage();
×
713
        if (!QString("pt_BR zh_CN zh_HK zh_TW").contains(lang))
×
714
        {
715
                lang = lang.split("_").at(0);
×
716
        }
717
        const QString descPath = currentSkyCulture.path + "/description.md";
×
718
        const bool pathExists = QFileInfo::exists(descPath);
×
719
        if (!pathExists)
×
720
                qWarning() << "Can't find description for skyculture" << currentSkyCulture.id;
×
721

722
        QString description;
×
723
        if (!pathExists)
×
724
        {
725
                description = QString("<h2>%1</2><p>%2</p>").arg(getCurrentSkyCultureNameI18(), q_("No description"));
×
726
        }
727
        else
728
        {
729
                QFile f(descPath);
×
730
                if(f.open(QIODevice::ReadOnly))
×
731
                {
732
                        const auto markdown = QString::fromUtf8(f.readAll());
×
733
                        description = descriptionMarkdownToHTML(markdown, descPath);
×
734
                }
×
735
                else
736
                {
737
                        qWarning().nospace() << "Failed to open sky culture description file " << descPath << ": " << f.errorString();
×
738
                }
739
        }
×
740

741
        description.append(getCurrentSkyCultureHtmlLicense());
×
742
        description.append(getCurrentSkyCultureHtmlClassification());
×
743
        description.append(getCurrentSkyCultureHtmlRegion());
×
744

745

746
        return description;
×
747
}
×
748

749
QString StelSkyCultureMgr::directoryToSkyCultureEnglish(const QString& directory) const
3✔
750
{
751
        return dirToNameEnglish[directory].englishName;
3✔
752
}
753

754
QString StelSkyCultureMgr::directoryToSkyCultureI18(const QString& directory) const
×
755
{
756
        QString culture = dirToNameEnglish[directory].englishName;
×
757
        if (culture=="")
×
758
        {
759
                qWarning().nospace() << "StelSkyCultureMgr::directoryToSkyCultureI18("
×
760
                                     << QDir::toNativeSeparators(directory) << "): could not find directory";
×
761
                return "";
×
762
        }
763
        return q_(culture);
×
764
}
×
765

766
QString StelSkyCultureMgr::skyCultureI18ToDirectory(const QString& cultureName) const
×
767
{
768
        const StelTranslator& trans = StelApp::getInstance().getLocaleMgr().getSkyCultureDescriptionsTranslator();
×
769
        QMapIterator<QString, StelSkyCulture> i(dirToNameEnglish);
×
770
        while (i.hasNext())
×
771
        {
772
                i.next();
×
773
                if (trans.qtranslate(i.value().englishName, "sky culture") == cultureName)
×
774
                        return i.key();
×
775
        }
776
        return "";
×
777
}
×
778

779
void StelSkyCultureMgr::setFlagOverrideUseCommonNames(bool override)
×
780
{
781
        flagOverrideUseCommonNames=override;
×
782
        emit flagOverrideUseCommonNamesChanged(override);
×
783
}
×
784

785
void StelSkyCultureMgr::setFlagUseAbbreviatedNames(bool b)
×
786
{
787
        flagUseAbbreviatedNames=b;
×
788
        StelApp::immediateSave("viewing/flag_constellation_abbreviations", b);
×
789
        emit flagUseAbbreviatedNamesChanged(b);
×
790
}
×
791

792
StelObject::CulturalDisplayStyle StelSkyCultureMgr::convertCulturalDisplayStyleFromCSVstring(const QString &csv)
×
793
{
794
        static const QMap<QString, StelObject::CulturalDisplayStyle> cdsEnumParts=
795
        { {"none", StelObject::CulturalDisplayStyle::NONE},
×
796
          {"modern", StelObject::CulturalDisplayStyle::Modern},
×
797
          {"byname", StelObject::CulturalDisplayStyle::Byname},
×
798
          {"ipa", StelObject::CulturalDisplayStyle::IPA},
×
799
          {"translated", StelObject::CulturalDisplayStyle::Translated},
×
800
          {"translit", StelObject::CulturalDisplayStyle::Translit},
×
801
          {"pronounce", StelObject::CulturalDisplayStyle::Pronounce},
×
802
          {"native", StelObject::CulturalDisplayStyle::Native}};
×
803

804
        StelObject::CulturalDisplayStyle styleEnum = StelObject::CulturalDisplayStyle::NONE;
×
805
        const QStringList styleParts=csv.split(",", SkipEmptyParts);
×
806

807
        for (const QString &part: styleParts)
×
808
        {
809
                styleEnum = static_cast<StelObject::CulturalDisplayStyle>( int(styleEnum) |  int(cdsEnumParts.value(part.trimmed().toLower(), StelObject::CulturalDisplayStyle::NONE)));
×
810
        }
811
        return styleEnum;
×
812
}
×
813

814
QString StelSkyCultureMgr::convertCulturalDisplayStyleToCSVstring(const StelObject::CulturalDisplayStyle style)
×
815
{
816
        return QVariant::fromValue(style).toString().replace('_', ',');
×
817
}
818

819

820
// Returns the screen labeling setting for the currently active skyculture
821
StelObject::CulturalDisplayStyle StelSkyCultureMgr::getScreenLabelStyle() const
2✔
822
{
823
        // This is needed for testing mode
824
        if (defaultSkyCultureID.isEmpty())
2✔
825
                return StelObject::CulturalDisplayStyle::Translated;
2✔
826

827
        static QSettings *conf=StelApp::getInstance().getSettings();
×
828
        QVariant val= conf->value(QString("SCScreenLabelStyle/%1").arg(getCurrentSkyCultureID()), "Translated");
×
829
        //qDebug() << "StelSkyCultureMgr::getScreenLabelStyle(): found " << val << "(" << val.toString() << ")";
830
        return convertCulturalDisplayStyleFromCSVstring(val.toString());
×
831
}
×
832
// Scripting version
833
QString StelSkyCultureMgr::getScreenLabelStyleString() const
×
834
{
835
        return convertCulturalDisplayStyleToCSVstring(getScreenLabelStyle());
×
836
}
837

838

839
// Sets the screen labeling setting for the currently active skyculture
840
void StelSkyCultureMgr::setScreenLabelStyle(const StelObject::CulturalDisplayStyle style)
2✔
841
{
842
        // This is needed for testing mode
843
        if (defaultSkyCultureID.isEmpty())
2✔
844
                return;
2✔
845

846
        static QSettings *conf=StelApp::getInstance().getSettings();
×
847
        conf->setValue(QString("SCScreenLabelStyle/%1").arg(getCurrentSkyCultureID()), convertCulturalDisplayStyleToCSVstring(style));
×
848
        //qInfo() << QString("SCScreenLabelStyle/%1=%2").arg(getCurrentSkyCultureID(), convertCulturalDisplayStyleToCSVstring(style));
849
        emit screenLabelStyleChanged(style);
×
850
}
851

852
// style can be the enum string like Native_IPA_Translated, or a comma-separated string like "Translated, native, IPA"
853
void StelSkyCultureMgr::setScreenLabelStyle(const QString &style)
×
854
{
855
        setScreenLabelStyle(convertCulturalDisplayStyleFromCSVstring(style));
×
856
}
×
857

858
// Returns the InfoString Labeling setting for the currently active skyculture
859
StelObject::CulturalDisplayStyle StelSkyCultureMgr::getInfoLabelStyle() const
2✔
860
{
861
        // This is needed for testing mode
862
        if (defaultSkyCultureID.isEmpty())
2✔
863
                return StelObject::CulturalDisplayStyle::Translated;
2✔
864

865
        static QSettings *conf=StelApp::getInstance().getSettings();
×
866
        QVariant val= conf->value(QString("SCInfoLabelStyle/%1").arg(getCurrentSkyCultureID()), "Translated");
×
867
        //qDebug() << "StelSkyCultureMgr::getInfoLabelStyle(): found " << val << "(" << val.toString() << ")";
868
        return convertCulturalDisplayStyleFromCSVstring(val.toString());
×
869
}
×
870
// Scripting version
871
QString StelSkyCultureMgr::getInfoLabelStyleString() const
×
872
{
873
        return convertCulturalDisplayStyleToCSVstring(getInfoLabelStyle());
×
874
}
875

876
// Sets the InfoString Labeling setting for the currently active skyculture
877
void StelSkyCultureMgr::setInfoLabelStyle(const StelObject::CulturalDisplayStyle style)
2✔
878
{
879
        // This is needed for testing mode
880
        if (defaultSkyCultureID.isEmpty())
2✔
881
                return;
2✔
882

883
        static QSettings *conf=StelApp::getInstance().getSettings();
×
884
        conf->setValue(QString("SCInfoLabelStyle/%1").arg(getCurrentSkyCultureID()), convertCulturalDisplayStyleToCSVstring(style));
×
885
        //qInfo() << QString("SCInfoLabelStyle/%1=%2").arg(getCurrentSkyCultureID(), convertCulturalDisplayStyleToCSVstring(style));
886
        emit infoLabelStyleChanged(style);
×
887
}
888

889
void StelSkyCultureMgr::setInfoLabelStyle(const QString &style)
×
890
{
891
        setInfoLabelStyle(convertCulturalDisplayStyleFromCSVstring(style));
×
892
}
×
893

894
QString StelSkyCultureMgr::createCulturalLabel(const StelObject::CulturalName &cName,
×
895
                                               const StelObject::CulturalDisplayStyle style,
896
                                               const QString &commonNameI18n,
897
                                               const QString &abbrevI18n) const
898
{
899
        // At least while many fields have not been filled, we should create a few fallbacks
900
        // If native contains non-Latin glyphs, pronounce or transliteration is mandatory.
901
        QString pronounceStr=(cName.pronounceI18n.isEmpty() ? cName.pronounce : cName.pronounceI18n);
×
902
        QString nativeOrPronounce = (cName.native.isEmpty() ? cName.pronounceI18n : cName.native);
×
903
        QString pronounceOrNative = (cName.pronounceI18n.isEmpty() ? cName.native : cName.pronounceI18n);
×
904
        QString translitOrPronounce = (cName.transliteration.isEmpty() ? pronounceStr : cName.transliteration);
×
905

906
        // If you call this with an actual argument abbrevI18n, you really only want a short label.
907
        if (flagUseAbbreviatedNames && !abbrevI18n.isNull())
×
908
                return (abbrevI18n.startsWith('.') ? QString() : abbrevI18n);
×
909

910
        const int styleInt=int(style);
×
911
        QString label;
×
912
        switch (style)
×
913
        {
914
                case StelObject::CulturalDisplayStyle::Native: // native if available. fallback to pronounce and english entries
×
915
                        return cName.native.isEmpty() ? (cName.pronounceI18n.isEmpty() ? cName.translatedI18n : cName.pronounceI18n) : cName.native;
×
916
                case StelObject::CulturalDisplayStyle::Pronounce: // pronounce if available. fallback to native
×
917
                        return pronounceOrNative;
×
918
                case StelObject::CulturalDisplayStyle::Translit:
×
919
                        return translitOrPronounce;
×
920
                case StelObject::CulturalDisplayStyle::Translated:
×
921
                        return (cName.translatedI18n.isEmpty() ? (pronounceStr.isEmpty() ? cName.native : pronounceStr) : cName.translatedI18n);
×
922
                case StelObject::CulturalDisplayStyle::IPA: // really only IPA?
×
923
                        return cName.IPA;
×
924
                case StelObject::CulturalDisplayStyle::NONE: // fully non-cultural!
×
925
                case StelObject::CulturalDisplayStyle::Modern:
926
                        return commonNameI18n;
×
927
                case StelObject::CulturalDisplayStyle::Byname:
×
928
                        return (cName.bynameI18n.isEmpty() ? (pronounceStr.isEmpty() ? cName.native : pronounceStr) : cName.bynameI18n);
×
929
                default:
×
930
                        break;
×
931
        }
932
        // simple cases done. Now build-up label of form "primary [common transliteration aka pronounce, scientific transliteration] [IPA] (translation) <modern>"
933
        // (The first of the square brackets is formatted with "turtle brackets" so that only IPA is in regular square brackets.)
934
        // "primary" is usually either native or one of the reading aids, or translation or even modern when other options are switched off.
935
        // Rules:
936
        // Styles with Native_* start with just native, but we must fallback to other strings if native is empty.
937
        // Styles with Pronounce_* start with Pronounce or transliteration, but Pronounce_Translit_... must show Translit in turtle brackets when both exist.
938
        // Styles with Translit_* start with Transliteration or fallback to Pronounce
939
        // Styles with ...IPA... must add IPA (when exists) in square brackets, conditionally after the comma-separated turtle brackets with Pronounce and Transliteration
940
        // Styles with ...Translated have translation in brackets appended
941
        // Styles with ...Modern have the modern name (commonNameI18n) in slightly decorative curved angle brackets appended
942

943
        QStringList braced; // the contents of the secondary term, i.e. pronunciation and transliteration
×
944
        if (styleInt & int(StelObject::CulturalDisplayStyle::Native))
×
945
        {
946
                label=nativeOrPronounce;
×
947
                // Add pronounciation and Translit in braces
948
                if (styleInt & int(StelObject::CulturalDisplayStyle::Pronounce))
×
949
                        braced.append(pronounceStr);
×
950
                if (styleInt & int(StelObject::CulturalDisplayStyle::Translit))
×
951
                        braced.append(cName.transliteration);
×
952
        }
953
        else // not including native
954
        {
955
                // Use the first valid of pronunciation or transliteration as main name (fallback to native), add the others in braces if applicable
956
                if (styleInt & int(StelObject::CulturalDisplayStyle::Pronounce))
×
957
                {
958
                        label=pronounceOrNative;
×
959
                        if (styleInt & int(StelObject::CulturalDisplayStyle::Translit))
×
960
                                braced.append(cName.transliteration);
×
961
                }
962

963
                else if (styleInt & int(StelObject::CulturalDisplayStyle::Translit))
×
964
                {
965
                        label=translitOrPronounce;
×
966
                }
967
        }
968

969
        braced.removeDuplicates();
×
970
        braced.removeOne(QString(""));
×
971
        braced.removeOne(QString());
×
972
        braced.removeOne(label); // avoid repeating the main thing if it was used as fallback!
×
973
        if (!braced.isEmpty()) label.append(QString(" %1%3%2").arg(QChar(0x2997), QChar(0x2998), braced.join(", ")));
×
974

975
        // Add IPA (where possible)
976
        if ((styleInt & int(StelObject::CulturalDisplayStyle::IPA)) && (!cName.IPA.isEmpty()) && (label != cName.IPA))
×
977
                label.append(QString(" [%1]").arg(cName.IPA));
×
978

979
        // Add translation and optional byname in brackets
980

981
        QStringList bracketed;
×
982
        if ((styleInt & int(StelObject::CulturalDisplayStyle::Translated)) && (!cName.translatedI18n.isEmpty()))
×
983
        {
984
                if (label.isEmpty())
×
985
                        label=cName.translatedI18n;
×
986
                else if (!label.startsWith(cName.translatedI18n, Qt::CaseInsensitive)) // seems useless to add translation into same string
×
987

988
                        //label.append(QString(" (%1)").arg(cName.translatedI18n));
989
                        bracketed.append(cName.translatedI18n);
×
990
        }
991

992
        if ( (styleInt & int(StelObject::CulturalDisplayStyle::Byname)) && (!cName.bynameI18n.isEmpty()))
×
993
                bracketed.append(cName.bynameI18n);
×
994
        if (!bracketed.isEmpty())
×
995
                label.append(QString(" (%1)").arg(bracketed.join(", ")));
×
996

997

998
        // Add an explanatory modern name in decorative angle brackets
999
        if ((styleInt & int(StelObject::CulturalDisplayStyle::Modern)) && (!commonNameI18n.isEmpty()) && (!label.startsWith(commonNameI18n)) && (commonNameI18n!=cName.translatedI18n))
×
1000
                label.append(QString(" %1%3%2").arg(QChar(0x29FC), QChar(0x29FD), commonNameI18n));
×
1001
        if ((styleInt & int(StelObject::CulturalDisplayStyle::Modern)) && label.isEmpty()) // if something went wrong?
×
1002
                label=commonNameI18n;
×
1003

1004
        return label;
×
1005
}
×
1006

1007
//! Returns whether current skyculture uses (incorporates) common names.
1008
bool StelSkyCultureMgr::currentSkycultureUsesCommonNames() const
×
1009
{
1010
        return currentSkyCulture.fallbackToInternationalNames;
×
1011
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc