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 
38  QString CTextMessage::convertToQString(bool i18n) const
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  if (this->getFrequency() != textMessage.getFrequency()) { return false; }
117  return true;
118  }
119  else if (this->isPrivateMessage() && textMessage.isPrivateMessage())
120  {
121  if (this->getRecipientCallsign() != textMessage.getRecipientCallsign()) { return false; }
122  return true;
123  }
124  return false;
125  }
126 
128  {
129  if (textMessage.isEmpty()) { return false; }
130  if (!this->canBeAppended(textMessage)) { return false; }
131  m_message += u' ' % textMessage.getMessage();
132  return true;
133  }
134 
136  {
137  if (!m_recipientCallsign.isEmpty()) { return m_recipientCallsign.asString(); }
138  if (m_frequency.isNull()) { return {}; }
139  return m_frequency.valueRoundedWithUnit(CFrequencyUnit::MHz(), 3);
140  }
141 
142  bool CTextMessage::isSendToFrequency(const CFrequency &frequency) const
143  {
144  if (!this->isRadioMessage()) { return false; }
145  return m_frequency == frequency;
146  }
147 
149  {
150  return this->isSendToFrequency(CPhysicalQuantitiesConstants::FrequencyUnicom());
151  }
152 
154  {
155  if (!m_recipientCallsign.isEmpty()) { return true; }
156  return CComSystem::isValidCivilAviationFrequency(m_frequency);
157  }
158 
159  bool CTextMessage::mentionsCallsign(const CCallsign &callsign) const
160  {
161  if (callsign.isEmpty()) { return false; }
162  if (m_message.length() < callsign.asString().length()) { return false; }
163  return m_message.contains(callsign.asString(), Qt::CaseInsensitive);
164  }
165 
167  {
168  if (m_message.isEmpty()) { return {}; }
169  return asciiOnlyString(simplifyAccents(m_message));
170  }
171 
173  {
174  if (m_message.isEmpty()) { return {}; }
175  return m_message.toHtmlEscaped();
176  }
177 
178  void CTextMessage::setMessage(const QString &message) { m_message = message.simplified().trimmed(); }
179 
180  bool CTextMessage::isRadioMessage() const { return (CComSystem::isValidCivilAviationFrequency(m_frequency)); }
181 
183  {
184  if (!this->isPrivateMessage()) { return false; }
185  const CCallsign cs = this->getSenderCallsign();
186  return (cs.asString().startsWith("SERVER", Qt::CaseInsensitive));
187  }
188 
190  {
191  const CCallsign cs = this->getRecipientCallsign();
192  return cs.isBroadcastCallsign();
193  }
194 
196  {
197  const CCallsign cs = this->getRecipientCallsign();
198  return cs.getStringAsSet() == "*S";
199  }
200 
201  QString CTextMessage::asString(bool withSender, bool withRecipient, const QString &separator) const
202  {
203  QString s(this->getFormattedUtcTimestampHms());
204  if (withSender)
205  {
206  if (!m_senderCallsign.isEmpty())
207  {
208  if (!s.isEmpty()) { s += separator % m_senderCallsign.getStringAsSet(); }
209  }
210  }
211 
212  if (withRecipient)
213  {
214  if (!m_recipientCallsign.isEmpty())
215  {
216  if (!s.isEmpty()) { s += separator % m_recipientCallsign.getStringAsSet(); }
217  }
218  else
219  {
220  if (CComSystem::isValidCivilAviationFrequency(m_frequency))
221  {
222  if (!s.isEmpty()) { s += separator % m_frequency.valueRoundedWithUnit(3, true); }
223  }
224  }
225  } // to
226 
227  if (m_message.isEmpty()) { return s; }
228  if (!s.isEmpty()) { s += separator % m_message; }
229  return s;
230  }
231 
232  CStatusMessage CTextMessage::asStatusMessage(bool withSender, bool withRecipient, const QString &separator) const
233  {
234  const QString m = this->asString(withSender, withRecipient, separator);
235  return { this, CStatusMessage::SeverityInfo, m };
236  }
237 
238  QString CTextMessage::asHtmlSummary(const QString &separator) const
239  {
240  return this->asString(true, true, separator);
241  }
242 
243  void CTextMessage::toggleSenderRecipient() { std::swap(m_senderCallsign, m_recipientCallsign); }
244 
246  {
247  // some first level checks, before really parsing the message
248  if (this->isEmpty()) { return false; }
249  if (this->isPrivateMessage()) { return false; }
250  if (m_message.length() > 15 || m_message.length() < 10)
251  {
252  return false;
253  } // SELCAL AB-CD -> 12, I allow some more characters as I do not know wheter in real life it exactly matches
254  return this->getSelcalCode().length() == 4;
255  }
256 
257  bool CTextMessage::isSelcalMessageFor(const QString &selcal) const
258  {
259  if (!CSelcal::isValidCode(selcal)) return false;
260  return selcal.toUpper() == this->getSelcalCode();
261  }
262 
264  {
265  // http://forums.vatsim.net/viewtopic.php?f=8&t=63467#p458062
266  // When the ATC client sends a selcal, it goes out as a text message
267  // on their primary frequency, formatted like so:
268  // SELCAL AB-CD
269 
270  if (this->isEmpty()) return {};
271  if (this->isPrivateMessage()) return {};
272  if (!m_message.startsWith(QLatin1String("SELCAL"), Qt::CaseInsensitive)) return {};
273  if (m_message.length() > 15 || m_message.length() < 10)
274  return {}; // SELCAL AB-CD -> 12, I allow some more characters as I do not know wheter in real life it
275  // exactly matches
276  QString candidate = removeChars(m_message, [](QChar c) { return !c.isLetter(); }); // SELCALABCD
277  if (candidate.length() != 10) return {};
278  return std::move(candidate).right(4).toUpper();
279  }
280 
281  CIcons::IconIndex CTextMessage::toIcon() const { return m_senderCallsign.toIcon(); }
282 
283  QPixmap CTextMessage::toPixmap() const { return CIcon(toIcon()).toPixmap(); }
284 
286  {
287  if (index.isMyself()) { return QVariant::fromValue(*this); }
289 
290  const ColumnIndex i = index.frontCasted<ColumnIndex>();
291  switch (i)
292  {
293  case IndexSenderCallsign: return m_senderCallsign.propertyByIndex(index.copyFrontRemoved());
294  case IndexRecipientCallsign: return m_recipientCallsign.propertyByIndex(index.copyFrontRemoved());
295  case IndexRecipientCallsignOrFrequency: return QVariant::fromValue(this->getRecipientCallsignOrFrequency());
296  case IndexMessage: return QVariant::fromValue(m_message);
297  default: return CValueObject::propertyByIndex(index);
298  }
299  }
300 
301  void CTextMessage::setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
302  {
303  if (index.isMyself())
304  {
305  (*this) = variant.value<CTextMessage>();
306  return;
307  }
309  {
310  ITimestampBased::setPropertyByIndex(index, variant);
311  return;
312  }
313 
314  const ColumnIndex i = index.frontCasted<ColumnIndex>();
315  switch (i)
316  {
317  case IndexSenderCallsign: m_senderCallsign.setPropertyByIndex(index.copyFrontRemoved(), variant); break;
318  case IndexRecipientCallsign: m_recipientCallsign.setPropertyByIndex(index.copyFrontRemoved(), variant); break;
319  case IndexMessage: m_message = variant.value<QString>(); break;
320  default: CValueObject::setPropertyByIndex(index, variant); break;
321  }
322  }
323 
325  {
327  {
328  return ITimestampBased::comparePropertyByIndex(index, compareValue);
329  }
330  const ColumnIndex i = index.frontCasted<ColumnIndex>();
331  switch (i)
332  {
333  case IndexSenderCallsign:
334  return m_senderCallsign.comparePropertyByIndex(index.copyFrontRemoved(), compareValue.getSenderCallsign());
335  case IndexRecipientCallsign:
336  return m_recipientCallsign.comparePropertyByIndex(index.copyFrontRemoved(),
337  compareValue.getRecipientCallsign());
338  case IndexRecipientCallsignOrFrequency:
339  if (this->isRadioMessage()) { return this->getFrequency().compare(compareValue.getFrequency()); }
340  return m_recipientCallsign.comparePropertyByIndex(index.copyFrontRemoved(),
341  compareValue.getRecipientCallsign());
342  default: return CValueObject::comparePropertyByIndex(index, *this);
343  }
344  Q_ASSERT_X(false, Q_FUNC_INFO, "No comparison");
345  return 0;
346  }
347 } // 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: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
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:76
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
CTextMessage()
Default constructor.
Definition: textmessage.h:43
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.
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:35
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:204
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
#define SWIFT_DEFINE_VALUEOBJECT_MIXINS(Namespace, Class)
Explicit template definition of mixins for a CValueObject subclass.
Definition: valueobject.h:67