swift
textmessage.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 <QStringBuilder>
7 #include <Qt>
8 #include <QtGlobal>
9 
10 #include "misc/aviation/callsign.h"
12 #include "misc/aviation/selcal.h"
13 #include "misc/pq/constants.h"
15 #include "misc/stringutils.h"
16 
17 using namespace swift::misc::aviation;
18 using namespace swift::misc::physical_quantities;
19 
20 SWIFT_DEFINE_VALUEOBJECT_MIXINS(swift::misc::network, CTextMessage)
21 
22 namespace swift::misc::network
23 {
24  CTextMessage::CTextMessage(const QString &message, const CFrequency &frequency, const CCallsign &senderCallsign)
25  : m_senderCallsign(senderCallsign), m_frequency(frequency)
26  {
27  this->setMessage(message); // single place to modify message
29  }
30 
31  CTextMessage::CTextMessage(const QString &message, const CCallsign &senderCallsign,
32  const CCallsign &recipientCallsign)
33  : m_senderCallsign(senderCallsign), m_recipientCallsign(recipientCallsign), m_frequency(0, nullptr)
34  {
35  this->setMessage(message); // single place to modify message
36  }
37 
39  {
40  if (this->isPrivateMessage())
41  {
42  return m_message % u' ' % m_senderCallsign.toQString(i18n) % u' ' % m_recipientCallsign.toQString(i18n);
43  }
44  return m_message % u' ' % m_frequency.toQString(i18n);
45  }
46 
48  {
49  static const QString s("swift relayed: ");
50  return s;
51  }
52 
54  {
55  return !m_senderCallsign.isEmpty() && !m_recipientCallsign.isEmpty();
56  }
57 
59  {
60  // ignore broadcast messages
61  if (this->isBroadcastMessage()) { return false; }
62 
63  // normal SUP message
64  return m_senderCallsign.isSupervisorCallsign();
65  }
66 
68  {
69  m_wasSent = true;
70  if (!this->hasValidTimestamp()) { this->setCurrentUtcTime(); }
71  }
72 
74  {
75  return m_relayedMessage || this->getMessage().startsWith(CTextMessage::swiftRelayMessage());
76  }
77 
79 
80  void CTextMessage::makeRelayedMessage(const CCallsign &partnerCallsign)
81  {
82  if (this->getMessage().startsWith(CTextMessage::swiftRelayMessage())) { return; }
83  const QString sender = this->getSenderCallsign().asString();
84  const QString recipient = this->getRecipientCallsign().asString();
85  this->markAsRelayedMessage();
86  this->setRecipientCallsign(partnerCallsign);
87  m_recipientCallsign.setTypeHint(CCallsign::Aircraft);
88  const QString newMessage =
89  CTextMessage::swiftRelayMessage() % sender % u" " % recipient % u";" % this->getMessage();
90  m_message = newMessage;
91  }
92 
94  {
95  if (!this->isRelayedMessage()) { return false; }
96  const int index = m_message.indexOf(';');
97  if (index < CTextMessage::swiftRelayMessage().length()) { return false; }
98  if (m_message.length() <= index + 1) { return false; } // no next line
99  const QString senderRecipient = m_message.left(index).remove(CTextMessage::swiftRelayMessage()).trimmed();
100  const QStringList sr = senderRecipient.split(' ');
101  if (sr.size() != 2) { return false; }
102  const QString originalSender = sr.first();
103  const QString originalRecipient = sr.last();
104  this->setSenderCallsign(CCallsign(originalSender)); // sender can be aircraft or ATC
105  this->setRecipientCallsign(CCallsign(originalRecipient)); // recipient can be aircraft or ATC
106  m_message = m_message.mid(index + 1);
107  return true;
108  }
109 
110  bool CTextMessage::canBeAppended(const CTextMessage &textMessage) const
111  {
112  if (textMessage.isEmpty()) { return false; }
113  if (this->getSenderCallsign() != textMessage.getSenderCallsign()) { return false; }
114  if (this->isRadioMessage() && textMessage.isRadioMessage())
115  {
116  return this->getFrequency() == textMessage.getFrequency();
117  }
118  else if (this->isPrivateMessage() && textMessage.isPrivateMessage())
119  {
120  return this->getRecipientCallsign() == textMessage.getRecipientCallsign();
121  }
122  return false;
123  }
124 
126  {
127  if (textMessage.isEmpty()) { return false; }
128  if (!this->canBeAppended(textMessage)) { return false; }
129  m_message += u' ' % textMessage.getMessage();
130  return true;
131  }
132 
134  {
135  if (!m_recipientCallsign.isEmpty()) { return m_recipientCallsign.asString(); }
136  if (m_frequency.isNull()) { return {}; }
137  return m_frequency.valueRoundedWithUnit(CFrequencyUnit::MHz(), 3);
138  }
139 
140  bool CTextMessage::isSendToFrequency(const CFrequency &frequency) const
141  {
142  if (!this->isRadioMessage()) { return false; }
143  return m_frequency == frequency;
144  }
145 
147  {
148  return this->isSendToFrequency(CPhysicalQuantitiesConstants::FrequencyUnicom());
149  }
150 
152  {
153  if (!m_recipientCallsign.isEmpty()) { return true; }
154  return CComSystem::isValidCivilAviationFrequency(m_frequency);
155  }
156 
157  bool CTextMessage::mentionsCallsign(const CCallsign &callsign) const
158  {
159  if (callsign.isEmpty()) { return false; }
160  if (m_message.length() < callsign.asString().length()) { return false; }
161  return m_message.contains(callsign.asString(), Qt::CaseInsensitive);
162  }
163 
165  {
166  if (m_message.isEmpty()) { return {}; }
167  return asciiOnlyString(simplifyAccents(m_message));
168  }
169 
171  {
172  if (m_message.isEmpty()) { return {}; }
173  return m_message.toHtmlEscaped();
174  }
175 
176  void CTextMessage::setMessage(const QString &message) { m_message = message.simplified().trimmed(); }
177 
178  bool CTextMessage::isRadioMessage() const { return (CComSystem::isValidCivilAviationFrequency(m_frequency)); }
179 
181  {
182  if (!this->isPrivateMessage()) { return false; }
183  const CCallsign cs = this->getSenderCallsign();
184  return (cs.asString().startsWith("SERVER", Qt::CaseInsensitive));
185  }
186 
188  {
189  const CCallsign cs = this->getRecipientCallsign();
190  return cs.isBroadcastCallsign();
191  }
192 
194  {
195  const CCallsign cs = this->getRecipientCallsign();
196  return cs.getStringAsSet() == "*S";
197  }
198 
199  QString CTextMessage::asString(bool withSender, bool withRecipient, const QString &separator) const
200  {
202  if (withSender)
203  {
204  if (!m_senderCallsign.isEmpty())
205  {
206  if (!s.isEmpty()) { s += separator % m_senderCallsign.getStringAsSet(); }
207  }
208  }
209 
210  if (withRecipient)
211  {
212  if (!m_recipientCallsign.isEmpty())
213  {
214  if (!s.isEmpty()) { s += separator % m_recipientCallsign.getStringAsSet(); }
215  }
216  else
217  {
218  if (CComSystem::isValidCivilAviationFrequency(m_frequency))
219  {
220  if (!s.isEmpty()) { s += separator % m_frequency.valueRoundedWithUnit(3, true); }
221  }
222  }
223  } // to
224 
225  if (m_message.isEmpty()) { return s; }
226  if (!s.isEmpty()) { s += separator % m_message; }
227  return s;
228  }
229 
230  CStatusMessage CTextMessage::asStatusMessage(bool withSender, bool withRecipient, const QString &separator) const
231  {
232  const QString m = this->asString(withSender, withRecipient, separator);
233  return { this, CStatusMessage::SeverityInfo, m };
234  }
235 
237  {
238  return this->asString(true, true, separator);
239  }
240 
241  void CTextMessage::toggleSenderRecipient() { std::swap(m_senderCallsign, m_recipientCallsign); }
242 
244  {
245  // some first level checks, before really parsing the message
246  if (this->isEmpty()) { return false; }
247  if (this->isPrivateMessage()) { return false; }
248  if (m_message.length() > 15 || m_message.length() < 10)
249  {
250  return false;
251  } // SELCAL AB-CD -> 12, I allow some more characters as I do not know wheter in real life it exactly matches
252  return this->getSelcalCode().length() == 4;
253  }
254 
255  bool CTextMessage::isSelcalMessageFor(const QString &selcal) const
256  {
257  if (!CSelcal::isValidCode(selcal)) return false;
258  return selcal.toUpper() == this->getSelcalCode();
259  }
260 
262  {
263  // http://forums.vatsim.net/viewtopic.php?f=8&t=63467#p458062
264  // When the ATC client sends a selcal, it goes out as a text message
265  // on their primary frequency, formatted like so:
266  // SELCAL AB-CD
267 
268  if (this->isEmpty()) return {};
269  if (this->isPrivateMessage()) return {};
270  if (!m_message.startsWith(QLatin1String("SELCAL"), Qt::CaseInsensitive)) return {};
271  if (m_message.length() > 15 || m_message.length() < 10)
272  return {}; // SELCAL AB-CD -> 12, I allow some more characters as I do not know wheter in real life it
273  // exactly matches
274  QString candidate = removeChars(m_message, [](QChar c) { return !c.isLetter(); }); // SELCALABCD
275  if (candidate.length() != 10) return {};
276  return std::move(candidate).right(4).toUpper();
277  }
278 
279  CIcons::IconIndex CTextMessage::toIcon() const { return m_senderCallsign.toIcon(); }
280 
282 
284  {
285  if (index.isMyself()) { return QVariant::fromValue(*this); }
287 
288  const auto i = index.frontCasted<ColumnIndex>();
289  switch (i)
290  {
291  case IndexSenderCallsign: return m_senderCallsign.propertyByIndex(index.copyFrontRemoved());
292  case IndexRecipientCallsign: return m_recipientCallsign.propertyByIndex(index.copyFrontRemoved());
293  case IndexRecipientCallsignOrFrequency: return QVariant::fromValue(this->getRecipientCallsignOrFrequency());
294  case IndexMessage: return QVariant::fromValue(m_message);
295  default: return CValueObject::propertyByIndex(index);
296  }
297  }
298 
300  {
301  if (index.isMyself())
302  {
303  (*this) = variant.value<CTextMessage>();
304  return;
305  }
307  {
308  ITimestampBased::setPropertyByIndex(index, variant);
309  return;
310  }
311 
312  const auto i = index.frontCasted<ColumnIndex>();
313  switch (i)
314  {
315  case IndexSenderCallsign: m_senderCallsign.setPropertyByIndex(index.copyFrontRemoved(), variant); break;
316  case IndexRecipientCallsign: m_recipientCallsign.setPropertyByIndex(index.copyFrontRemoved(), variant); break;
317  case IndexMessage: m_message = variant.value<QString>(); break;
318  default: CValueObject::setPropertyByIndex(index, variant); break;
319  }
320  }
321 
323  {
325  {
326  return ITimestampBased::comparePropertyByIndex(index, compareValue);
327  }
328  const auto i = index.frontCasted<ColumnIndex>();
329  switch (i)
330  {
331  case IndexSenderCallsign:
332  return m_senderCallsign.comparePropertyByIndex(index.copyFrontRemoved(), compareValue.getSenderCallsign());
333  case IndexRecipientCallsign:
334  return m_recipientCallsign.comparePropertyByIndex(index.copyFrontRemoved(),
335  compareValue.getRecipientCallsign());
336  case IndexRecipientCallsignOrFrequency:
337  if (this->isRadioMessage()) { return this->getFrequency().compare(compareValue.getFrequency()); }
338  return m_recipientCallsign.comparePropertyByIndex(index.copyFrontRemoved(),
339  compareValue.getRecipientCallsign());
340  default: return CValueObject::comparePropertyByIndex(index, *this);
341  }
342  Q_ASSERT_X(false, Q_FUNC_INFO, "No comparison");
343  return 0;
344  }
345 } // namespace swift::misc::network
Value object for icons. An icon is stored in the global icon repository and identified by its index....
Definition: icon.h:39
QPixmap toPixmap() const
Corresponding pixmap.
Definition: icon.cpp:34
IconIndex
Index for each icon, allows to send them via DBus, efficiently store them, etc.
Definition: icons.h:32
Non-owning reference to a CPropertyIndex with a subset of its features.
Q_REQUIRED_RESULT CPropertyIndexRef copyFrontRemoved() const
Copy with first element removed.
CastType frontCasted() const
First element casted to given type, usually the PropertIndex enum.
bool isMyself() const
Myself index, used with nesting.
Streamable status message, e.g.
constexpr static auto SeverityInfo
Status severities.
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
int comparePropertyByIndex(CPropertyIndexRef index, const ITimestampBased &compareValue) const
Compare for index.
static bool canHandleIndex(CPropertyIndexRef index)
Can given index be handled.
void setCurrentUtcTime()
Set the current time as timestamp.
QString getFormattedUtcTimestampHms() const
As hh:mm:ss.
bool hasValidTimestamp() const
Valid timestamp?
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
Value object encapsulating information of a callsign.
Definition: callsign.h:30
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
CIcons::IconIndex toIcon() const
As icon, not implemented by all classes.
Definition: callsign.h:157
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
Definition: callsign.cpp:310
bool isSupervisorCallsign() const
Supervisor?
Definition: callsign.cpp:146
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
Definition: callsign.cpp:324
bool isEmpty() const
Is empty?
Definition: callsign.h:63
void setTypeHint(TypeHint hint)
Type hint.
Definition: callsign.h:114
bool isBroadcastCallsign() const
Pseudo callsing for broadcast messages.
Definition: callsign.cpp:152
const QString & getStringAsSet() const
Get callsign.
Definition: callsign.h:99
ColumnIndex
Base class enums.
Definition: mixinindex.h:44
int comparePropertyByIndex(CPropertyIndexRef index, const Derived &compareValue) const
Compare for index.
Definition: mixinindex.h:185
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
Definition: mixinindex.h:158
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
Definition: mixinindex.h:165
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:74
Value object encapsulating information of a text message.
Definition: textmessage.h:31
QString getHtmlEncodedMessage() const
Get HTML escaped message.
int comparePropertyByIndex(CPropertyIndexRef index, const CTextMessage &compareValue) const
Compare for index.
bool isRelayedMessage() const
Is relayed message.
Definition: textmessage.cpp:73
bool relayedMessageToPrivateMessage()
Turn relayed message into private message.
Definition: textmessage.cpp:93
void setMessage(const QString &message)
Set message.
bool isWallopMessage() const
Is this a message send via .wallop.
bool isPrivateMessage() const
Is private message?
Definition: textmessage.cpp:53
bool mentionsCallsign(const aviation::CCallsign &callsign) const
Is the callsign given mentioned in that message.
void setSenderCallsign(const aviation::CCallsign &callsign)
Set callsign (from)
Definition: textmessage.h:57
bool isSelcalMessageFor(const QString &selcal) const
Is this a text message encapsulating a SELCAL for given code?
CIcons::IconIndex toIcon() const
As icon, not implement by all classes.
QString getRecipientCallsignOrFrequency() const
Get recipient or frequency.
QString getSelcalCode() const
Get SELCAL code (if applicable, e.g. ABCD), otherwise "".
bool isBroadcastMessage() const
Is this a broadcast message.
QString asHtmlSummary(const QString &separator="<br>") const
Summary HTML code.
void markAsRelayedMessage()
Mark as relayed message.
Definition: textmessage.h:156
bool isEmpty() const
Empty message.
Definition: textmessage.h:90
const QString & getMessage() const
Get message.
Definition: textmessage.h:78
bool canBeAppended(const CTextMessage &textMessage) const
Can another message be appended.
bool isSendToUnicom() const
Send to UNICOM?
QString asString(bool withSender, bool withRecipient, const QString &separator=", ") const
Whole message as formatted string. Used to display message in a console window.
void setRecipientCallsign(const aviation::CCallsign &callsign)
Set callsign (recipient)
Definition: textmessage.h:63
bool isServerMessage() const
Initial message of server?
QVariant propertyByIndex(swift::misc::CPropertyIndexRef index) const
Property by index.
bool hasValidRecipient() const
Valid receviver?
QPixmap toPixmap() const
As pixmap, required for most GUI views.
bool isSendToFrequency(const physical_quantities::CFrequency &frequency) const
Send to particular frequency?
void makeRelayedMessage(const aviation::CCallsign &partnerCallsign)
Mark as relayed and keep original sender.
Definition: textmessage.cpp:80
CStatusMessage asStatusMessage(bool withSender, bool withRecipient, const QString &separator=", ") const
Whole message as swift::misc::CStatusMessage. Used to display message in logs, or any other situation...
const aviation::CCallsign & getSenderCallsign() const
Get callsign (from)
Definition: textmessage.h:54
void setPropertyByIndex(swift::misc::CPropertyIndexRef index, const QVariant &variant)
Set property by index.
const aviation::CCallsign & getRecipientCallsign() const
Get callsign (to)
Definition: textmessage.h:60
void toggleSenderRecipient()
Toggle sender receiver, can be used to ping my own message.
static const QString & swiftRelayMessage()
swift relay message marker
Definition: textmessage.cpp:47
void markAsBroadcastMessage()
Mark as broadcast message.
Definition: textmessage.cpp:78
bool isRadioMessage() const
Is radio message?
QString getAsciiOnlyMessage() const
Get ASCII only message.
const physical_quantities::CFrequency & getFrequency() const
Get frequency.
Definition: textmessage.h:96
bool isSelcalMessage() const
Is this a text message encapsulating a SELCAL.
CTextMessage()=default
Default constructor.
bool appendIfPossible(const CTextMessage &textMessage)
Append if possible.
bool isSupervisorMessage() const
Supervisor message?
Definition: textmessage.cpp:58
QString convertToQString(bool i18n=false) const
Cast as QString.
Definition: textmessage.cpp:38
static CFrequencyUnit MHz()
Megahertz.
Definition: units.h:388
PQ & switchUnit(const MU &newUnit)
Change unit, and convert value to maintain the same quantity.
int compare(const PQ &other) const
Compare with other PQ.
QString valueRoundedWithUnit(const MU &unit, int digits=-1, bool withGroupSeparator=false, bool i18n=false) const
Value to QString with the given unit, e.g. "5.00m".
QString removeChars(const QString &s, F predicate)
Return a string with characters removed that match the given predicate.
Definition: stringutils.h:34
SWIFT_MISC_EXPORT QString simplifyAccents(const QString &candidate)
Remove accents / diacritic marks from a string.
QString asciiOnlyString(const QString &string)
String only with ASCII values.
Definition: stringutils.h:203
void swap(Optional< T > &a, Optional< T > &b) noexcept(std::is_nothrow_swappable_v< T >)
Efficient swap for two Optional objects.
Definition: optional.h:132
bool isLetter(char32_t ucs4)
T & first()
T & last()
qsizetype size() const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString left(qsizetype n) &&
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) &&
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString right(qsizetype n) &&
QString simplified() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString toHtmlEscaped() const const
QString toUpper() const const
QString trimmed() const const
CaseInsensitive
QVariant fromValue(T &&value)
T value() const &const
#define SWIFT_DEFINE_VALUEOBJECT_MIXINS(Namespace, Class)
Explicit template definition of mixins for a CValueObject subclass.
Definition: valueobject.h:67