swift
callsign.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2013 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <QRegularExpression>
7 #include <QRegularExpressionMatch>
8 #include <QStringList>
9 #include <Qt>
10 #include <QtGlobal>
11 
13 #include "misc/statusmessagelist.h"
14 #include "misc/stringutils.h"
15 
16 SWIFT_DEFINE_VALUEOBJECT_MIXINS(swift::misc::aviation, CCallsign)
17 
18 namespace swift::misc::aviation
19 {
20  CCallsign::CCallsign(const QString &callsign, CCallsign::TypeHint hint)
21  : m_callsignAsSet(callsign.trimmed()), m_callsign(CCallsign::unifyCallsign(callsign, hint)), m_typeHint(hint)
22  {}
23 
24  CCallsign::CCallsign(const QString &callsign, const QString &telephonyDesignator, CCallsign::TypeHint hint)
25  : m_callsignAsSet(callsign.trimmed()), m_callsign(CCallsign::unifyCallsign(callsign, hint)),
26  m_telephonyDesignator(telephonyDesignator.trimmed()), m_typeHint(hint)
27  {}
28 
29  CCallsign::CCallsign(const char *callsign, CCallsign::TypeHint hint)
30  : m_callsignAsSet(callsign), m_callsign(CCallsign::unifyCallsign(callsign, hint)), m_typeHint(hint)
31  {}
32 
34  {
36  qRegisterMetaType<CCallsign::TypeHint>();
37  }
38 
39  QString CCallsign::convertToQString(bool i18n) const
40  {
41  Q_UNUSED(i18n)
42  return m_callsign;
43  }
44 
45  void CCallsign::clear() { *this = CCallsign(); }
46 
47  int CCallsign::suffixToSortOrder(const QString &suffix)
48  {
49  if (QStringView(u"FSS") == suffix) { return 1; }
50  if (QStringView(u"CTR") == suffix) { return 2; }
51  if (QStringView(u"APP") == suffix) { return 3; }
52  if (QStringView(u"DEP") == suffix) { return 4; }
53  if (QStringView(u"TWR") == suffix) { return 5; }
54  if (QStringView(u"GND") == suffix) { return 6; }
55  if (QStringView(u"DEL") == suffix) { return 7; }
56  if (QStringView(u"ATIS") == suffix) { return 8; }
57  if (QStringView(u"SUP") == suffix) { return 9; }
58  if (QStringView(u"OBS") == suffix) { return 10; }
59  if (QStringView(u"INS") == suffix) { return 11; } // instructor/mentor
60  if (QStringView(u"ADM") == suffix) { return 12; } // admin
61  if (QStringView(u"VATGOV") == suffix) { return 13; } // VATSIM governors
62  if (QStringView(u"VATSIM") == suffix) { return 14; } // VATSIM founder
63  if (QStringView(u"EXAM") == suffix) { return 15; }
64  return std::numeric_limits<int>::max();
65  }
66 
67  QString CCallsign::unifyCallsign(const QString &callsign, TypeHint hint)
68  {
69  const QString ucCallsign = callsign.trimmed().toUpper();
70 
71  // Ref T664, allow ATC with hyphen, such as Ml-SNO_CTR
72  switch (hint)
73  {
74  // ATC allows "-", aircraft not
75  case Atc: return removeChars(ucCallsign, [](QChar c) { return !c.isLetterOrNumber() && c != '_' && c != '-'; });
76  case Aircraft: return removeChars(ucCallsign, [](QChar c) { return !c.isLetterOrNumber() && c != '_'; });
77  default: break;
78  }
79 
80  // no hint
81  if (CCallsign::looksLikeAtcCallsign(ucCallsign))
82  {
83  return removeChars(ucCallsign, [](QChar c) { return !c.isLetterOrNumber() && c != '_' && c != '-'; });
84  }
85 
86  // strict check
87  return removeChars(ucCallsign, [](QChar c) { return !c.isLetterOrNumber() && c != '_'; });
88  }
89 
90  const CIcon &CCallsign::convertToIcon(const CCallsign &callsign)
91  {
92  if (callsign.m_callsign.startsWith(QStringView(u"VATGOV")))
93  {
94  return CIcon::iconByIndex(CIcons::NetworkRolePilot);
95  }
96  const bool pilot = callsign.getTypeHint() == CCallsign::Aircraft || !callsign.hasSuffix();
97  return pilot ? CIcon::iconByIndex(CIcons::NetworkRolePilot) : CCallsign::atcSuffixToIcon(callsign.getSuffix());
98  }
99 
100  CStatusMessage CCallsign::logMessage(const CCallsign &callsign, const QString &message,
101  const QStringList &extraCategories, CStatusMessage::StatusSeverity s)
102  {
103  static const CLogCategoryList cats({ CLogCategories::aviation() });
104  const CStatusMessage m(cats.with(CLogCategoryList::fromQStringList(extraCategories)), s,
105  callsign.isEmpty() ? message.trimmed() :
106  callsign.toQString() + ": " + message.trimmed());
107  return m;
108  }
109 
110  void CCallsign::addLogDetailsToList(CStatusMessageList *log, const CCallsign &callsign, const QString &message,
111  const QStringList &extraCategories, CStatusMessage::StatusSeverity s)
112  {
113  if (!log) { return; }
114  if (message.isEmpty()) { return; }
115  log->push_back(logMessage(callsign, message, extraCategories, s));
116  }
117 
118  const CIcon &CCallsign::atcSuffixToIcon(const QString &suffix)
119  {
120  if (suffix.length() < 3) { return CIcon::iconByIndex(CIcons::NetworkRoleUnknown); }
121  const QString sfx = suffix.toUpper();
122  if (QStringView(u"APP") == sfx) { return CIcon::iconByIndex(CIcons::NetworkRoleApproach); }
123  if (QStringView(u"DEP") == sfx) { return CIcon::iconByIndex(CIcons::NetworkRoleDeparture); }
124  if (QStringView(u"GND") == sfx) { return CIcon::iconByIndex(CIcons::NetworkRoleGround); }
125  if (QStringView(u"TWR") == sfx) { return CIcon::iconByIndex(CIcons::NetworkRoleTower); }
126  if (QStringView(u"DEL") == sfx) { return CIcon::iconByIndex(CIcons::NetworkRoleDelivery); }
127  if (QStringView(u"CTR") == sfx) { return CIcon::iconByIndex(CIcons::NetworkRoleCenter); }
128  if (QStringView(u"SUP") == sfx) { return CIcon::iconByIndex(CIcons::NetworkRoleSup); }
129  if (QStringView(u"OBS") == sfx) { return CIcon::iconByIndex(CIcons::NetworkRoleObs); }
130  if (QStringView(u"INS") == sfx) { return CIcon::iconByIndex(CIcons::NetworkRoleMnt); }
131  if (QStringView(u"FSS") == sfx) { return CIcon::iconByIndex(CIcons::NetworkRoleFss); }
132  if (QStringView(u"ATIS") == sfx) { return CIcon::iconByIndex(CIcons::AviationAtis); }
133  if (QStringView(u"EXAM") == sfx) { return CIcon::iconByIndex(CIcons::NetworkRoleMnt); }
134  if (QStringView(u"VATSIM") == sfx) { return CIcon::iconByIndex(CIcons::NetworkRoleSup); }
135  if (QStringView(u"VATGOV") == sfx) { return CIcon::iconByIndex(CIcons::NetworkRoleSup); }
136  return CIcon::iconByIndex(CIcons::NetworkRoleUnknown);
137  }
138 
140  {
141  if (this->getTypeHint() == Atc) { return true; }
142  if (!this->hasSuffix()) { return false; }
143  return atcCallsignSuffixes().contains(this->getSuffix(), Qt::CaseInsensitive);
144  }
145 
147  {
148  if (this->getTypeHint() == Aircraft) { return false; }
149  return m_callsign.endsWith("SUP");
150  }
151 
153  {
154  return m_callsignAsSet == "*" || m_callsign == "*" || m_callsignAsSet == "BROADCAST";
155  }
156 
158  {
159  m_callsignAsSet = "BROADCAST";
160  m_callsign = "BROADCAST";
161  }
162 
164  {
165  m_callsignAsSet = "SUP";
166  m_callsign = "SUP";
167  }
168 
169  bool CCallsign::isMaybeCopilotCallsign(const CCallsign &pilotCallsign) const
170  {
171  return m_callsign.startsWith(pilotCallsign.asString()) &&
172  m_callsign.size() == pilotCallsign.asString().size() + 1 &&
173  m_callsign.at(m_callsign.size() - 1) >= 'A' && m_callsign.at(m_callsign.size() - 1) <= 'Z';
174  }
175 
177  {
178  // mainly used to fix the cross coupled callsigns such as *EDDF_TWR
179  if (m_callsignAsSet.startsWith('*')) { return this->getStringAsSet(); }
180  return this->asString();
181  }
182 
183  bool CCallsign::isSameAsSet() const { return m_callsign == m_callsignAsSet; }
184 
185  QString CCallsign::getIcaoCode() const
186  {
187  if (this->isAtcCallsign())
188  {
189  if (m_callsign.length() >= 4) { return m_callsign.left(4).toUpper(); }
190  }
191  return {};
192  }
193 
195  {
196  if (this->getTypeHint() == Aircraft) { return false; }
197  if (!this->hasSuffix()) { return false; }
198  return atcAlikeCallsignSuffixes().contains(this->getSuffix(), Qt::CaseInsensitive);
199  }
200 
201  bool CCallsign::isObserverCallsign() const { return m_callsignAsSet.endsWith("_OBS", Qt::CaseInsensitive); }
202 
204  {
205  if (this->isEmpty()) { return {}; }
206  QString obs = this->getStringAsSet();
207  if (obs.endsWith("_OBS", Qt::CaseInsensitive)) { return obs; } // already OBS
208  if (obs.contains('_')) { obs = obs.left(obs.lastIndexOf('_')); }
209  return obs.append("_OBS").toUpper();
210  }
211 
212  QString CCallsign::getSuffix() const
213  {
214  QString s;
215  if (this->hasSuffix()) { s = this->getStringAsSet().section('_', -1).toUpper(); }
216  return s;
217  }
218 
220  {
221  QString flightNumber;
222  return this->getAirlinePrefix(flightNumber);
223  }
224 
225  QString CCallsign::getAirlinePrefix(QString &flightNumber) const
226  {
227  QString identification;
228  return this->getAirlinePrefix(flightNumber, identification);
229  }
230 
231  QString CCallsign::getAirlinePrefix(QString &flightNumber, QString &flightIdentification) const
232  {
233  // DLH1WP only 11 number
234  // UPSE123 4 characters, then number
235 
236  flightNumber.clear();
237  if (m_callsign.length() < 3) { return {}; }
238  if (this->isAtcCallsign()) { return {}; }
239 
240  thread_local const QRegularExpression regExp("(^[A-Z]{3,})(\\d+)");
241  const QRegularExpressionMatch match = regExp.match(m_callsign);
242  if (!match.hasMatch()) { return {}; }
243  // 0 is whole capture
244  const QString airline = match.captured(1);
245  flightNumber = match.captured(2); // null string if not exits
246 
247  // hard facts
248  if (airline.length() == 3) // we allow 3 letters
249  {
250  flightIdentification = m_callsign.length() > 3 ? m_callsign.mid(3) : QString();
251  return airline;
252  }
253  if (airline.length() == 4 && airline.startsWith('V')) // we allow virtual 4 letter codes, e.g. VDLD
254  {
255  flightIdentification = m_callsign.length() > 4 ? m_callsign.mid(4) : QString();
256  return airline;
257  }
258 
259  // some people use callsigns like UPSE123
260  if (flightNumber.length() >= 1 && airline.length() == 4)
261  {
262  flightIdentification = m_callsign.mid(3);
263  return airline.left(3);
264  }
265 
266  return {}; // invalid
267  }
268 
270  {
271  if (this->isAtcCallsign()) { return {}; }
272  QString flightNumber;
273  QString identification;
274  const QString airline = this->getAirlinePrefix(flightNumber, identification);
275  return airline.isEmpty() ? QString() : identification;
276  }
277 
279  {
280  if (this->isAtcCallsign()) { return {}; }
281  QString flightNumber;
282  const QString airline = this->getAirlinePrefix(flightNumber);
283  return airline.isEmpty() ? QString() : flightNumber;
284  }
285 
287  {
288  if (this->isAtcCallsign()) { return -1; }
289  bool ok;
290  const int fn = this->getFlightNumber().toInt(&ok);
291  return ok ? fn : -1;
292  }
293 
294  bool CCallsign::hasSuffix() const { return this->getStringAsSet().contains('_'); }
295 
297  {
298  const QString s = this->getSuffix();
299  return !s.isEmpty() && atcCallsignSuffixes().contains(s);
300  }
301 
303 
304  bool CCallsign::equalsString(const QString &callsignString) const
305  {
306  CCallsign other(callsignString);
307  return other == (*this);
308  }
309 
311  {
312  if (index.isMyself()) { return QVariant::fromValue(*this); }
313  const ColumnIndex i = index.frontCasted<ColumnIndex>();
314  switch (i)
315  {
316  case IndexCallsignString: return QVariant(this->asString());
317  case IndexCallsignStringAsSet: return QVariant(this->getStringAsSet());
318  case IndexTelephonyDesignator: return QVariant(this->getTelephonyDesignator());
319  case IndexSuffix: return QVariant(this->getSuffix());
320  default: return CValueObject::propertyByIndex(index);
321  }
322  }
323 
324  void CCallsign::setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
325  {
326  if (index.isMyself())
327  {
328  (*this) = variant.value<CCallsign>();
329  return;
330  }
331  const ColumnIndex i = index.frontCasted<ColumnIndex>();
332  switch (i)
333  {
334  case IndexCallsignString: m_callsign = unifyCallsign(variant.toString()); break;
335  case IndexCallsignStringAsSet: m_callsignAsSet = variant.toString(); break;
336  case IndexTelephonyDesignator: m_telephonyDesignator = variant.toString(); break;
337  default: CValueObject::setPropertyByIndex(index, variant); break;
338  }
339  }
340 
341  int CCallsign::comparePropertyByIndex(CPropertyIndexRef index, const CCallsign &compareValue) const
342  {
343  if (index.isMyself()) { return m_callsign.compare(compareValue.m_callsign, Qt::CaseInsensitive); }
344  const ColumnIndex i = index.frontCasted<ColumnIndex>();
345  switch (i)
346  {
347  case IndexCallsignString: return m_callsign.compare(compareValue.m_callsign, Qt::CaseInsensitive);
348  case IndexCallsignStringAsSet:
349  return m_callsignAsSet.compare(compareValue.m_callsignAsSet, Qt::CaseInsensitive);
350  case IndexTelephonyDesignator:
351  return m_telephonyDesignator.compare(compareValue.m_telephonyDesignator, Qt::CaseInsensitive);
352  case IndexSuffix: return this->getSuffix().compare(compareValue.getSuffix(), Qt::CaseInsensitive);
353  default: return CValueObject::comparePropertyByIndex(index, compareValue);
354  }
355  Q_ASSERT_X(false, Q_FUNC_INFO, "Compare failed");
356  return 0;
357  }
358 
359  bool CCallsign::isValid() const
360  {
361  switch (m_typeHint)
362  {
363  case Atc: return isValidAtcCallsign(*this);
364  case Aircraft: return isValidAircraftCallsign(*this);
365  default: return !this->isEmpty();
366  }
367  }
368 
369  bool CCallsign::isValidAircraftCallsign(const QString &callsign)
370  {
371  if (callsign.length() < 2 || callsign.length() > 10) { return false; }
372  return !containsChar(callsign, [](QChar c) { return !c.isUpper() && !c.isDigit(); });
373  }
374 
376  {
377  return isValidAircraftCallsign(callsign.asString());
378  }
379 
380  bool CCallsign::isValidAtcCallsign(const QString &callsign)
381  {
382  // Ref T664, allow ATC with hyphen, such as Ml-SNO_CTR
383  if (callsign.length() < 2 || callsign.length() > 10) { return false; }
384  return !containsChar(callsign, [](QChar c) { return c != '-' && c != '_' && !c.isUpper() && !c.isDigit(); });
385  }
386 
387  bool CCallsign::isValidAtcCallsign(const CCallsign &callsign) { return isValidAtcCallsign(callsign.asString()); }
388 
389  const QStringList &CCallsign::atcCallsignSuffixes()
390  {
391  static const QStringList a({ "APP", "GND", "DEP", "TWR", "DEL", "CTR" });
392  return a;
393  }
394 
396  {
397  static const QStringList a({ "ATIS", "APP", "GND", "OBS", "DEP", "TWR", "DEL", "CTR", "SUP", "FSS", "INS" });
398  return a;
399  }
400 
401  bool CCallsign::looksLikeAtcCallsign(const QString &callsign)
402  {
403  if (!callsign.contains("_")) { return false; }
404  const QStringView uc = callsign.toUpper();
405 
406  for (const QString &r : CCallsign::atcAlikeCallsignSuffixes())
407  {
408  if (uc.endsWith(r)) { return true; }
409  }
410  return false;
411  }
412 } // namespace swift::misc::aviation
Value object for icons. An icon is stored in the global icon repository and identified by its index....
Definition: icon.h:39
static const CIcon & iconByIndex(CIcons::IconIndex index)
Icon for given index.
Definition: icon.cpp:54
@ NetworkRoleMnt
Mentor.
Definition: icons.h:78
static const QString & aviation()
Aviation specific.
A sequence of log categories.
static CLogCategoryList fromQStringList(const QStringList &stringList)
Convert a string list, such as that returned by toQStringList(), into a CLogCategoryList.
Non-owning reference to a CPropertyIndex with a subset of its features.
CastType frontCasted() const
First element casted to given type, usually the PropertIndex enum.
bool isMyself() const
Myself index, used with nesting.
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition: sequence.h:305
Streamable status message, e.g.
Status messages, e.g. from Core -> GUI.
Value object encapsulating information of a callsign.
Definition: callsign.h:30
int getSuffixSortOrder() const
Sort order by suffix.
Definition: callsign.cpp:302
void clear()
Clear this callsign.
Definition: callsign.cpp:45
int comparePropertyByIndex(CPropertyIndexRef index, const CCallsign &compareValue) const
Compare for index.
Definition: callsign.cpp:341
const QString & asString() const
Get callsign (normalized)
Definition: callsign.h:96
bool isMaybeCopilotCallsign(const CCallsign &pilotCallsign) const
Returns true if this is a co-pilot callsign of pilot. The logic is that the callsign is the same as t...
Definition: callsign.cpp:169
static int suffixToSortOrder(const QString &suffix)
Index for ATC suffix, if unknown int max value.
Definition: callsign.cpp:47
QString getSuffix() const
Get the callsign suffix ("TWR", "ATIS" ...) if any ("_" is removed)
Definition: callsign.cpp:212
TypeHint
Representing what.
Definition: callsign.h:44
bool hasSuffix() const
Suffix such as "_TWR"?
Definition: callsign.cpp:294
void markAsBroadcastCallsign()
Set a human readable name as "broadcast" callsign.
Definition: callsign.cpp:157
QString convertToQString(bool i18n=false) const
Cast as QString.
Definition: callsign.cpp:39
int getFlightNumberInt() const
Flight number as integer.
Definition: callsign.cpp:286
QString getFlightIndentification() const
Flight number (e.g. DLH1234 -> 1234) if applicable.
Definition: callsign.cpp:269
static const QStringList & atcCallsignSuffixes()
List of real ATC suffixes (e.g. TWR);.
Definition: callsign.cpp:389
static bool looksLikeAtcCallsign(const QString &callsign)
Does this look like an ATC callsign.
Definition: callsign.cpp:401
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
Definition: callsign.cpp:310
void markAsWallopCallsign()
Set a human readable name as "wallop-channel" callsign.
Definition: callsign.cpp:163
QString getFlightNumber() const
Flight number (e.g. DLH1234 -> 1234) if applicable.
Definition: callsign.cpp:278
bool isSupervisorCallsign() const
Supervisor?
Definition: callsign.cpp:146
static void addLogDetailsToList(CStatusMessageList *log, const CCallsign &callsign, const QString &message, const QStringList &extraCategories={}, CStatusMessage::StatusSeverity s=CStatusMessage::SeverityInfo)
Specialized log for matching / reverse lookup.
Definition: callsign.cpp:110
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
Definition: callsign.cpp:324
bool isObserverCallsign() const
Observer callsign?
Definition: callsign.cpp:201
bool isAtcAlikeCallsign() const
ATC alike callsign.
Definition: callsign.cpp:194
static CStatusMessage logMessage(const CCallsign &callsign, const QString &message, const QStringList &extraCategories={}, CStatusMessage::StatusSeverity s=CStatusMessage::SeverityInfo)
Specialized log message for matching / reverse lookup.
Definition: callsign.cpp:100
static QString unifyCallsign(const QString &callsign, TypeHint hint=NoHint)
Unify the callsign by removing illegal characters.
Definition: callsign.cpp:67
bool isEmpty() const
Is empty?
Definition: callsign.h:63
static bool isValidAircraftCallsign(const QString &callsign)
Valid callsign?
Definition: callsign.cpp:369
const QString & getTelephonyDesignator() const
Get callsign telephony designator (how callsign is pronounced)
Definition: callsign.h:108
TypeHint getTypeHint() const
Type hint.
Definition: callsign.h:111
static const CIcon & convertToIcon(const CCallsign &callsign)
Representing icon.
Definition: callsign.cpp:90
QString getAsObserverCallsignString() const
Makes this callsign looking like an observer callsign (DAMBZ -> DAMBZ_OBS)
Definition: callsign.cpp:203
bool hasAtcSuffix() const
Has an ATC suffix?
Definition: callsign.cpp:296
CCallsign()
Default constructor.
Definition: callsign.h:51
QString getIcaoCode() const
Get ICAO code, if this makes sense (EDDF_TWR -> EDDF)
Definition: callsign.cpp:185
QString getAirlinePrefix() const
Airline suffix (e.g. DLH1234 -> DLH) if applicable.
Definition: callsign.cpp:219
static const CIcon & atcSuffixToIcon(const QString &suffix)
Suffix to icon.
Definition: callsign.cpp:118
bool isAtcCallsign() const
ATC callsign.
Definition: callsign.cpp:139
static void registerMetadata()
Register metadata.
Definition: callsign.cpp:33
QString getFsdCallsignString() const
The callsign string used with FSD.
Definition: callsign.cpp:176
bool isSameAsSet() const
Same as set callsign?
Definition: callsign.cpp:183
bool equalsString(const QString &callsignString) const
Equals callsign string?
Definition: callsign.cpp:304
bool isBroadcastCallsign() const
Pseudo callsing for broadcast messages.
Definition: callsign.cpp:152
const QString & getStringAsSet() const
Get callsign.
Definition: callsign.h:99
static bool isValidAtcCallsign(const QString &callsign)
Valid callsign?
Definition: callsign.cpp:380
bool isValid() const
Valid callsign?
Definition: callsign.cpp:359
static const QStringList & atcAlikeCallsignSuffixes()
List of real ("TWR") and treated like ATC suffixes (e.g. OBS);.
Definition: callsign.cpp:395
int comparePropertyByIndex(CPropertyIndexRef index, const Derived &compareValue) const
Compare for index.
Definition: mixinindex.h:187
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
Definition: mixinindex.h:160
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
Definition: mixinindex.h:167
static void registerMetadata()
Register metadata.
Definition: mixinmetatype.h:56
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:76
QString removeChars(const QString &s, F predicate)
Return a string with characters removed that match the given predicate.
Definition: stringutils.h:35
StatusSeverity
Status severities.
Definition: statusmessage.h:35
bool containsChar(const QString &s, F predicate)
True if any character in the string matches the given predicate.
Definition: stringutils.h:65
#define SWIFT_DEFINE_VALUEOBJECT_MIXINS(Namespace, Class)
Explicit template definition of mixins for a CValueObject subclass.
Definition: valueobject.h:67