swift
flightplancomponent.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 
4 #include "flightplancomponent.h"
5 
6 #include <QCheckBox>
7 #include <QComboBox>
8 #include <QCompleter>
9 #include <QDateTime>
10 #include <QDesktopServices>
11 #include <QFile>
12 #include <QLabel>
13 #include <QLineEdit>
14 #include <QMessageBox>
15 #include <QPlainTextEdit>
16 #include <QPointer>
17 #include <QPushButton>
18 #include <QRadioButton>
19 #include <QStringBuilder>
20 #include <QTabBar>
21 #include <QToolButton>
22 #include <QWidgetAction>
23 #include <Qt>
24 
25 #include "ui_flightplancomponent.h"
26 
27 #include "config/buildconfig.h"
31 #include "core/webdataservices.h"
36 #include "gui/eventfilter.h"
37 #include "gui/guiapplication.h"
38 #include "gui/uppercasevalidator.h"
41 #include "misc/aviation/altitude.h"
42 #include "misc/aviation/callsign.h"
43 #include "misc/directoryutils.h"
44 #include "misc/logcategories.h"
45 #include "misc/logmessage.h"
46 #include "misc/network/user.h"
47 #include "misc/pq/pqstring.h"
48 #include "misc/pq/speed.h"
49 #include "misc/pq/units.h"
51 #include "misc/statusmessage.h"
52 #include "misc/stringutils.h"
53 #include "misc/swiftdirectories.h"
54 
55 using namespace swift::misc;
56 using namespace swift::misc::aviation;
57 using namespace swift::misc::network;
58 using namespace swift::misc::simulation;
59 using namespace swift::misc::physical_quantities;
60 using namespace swift::gui;
61 using namespace swift::core;
62 using namespace swift::config;
63 
64 namespace swift::gui::components
65 {
66  CFlightPlanComponent::CFlightPlanComponent(QWidget *parent)
67  : COverlayMessagesTabWidget(parent), ui(new Ui::CFlightPlanComponent)
68  {
69  Q_ASSERT_X(sGui, Q_FUNC_INFO, "missing sGui");
70  Q_ASSERT_X(sGui->hasWebDataServices(), Q_FUNC_INFO, "missing web services");
71 
72  // UI
73  ui->setupUi(this);
74  this->setCurrentIndex(0);
75 
76  // Initialize wake turbulence category selection
77  for (const WakeTurbulenceEntry &item : std::as_const(m_wakeTurbulenceCategories))
78  {
79  ui->cb_Wtc->addItem(item.m_name);
80  }
81 
82  // fix style
83  this->tabBar()->setExpanding(false);
84  this->tabBar()->setUsesScrollButtons(true);
85 
86  // overlay
87  this->setOverlaySizeFactors(0.8, 0.9);
88  this->setReducedInfo(true);
89  this->setForceSmall(true);
90 
91  setupNavComContextMenu();
92  setupSsrContextMenu();
93 
94  // rules
95  ui->cb_FlightRule->clear();
96  ui->cb_FlightRule->addItems(CFlightPlan::flightRules());
97 
98  // validators
99  ui->le_Callsign->setValidator(new CUpperCaseValidator(ui->le_Callsign));
100  ui->le_AircraftType->setValidator(new CUpperCaseValidator(ui->le_AircraftType));
101  ui->le_DestinationAirport->setValidator(new CUpperCaseValidator(ui->le_DestinationAirport));
102  ui->le_AlternateAirport->setValidator(new CUpperCaseValidator(ui->le_AlternateAirport));
103  ui->le_OriginAirport->setValidator(new CUpperCaseValidator(ui->le_OriginAirport));
104  ui->le_AirlineOperator->setValidator(new CUpperCaseValidator(ui->le_AirlineOperator));
105  ui->le_AircraftRegistration->setValidator(new CUpperCaseValidator(ui->le_AircraftRegistration));
106 
107  ui->le_NavComEquipment->setReadOnly(true);
108  ui->le_SsrEquipment->setReadOnly(true);
109 
110  CUpperCaseEventFilter *ef = new CUpperCaseEventFilter(ui->pte_Route);
111  ui->pte_Route->installEventFilter(ef);
112  ef = new CUpperCaseEventFilter(ui->pte_Remarks);
113  ui->pte_Remarks->installEventFilter(ef);
114  ef = new CUpperCaseEventFilter(ui->pte_AdditionalRemarks);
115  ui->pte_AdditionalRemarks->installEventFilter(ef);
116 
117  // connect
118  connect(ui->pb_Send, &QPushButton::pressed, this, &CFlightPlanComponent::sendFlightPlan, Qt::QueuedConnection);
119  connect(ui->pb_Download, &QPushButton::pressed, this, &CFlightPlanComponent::loadFlightPlanFromNetwork,
120  Qt::QueuedConnection);
121  connect(ui->pb_Reset, &QPushButton::pressed, this, &CFlightPlanComponent::resetFlightPlan,
122  Qt::QueuedConnection);
123  connect(ui->tb_SyncWithSimulator, &QPushButton::released, this, &CFlightPlanComponent::syncWithSimulator,
124  Qt::QueuedConnection);
125  connect(ui->pb_SimBrief, &QPushButton::pressed, this, &CFlightPlanComponent::loadFromSimBrief,
126  Qt::QueuedConnection);
127 
128  connect(ui->pb_SaveTemplate, &QPushButton::released, this, &CFlightPlanComponent::saveTemplateToDisk,
129  Qt::QueuedConnection);
130  connect(ui->pb_LoadTemplate, &QPushButton::released, this, &CFlightPlanComponent::loadTemplateFromDisk,
131  Qt::QueuedConnection);
132  connect(ui->pb_ClearTemplate, &QPushButton::released, this, &CFlightPlanComponent::clearTemplate,
133  Qt::QueuedConnection);
134 
135  connect(ui->cb_VoiceCapabilities, &QComboBox::currentTextChanged, this,
136  &CFlightPlanComponent::currentTextChangedToBuildRemarks, Qt::QueuedConnection);
137  connect(ui->cb_VoiceCapabilities, &QComboBox::currentTextChanged, this,
138  &CFlightPlanComponent::syncVoiceComboBoxes, Qt::QueuedConnection);
139  connect(ui->cb_VoiceCapabilitiesFirstPage, &QComboBox::currentTextChanged, this,
140  &CFlightPlanComponent::syncVoiceComboBoxes, Qt::QueuedConnection);
141  connect(ui->cb_PerformanceCategory, &QComboBox::currentTextChanged, this,
142  &CFlightPlanComponent::currentTextChangedToBuildRemarks, Qt::QueuedConnection);
143  connect(ui->cb_PilotRating, &QComboBox::currentTextChanged, this,
144  &CFlightPlanComponent::currentTextChangedToBuildRemarks, Qt::QueuedConnection);
145  connect(ui->cb_RequiredNavigationPerformance, &QComboBox::currentTextChanged, this,
146  &CFlightPlanComponent::currentTextChangedToBuildRemarks, Qt::QueuedConnection);
147 
148  connect(ui->pb_LoadDisk, &QPushButton::clicked, this, &CFlightPlanComponent::loadFromDisk,
149  Qt::QueuedConnection);
150  connect(ui->pb_SaveDisk, &QPushButton::clicked, this, &CFlightPlanComponent::saveToDisk, Qt::QueuedConnection);
151 
152  connect(ui->le_AircraftRegistration, &QLineEdit::textChanged, this, &CFlightPlanComponent::buildRemarksString,
153  Qt::QueuedConnection);
154  connect(ui->le_AirlineOperator, &QLineEdit::textChanged, this, &CFlightPlanComponent::buildRemarksString,
155  Qt::QueuedConnection);
156  connect(ui->cb_NoSidsStarts, &QCheckBox::released, this, &CFlightPlanComponent::buildRemarksString,
157  Qt::QueuedConnection);
158 
159  connect(ui->pte_AdditionalRemarks, &QPlainTextEdit::textChanged, this,
160  &CFlightPlanComponent::buildRemarksString, Qt::QueuedConnection);
161  connect(ui->frp_SelcalCode, &CSelcalCodeSelector::valueChanged, this, &CFlightPlanComponent::buildRemarksString,
162  Qt::QueuedConnection);
163  connect(ui->frp_SelcalCode, &CSelcalCodeSelector::valueChanged, this,
164  &CFlightPlanComponent::setSelcalInOwnAircraft, Qt::QueuedConnection);
165  connect(ui->pb_CopyOver, &QPushButton::pressed, this, &CFlightPlanComponent::copyRemarksConfirmed,
166  Qt::QueuedConnection);
167  connect(ui->pb_GetFromGenerator, &QPushButton::pressed, this, &CFlightPlanComponent::copyRemarksConfirmed,
168  Qt::QueuedConnection);
169  connect(ui->pb_RemarksGenerator, &QPushButton::clicked, this, &CFlightPlanComponent::currentTabGenerator,
170  Qt::QueuedConnection);
171 
172  connect(
173  ui->tb_EditNavComEquipment, &QToolButton::clicked, this,
174  [this]() { m_navComEquipmentMenu->popup(QCursor::pos()); }, Qt::QueuedConnection);
175  connect(
176  ui->tb_NavComHelp, &QToolButton::clicked, this,
177  []() { QDesktopServices::openUrl(sGui->getGlobalSetup().getComNavEquipmentHelpUrl()); },
178  Qt::QueuedConnection);
179 
180  connect(
181  ui->tb_EditSsrEquipment, &QToolButton::clicked, this,
182  [this]() { m_ssrEquipmentMenu->popup(QCursor::pos()); }, Qt::QueuedConnection);
183  connect(
184  ui->tb_SsrHelp, &QToolButton::clicked, this,
185  []() { QDesktopServices::openUrl(sGui->getGlobalSetup().getSsrEquipmentHelpUrl()); }, Qt::QueuedConnection);
186 
187  connect(ui->tb_AltitudeDialog, &QToolButton::clicked, this, &CFlightPlanComponent::altitudeDialog,
188  Qt::QueuedConnection);
189 
190  connect(ui->le_AircraftType, &QLineEdit::editingFinished, this, &CFlightPlanComponent::aircraftTypeChanged,
191  Qt::QueuedConnection);
192 
193  connect(ui->pb_Remarks, &QPushButton::pressed, this, &CFlightPlanComponent::remarksHistory,
194  Qt::QueuedConnection);
195  connect(ui->pb_AddRemarks, &QPushButton::pressed, this, &CFlightPlanComponent::remarksHistory,
196  Qt::QueuedConnection);
197 
198  // web services
199  connect(sGui->getWebDataServices(), &CWebDataServices::swiftDbAllDataRead, this,
200  &CFlightPlanComponent::swiftWebDataRead, Qt::QueuedConnection);
201 
202  // init GUI
203  this->resetFlightPlan();
204  this->buildRemarksString();
205 
206  // prefill some data derived from what was used last
207  const QPointer<CFlightPlanComponent> myself(this);
208  QTimer::singleShot(2500, this, [=] {
209  if (!sGui || sGui->isShuttingDown() || !myself) { return; }
210 
211  this->loadTemplateFromDisk();
212  if (sGui->getIContextSimulator()->isSimulatorAvailable()) { this->prefillWithOwnAircraftData(); }
213  else
214  {
215  const CAircraftModel model = m_lastAircraftModel.get();
216  const CServer server = m_lastServer.get();
217  CSimulatedAircraft aircraft(model);
218  aircraft.setPilot(server.getUser());
219  this->prefillWithAircraftData(aircraft);
220  }
221  });
222  }
223 
225 
227  {
228  if (m_sentFlightPlan.wasSentOrLoaded()) { return; } // when loaded or sent do not override
229  this->prefillWithOwnAircraftData();
230  }
231 
232  void CFlightPlanComponent::prefillWithOwnAircraftData()
233  {
234  if (!sGui->getIContextOwnAircraft()) { return; }
235  if (!sGui->getIContextSimulator()) { return; }
236  if (!sGui->getIContextSimulator()->isSimulatorAvailable()) { return; }
238  this->prefillWithAircraftData(ownAircraft);
239  }
240 
241  void CFlightPlanComponent::prefillWithAircraftData(const CSimulatedAircraft &aircraft, bool force)
242  {
243  if (!force && m_sentFlightPlan.wasSentOrLoaded()) { return; }
244 
245  // only override with valid values
246  if (CCallsign::isValidAircraftCallsign(aircraft.getCallsignAsString()))
247  {
248  ui->le_Callsign->setText(aircraft.getCallsign().asString());
249  }
250  if (CAircraftIcaoCode::isValidDesignator(aircraft.getAircraftIcaoCodeDesignator()))
251  {
252  ui->le_AircraftType->setText(aircraft.getAircraftIcaoCodeDesignator());
253  }
254  m_model = aircraft.getModel();
255 
256  if (aircraft.getAircraftIcaoCode().isLoadedFromDb() && aircraft.getAircraftIcaoCode().hasValidWtc())
257  {
258  updateWakeTurbulenceCategorySelector(aircraft.getAircraftIcaoCode().getWtc());
259  }
260 
261  this->prefillWithUserData(aircraft.getPilot());
262  }
263 
264  void CFlightPlanComponent::prefillWithUserData(const CUser &user)
265  {
266  if (m_sentFlightPlan.wasSentOrLoaded()) { return; }
267 
268  // only override with valid values
269  if (user.hasRealName()) { ui->le_PilotsName->setText(user.getRealName()); }
270  if (user.hasHomeBase()) { ui->le_PilotsHomeBase->setText(user.getHomeBase().getIcaoCode()); }
271  if (user.hasCallsign()) { ui->le_Callsign->setText(user.getCallsign().asString()); }
272  }
273 
275  {
276  ui->le_AlternateAirport->setText(flightPlan.getAlternateAirportIcao().asString());
277  ui->le_DestinationAirport->setText(flightPlan.getDestinationAirportIcao().asString());
278  ui->le_OriginAirport->setText(flightPlan.getOriginAirportIcao().asString());
279  ui->pte_Route->setPlainText(flightPlan.getRoute());
280  ui->pte_Remarks->setPlainText(flightPlan.getRemarks());
281  ui->le_TakeOffTimePlanned->setText(flightPlan.getTakeoffTimePlannedHourMin());
282  ui->le_FuelOnBoard->setText(flightPlan.getFuelTimeHourMin());
283  ui->le_EstimatedTimeEnroute->setText(flightPlan.getEnrouteTimeHourMin());
284  ui->le_CruiseTrueAirspeed->setText(
285  flightPlan.getCruiseTrueAirspeed().valueRoundedWithUnit(CSpeedUnit::kts(), 0));
286 
287  const CAltitude cruiseAlt = flightPlan.getCruiseAltitude();
288  ui->lep_CrusingAltitude->setAltitude(cruiseAlt);
289 
290  const QString r = flightPlan.getFlightRulesAsString();
291  if (CFlightPlan::flightRules().contains(r, Qt::CaseInsensitive)) { ui->cb_FlightRule->setCurrentText(r); }
292  else if (flightPlan.getFlightRules() == CFlightPlan::UNKNOWN)
293  {
294  ui->cb_FlightRule->setCurrentText(CFlightPlan::flightRulesToString(CFlightPlan::IFR));
295  const CStatusMessage m = CStatusMessage(this).validationWarning(u"Unknown flight rule, setting to default");
296  this->showOverlayMessage(m);
297  }
298 
299  if (!flightPlan.getRemarks().isEmpty())
300  {
301  const QString rem = flightPlan.getRemarks();
302  this->setRemarksUIValues(rem);
303  }
304 
305  updateWakeTurbulenceCategorySelector(flightPlan.getAircraftInfo().getWtc());
306 
307  m_navComEquipment = flightPlan.getAircraftInfo().getComNavEquipment();
308  updateNavComEquipmentUi();
309 
310  m_ssrEquipment = flightPlan.getAircraftInfo().getSsrEquipment();
311  updateSsrEquipmentUi();
312  }
313 
315  {
316  static const QStringList cats { CLogCategories::flightPlan(), CLogCategories::guiComponent() };
317  return cats;
318  }
319 
320  CStatusMessageList CFlightPlanComponent::validateAndInitializeFlightPlan(CFlightPlan &flightPlan)
321  {
322  CStatusMessageList messages;
323  const bool vfr = this->isVfr();
324 
325  const CFlightPlan::FlightRules rules = this->getFlightRules();
326  flightPlan.setFlightRule(rules);
327 
328  // callsign
329  QString v;
330  v = ui->le_Callsign->text().trimmed().toUpper();
331  if (v.isEmpty())
332  {
333  // messages.push_back(CStatusMessage(this).validationError(u"Missing '%1'") << ui->lbl_Callsign->text());
334  }
335  else if (!CCallsign::isValidAircraftCallsign(v))
336  {
337  messages.push_back(CStatusMessage(this).validationError(u"Invalid callsign '%1'") << v);
338  }
339  flightPlan.setCallsign(CCallsign(v));
340 
341  // aircraft ICAO / aircraft type
342  v = ui->le_AircraftType->text().trimmed().toUpper();
343  if (v.isEmpty())
344  {
345  messages.push_back(CStatusMessage(this).validationError(u"Missing '%1'") << ui->lbl_AircraftType->text());
346  }
347  else if (!CAircraftIcaoCode::isValidDesignator(v))
348  {
349  messages.push_back(CStatusMessage(this).validationError(u"Invalid aircraft ICAO code '%1'") << v);
350  }
353  {
354  messages.push_back(CStatusMessage(this).validationWarning(u"Are you sure '%1' is a valid type?") << v);
355  }
356 
357  CFlightPlanAircraftInfo info(this->getAircraftIcaoCode(), m_navComEquipment, m_ssrEquipment,
358  getSelectedWakeTurbulenceCategory());
359  flightPlan.setAircraftInfo(info);
360 
361  // route
362  v = ui->pte_Route->toPlainText().trimmed();
363  const int routeLength = v.length();
364  if (v.isEmpty())
365  {
366  messages.push_back(
367  CStatusMessage(this).validation(vfr ? CStatusMessage::SeverityInfo : CStatusMessage::SeverityWarning,
368  u"Missing '%1'")
369  << ui->lbl_Route->text());
370  }
371  else if (routeLength > CFlightPlan::MaxRouteLength)
372  {
373  messages.push_back(
374  CStatusMessage(this).validationError(u"Flight plan route length exceeded (%1 chars max.)")
375  << CFlightPlan::MaxRouteLength);
376  }
377  else { flightPlan.setRoute(v); }
378 
379  // remarks
380  v = ui->pte_Remarks->toPlainText().trimmed();
381  const int remarksLength = v.length();
382  if (v.isEmpty())
383  {
384  messages.push_back(CStatusMessage(this).validationError(u"No '%1', voice capabilities are mandatory")
385  << ui->pb_Remarks->text());
386  }
387  else if (remarksLength > CFlightPlan::MaxRemarksLength)
388  {
389  messages.push_back(
390  CStatusMessage(this).validationError(u"Flight plan remarks length exceeded (%1 chars max.)")
391  << CFlightPlan::MaxRemarksLength);
392  }
393  else { flightPlan.setRemarks(v); }
394 
395  // Total length
396  if ((remarksLength + routeLength) > CFlightPlan::MaxRouteAndRemarksLength)
397  {
398  messages.push_back(CStatusMessage(this).validationError(
399  u"Flight plan route (%1) and remarks (%2) length exceeded (%3 chars max.)")
400  << routeLength << remarksLength << CFlightPlan::MaxRemarksLength);
401  }
402 
403  // time enroute
404  v = ui->le_EstimatedTimeEnroute->text();
405  if (v.isEmpty() || v == defaultTime())
406  {
407  messages.push_back(CStatusMessage(this).validationWarning(u"Missing '%1'")
408  << ui->lbl_EstimatedTimeEnroute->text());
409  }
410  flightPlan.setEnrouteTime(v);
411 
412  // fuel
413  v = ui->le_FuelOnBoard->text();
414  if (v.isEmpty() || v == defaultTime())
415  {
416  messages.push_back(CStatusMessage(this).validationWarning(u"Missing '%1'") << ui->lbl_FuelOnBoard->text());
417  }
418  flightPlan.setFuelTime(v);
419 
420  // take off time
421  v = ui->le_TakeOffTimePlanned->text();
422  if (v.isEmpty() || v == defaultTime())
423  {
424  messages.push_back(CStatusMessage(this).validationWarning(u"Missing '%1'")
425  << ui->lbl_TakeOffTimePlanned->text());
426  }
427  flightPlan.setTakeoffTimePlanned(v);
428 
429  // cruising alt
430  if (ui->lep_CrusingAltitude->isValid(&messages))
431  {
432  const CAltitude cruisingAltitude = ui->lep_CrusingAltitude->getAltitude();
433  flightPlan.setCruiseAltitude(cruisingAltitude);
434  }
435 
436  // destination airport
437  v = ui->le_DestinationAirport->text();
438  if (v.isEmpty() || v.endsWith(defaultIcao(), Qt::CaseInsensitive))
439  {
440  messages.push_back(CStatusMessage(this).validationError(u"Missing '%1'")
441  << ui->lbl_DestinationAirport->text());
442  flightPlan.setDestinationAirportIcao(QString());
443  }
444  else
445  {
446  flightPlan.setDestinationAirportIcao(v);
447  if (!flightPlan.getDestinationAirportIcao().hasValidIcaoCode(false))
448  {
449  messages.push_back(CStatusMessage(this).validationWarning(u"Wrong or missing '%1'")
450  << ui->lbl_DestinationAirport->text());
451  }
452  }
453 
454  // origin airport
455  v = ui->le_OriginAirport->text();
456  if (v.isEmpty() || v.endsWith(defaultIcao(), Qt::CaseInsensitive))
457  {
458  messages.push_back(CStatusMessage(this).validationError(u"Missing '%1'") << ui->lbl_OriginAirport->text());
459  flightPlan.setOriginAirportIcao(defaultIcao());
460  }
461  else
462  {
463  flightPlan.setOriginAirportIcao(v);
464  if (!flightPlan.getOriginAirportIcao().hasValidIcaoCode(false))
465  {
466  messages.push_back(CStatusMessage(this).validationWarning(u"Wrong or missing '%1'")
467  << ui->lbl_DestinationAirport->text());
468  }
469  }
470 
471  // TAS
472  v = ui->le_CruiseTrueAirspeed->text();
473  CSpeed cruiseTAS;
474  cruiseTAS.parseFromString(v, CPqString::SeparatorBestGuess);
475  if (cruiseTAS.isNull())
476  {
477  messages.push_back(
478  CStatusMessage(this).validationError(u"Wrong TAS, %1. Try adding a unit like '100kts' or '150km/h'")
479  << ui->lbl_CruiseTrueAirspeed->text());
480  flightPlan.setDestinationAirportIcao(defaultIcao());
481  }
482  else { flightPlan.setCruiseTrueAirspeed(cruiseTAS); }
483 
484  // Optional fields
485  v = ui->le_AlternateAirport->text();
486  if (v.isEmpty() || v.endsWith(defaultIcao(), Qt::CaseInsensitive))
487  {
488  if (!messages.hasWarningOrErrorMessages())
489  {
490  messages.push_back(CStatusMessage(this).validationInfo(u"Missing %1")
491  << ui->lbl_AlternateAirport->text());
492  }
493  flightPlan.setAlternateAirportIcao(QString());
494  }
495  else { flightPlan.setAlternateAirportIcao(v); }
496 
497  // OK
498  if (!messages.isFailure())
499  {
500  messages.push_back(CStatusMessage(this).validationInfo(u"Flight plan validation passed"));
501  }
502  return messages;
503  }
504 
505  void CFlightPlanComponent::sendFlightPlan()
506  {
507  CFlightPlan flightPlan;
508  const CStatusMessageList messages = this->validateAndInitializeFlightPlan(flightPlan);
509  if (messages.isSuccess())
510  {
511  // no error, send if possible
512  CStatusMessage m;
513  QString lastSent;
515  {
516  flightPlan.setWhenLastSentOrLoaded(QDateTime::currentDateTimeUtc());
517  sGui->getIContextNetwork()->sendFlightPlan(flightPlan);
518  lastSent = flightPlan.whenLastSentOrLoaded().toString();
519  m = CStatusMessage(this).validationInfo(u"Sent flight plan");
520  }
521  else
522  {
523  flightPlan.setWhenLastSentOrLoaded(QDateTime()); // empty
524  m = CStatusMessage(this).validationError(u"No errors, but not connected, cannot send flight plan");
525  }
526  ui->le_LastSent->setText(lastSent);
527  if (m.isSeverityInfoOrLess())
528  {
529  this->showOverlayHTMLMessage(m, OverlayTimeout);
530  this->updateRemarksHistories(); // all OK, we keep that in history
531  }
532  else { this->showOverlayMessage(m, OverlayTimeout); }
533  m_sentFlightPlan = flightPlan; // last valid FP
534  }
535  else { this->showOverlayMessages(messages); }
536  }
537 
538  void CFlightPlanComponent::resetFlightPlan()
539  {
540  this->prefillWithOwnAircraftData();
541  ui->le_AircraftRegistration->clear();
542  ui->le_AirlineOperator->clear();
543  ui->lep_CrusingAltitude->setText("FL70");
544  ui->le_CruiseTrueAirspeed->setText("100 kts");
545  ui->pte_Remarks->clear();
546  ui->pte_Route->clear();
547  ui->le_AlternateAirport->clear();
548  ui->le_DestinationAirport->clear();
549  ui->le_OriginAirport->clear();
550  ui->le_FuelOnBoard->setText(defaultTime());
551  ui->le_EstimatedTimeEnroute->setText(defaultTime());
552  ui->le_TakeOffTimePlanned->setText(QDateTime::currentDateTimeUtc().addSecs(30 * 60).toString("hh:mm"));
553  this->syncVoiceComboBoxes(ui->cb_VoiceCapabilities->itemText(0));
554  }
555 
556  void CFlightPlanComponent::loadFromDisk()
557  {
558  CStatusMessageList msgs;
559  const QString fileName = QFileDialog::getOpenFileName(
560  this, tr("Load flight plan"), this->getDefaultFilename(true),
561  "Flight plans (*.json *.sfp *.xml);;swift (*.json *.txt);;SimBrief (*.xml);;SB4 (*.sfp)");
562  if (fileName.isEmpty()) { return; }
563  CFlightPlan fp = CFlightPlan::loadFromMultipleFormats(fileName, &msgs);
564  if (!fp.hasCallsign()) { fp.setCallsign(ui->le_Callsign->text()); } // set callsign if it wasn't set
565 
566  if (msgs.isSuccess())
567  {
568  this->fillWithFlightPlanData(fp);
569  this->updateDirectorySettings(fileName);
570  }
571  else { this->showOverlayMessages(msgs, true, OverlayTimeout); }
572  }
573 
574  void CFlightPlanComponent::loadTemplateFromDisk()
575  {
576  const QFile f(this->getTemplateName());
577  if (!f.exists()) { return; }
578 
579  CStatusMessageList msgs;
580  CFlightPlan fp = CFlightPlan::loadFromMultipleFormats(f.fileName(), &msgs);
581  if (!fp.hasCallsign()) { fp.setCallsign(ui->le_Callsign->text()); } // set callsign if it wasn't set
582  if (msgs.isSuccess()) { this->fillWithFlightPlanData(fp); }
583  }
584 
585  void CFlightPlanComponent::saveToDisk()
586  {
587  CStatusMessage m;
588  const QString fileName = QFileDialog::getSaveFileName(
589  nullptr, tr("Save flight plan"), this->getDefaultFilename(false), tr("swift (*.json;*.txt)"));
590  do {
591  if (fileName.isEmpty())
592  {
593  m = CStatusMessage(this, CStatusMessage::SeverityDebug, u"Save canceled", true);
594  break;
595  }
596 
597  QFileInfo fi(fileName);
598  QDir fpDir = fi.absoluteDir();
599  if (CDirectoryUtils::isInApplicationDirectory(fpDir.absolutePath()))
600  {
601  const int ret =
602  QMessageBox::warning(this, "swift flight plan",
603  "You try to save inside the swift directory '" + fpDir.absolutePath() +
604  "'\n\nThis is not recommended!"
605  "\n\nDo you want to really do this?",
606  QMessageBox::Save | QMessageBox::Cancel);
607  if (ret != QMessageBox::Save) { return; }
608  }
609 
610  const bool ok = this->saveFPToDisk(fileName);
611  if (ok)
612  {
613  m = CStatusMessage(this, CStatusMessage::SeverityInfo, u"Written " % fileName, true);
614  this->updateDirectorySettings(fileName);
615  }
616  else { m = CStatusMessage(this, CStatusMessage::SeverityError, u"Writing " % fileName % u" failed", true); }
617  }
618  while (false);
619  if (m.isFailure()) { CLogMessage::preformatted(m); }
620  }
621 
622  bool CFlightPlanComponent::saveFPToDisk(const QString &fileName)
623  {
624  CFlightPlan fp;
625  const CStatusMessageList msgs = this->validateAndInitializeFlightPlan(fp); // get data
626  // if (msgs.hasErrorMessages()) { return false; }
627  Q_UNUSED(msgs)
628 
629  // save as CVariant format
630  const CVariant variantFp = CVariant::fromValue(fp);
631  const QString json(variantFp.toJsonString());
632  const bool ok = CFileUtils::writeStringToFile(json, fileName);
633 
634  return ok;
635  }
636 
637  void CFlightPlanComponent::saveTemplateToDisk()
638  {
639  const QString fn = this->getTemplateName();
640  const bool ok = this->saveFPToDisk(fn);
641  if (ok) { CLogMessage(this).info(u"Saved FP template '%1'") << fn; }
642  else { CLogMessage(this).warning(u"Saving FP template '%1' failed") << fn; }
643  }
644 
645  void CFlightPlanComponent::clearTemplate()
646  {
647  QFile f(this->getTemplateName());
648  if (!f.exists()) { return; }
649  const bool r = f.remove();
650  if (r) { CLogMessage(this).info(u"Deleted FP template '%1'") << f.fileName(); }
651  }
652 
653  QString CFlightPlanComponent::getTemplateName() const
654  {
655  const QString fn = CFileUtils::appendFilePathsAndFixUnc(CSwiftDirectories::normalizedApplicationDataDirectory(),
656  QStringLiteral("swiftFlightPlanTemplate.json"));
657  return fn;
658  }
659 
660  void CFlightPlanComponent::setSelcalInOwnAircraft()
661  {
662  if (!sGui || !sGui->getIContextOwnAircraft()) { return; }
663  if (!ui->frp_SelcalCode->hasValidCode()) { return; }
664  sGui->getIContextOwnAircraft()->updateSelcal(ui->frp_SelcalCode->getSelcal(), flightPlanIdentifier());
665  }
666 
667  void CFlightPlanComponent::loadFlightPlanFromNetwork()
668  {
669  if (!sGui || sGui->isShuttingDown() || !sGui->getIContextNetwork() ||
671  {
672  const CStatusMessage m =
673  CLogMessage(this).validationWarning(u"Cannot load network flight plan, network not connected");
674  this->showOverlayHTMLMessage(m, OverlayTimeout);
675  return;
676  }
677 
679  const CFlightPlan loadedPlan = sGui->getIContextNetwork()->loadFlightPlanFromNetwork(ownAircraft.getCallsign());
680  if (loadedPlan.wasSentOrLoaded())
681  {
682  const QMessageBox::StandardButton r = QMessageBox::warning(
683  this, "Loaded FP", "Override current flight plan data?", QMessageBox::Yes | QMessageBox::No);
684  if (r != QMessageBox::Yes) { return; }
685  this->fillWithFlightPlanData(loadedPlan);
686  CLogMessage(this).info(u"Updated with loaded flight plan");
687  }
688  else
689  {
690  const CStatusMessage m = CLogMessage(this).warning(u"No flight plan data in loaded plan");
691  this->showOverlayHTMLMessage(m, OverlayTimeout);
692  }
693  }
694 
695  void CFlightPlanComponent::buildRemarksString()
696  {
697  QString v = ui->cb_VoiceCapabilities->currentText().toUpper();
698  QString rem = CFlightPlanRemarks::textToVoiceCapabilitiesRemarks(v).append(" ");
699 
700  v = ui->le_AirlineOperator->text().trimmed();
701  if (!v.isEmpty()) rem.append("OPR/").append(v).append(" ");
702 
703  v = ui->le_AircraftRegistration->text().trimmed();
704  if (!v.isEmpty()) rem.append("REG/").append(v).append(" ");
705 
706  v = ui->cb_PilotRating->currentText().toUpper();
707  if (v.contains("P1")) { rem.append("PR/P1 "); }
708  else if (v.contains("P2")) { rem.append("PR/P2 "); }
709  else if (v.contains("P3")) { rem.append("PR/P3 "); }
710  else if (v.contains("P4")) { rem.append("PR/P4 "); }
711  else if (v.contains("P5")) { rem.append("PR/P5 "); }
712 
713  v = ui->cb_RequiredNavigationPerformance->currentText().toUpper();
714  if (v.contains("10")) { rem.append("RNP10 "); }
715  else if (v.contains("4")) { rem.append("RNP4 "); }
716 
717  v = ui->cb_PerformanceCategory->currentText().toUpper();
718  if (v.startsWith("A")) { rem.append("PER/A "); }
719  else if (v.startsWith("B")) { rem.append("PER/B "); }
720  else if (v.startsWith("C")) { rem.append("PER/C "); }
721  else if (v.startsWith("D")) { rem.append("PER/D "); }
722  else if (v.startsWith("E")) { rem.append("PER/E "); }
723 
724  if (ui->frp_SelcalCode->hasValidCode())
725  {
726  rem.append("SEL/").append(ui->frp_SelcalCode->getSelcalCode());
727  rem.append(" ");
728  }
729 
730  if (ui->cb_NoSidsStarts->isChecked()) { rem.append("NO SID/STAR "); }
731 
732  v = ui->pte_AdditionalRemarks->toPlainText().trimmed();
733  if (!v.isEmpty()) { rem.append(v); }
734 
735  rem = rem.simplified().trimmed();
736  ui->pte_RemarksGenerated->setPlainText(rem);
737  }
738 
739  void CFlightPlanComponent::copyRemarks(bool confirm)
740  {
741  const QString generated = ui->pte_RemarksGenerated->toPlainText().trimmed();
742  if (confirm && !this->overrideRemarks()) { return; }
743  ui->pte_Remarks->setPlainText(generated);
744  CLogMessage(this).info(u"Copied remarks");
745  }
746 
747  void CFlightPlanComponent::currentTabGenerator() { this->setCurrentWidget(ui->tb_RemarksGenerator); }
748 
749  void CFlightPlanComponent::swiftWebDataRead() { this->initCompleters(); }
750 
751  void CFlightPlanComponent::aircraftTypeChanged()
752  {
753  const CAircraftIcaoCode icao = this->getAircraftIcaoCode();
754  if (!icao.isLoadedFromDb()) { return; }
755  QPointer<CFlightPlanComponent> myself(this);
756  QTimer::singleShot(25, this, [=] {
757  if (!myself || !sGui || sGui->isShuttingDown()) { return; }
758  updateWakeTurbulenceCategorySelector(icao.getWtc());
759  });
760  }
761 
762  void CFlightPlanComponent::syncWithSimulator()
763  {
764  if (!sGui || sGui->isShuttingDown() || !sGui->getIContextOwnAircraft()) { return; }
765  const QMessageBox::StandardButton reply = QMessageBox::question(
766  this, QStringLiteral("Override aircraft data"),
767  QStringLiteral("Override aircraft ICAO data from simulator"), QMessageBox::Yes | QMessageBox::No);
768  if (reply != QMessageBox::Yes) { return; }
769 
771  this->prefillWithAircraftData(aircraft, true);
772  }
773 
774  CAircraftIcaoCode CFlightPlanComponent::getAircraftIcaoCode() const
775  {
776  const QString designator(ui->le_AircraftType->text());
777  if (!CAircraftIcaoCode::isValidDesignator(designator)) { return CAircraftIcaoCode(); }
778  if (sApp && sApp->hasWebDataServices())
779  {
780  const CAircraftIcaoCode designatorFromDb =
782  if (designatorFromDb.isLoadedFromDb()) { return designatorFromDb; }
783  }
784  return designator;
785  }
786 
787  bool CFlightPlanComponent::isVfr() const
788  {
789  const bool vfr = CFlightPlan::isVFRRules(ui->cb_FlightRule->currentText());
790  return vfr;
791  }
792 
793  CFlightPlan::FlightRules CFlightPlanComponent::getFlightRules() const
794  {
795  const CFlightPlan::FlightRules r = CFlightPlan::stringToFlightRules(ui->cb_FlightRule->currentText());
796  return r;
797  }
798 
799  bool CFlightPlanComponent::overrideRemarks()
800  {
801  if (!ui->pte_Remarks->toPlainText().trimmed().isEmpty())
802  {
803  const int reply = QMessageBox::question(this, "Remarks", "Override existing remarks?",
804  QMessageBox::Yes | QMessageBox::No);
805  if (reply != QMessageBox::Yes) { return false; }
806  }
807  return true;
808  }
809 
810  void CFlightPlanComponent::updateDirectorySettings(const QString &fileOrDirectory)
811  {
812  if (fileOrDirectory.isEmpty()) { return; }
813 
814  CDirectories swiftDirs = m_directories.get();
815  swiftDirs.setFlightPlanDirectory(CDirectories::fileNameToDirectory(fileOrDirectory));
816  CStatusMessage saveMsg = m_directories.setAndSave(swiftDirs);
817  CLogMessage::preformatted(saveMsg);
818  }
819 
820  void CFlightPlanComponent::altitudeDialog()
821  {
822  if (!m_altitudeDialog) { m_altitudeDialog = new CAltitudeDialog(this); }
823 
824  const QDialog::DialogCode ret = static_cast<QDialog::DialogCode>(m_altitudeDialog->exec());
825  if (ret != QDialog::Accepted) { return; }
826 
827  if (!m_altitudeDialog->getAltitudeString().isEmpty())
828  {
829  ui->lep_CrusingAltitude->setText(m_altitudeDialog->getAltitudeString());
830  }
831  }
832 
833  void CFlightPlanComponent::updateRemarksHistories()
834  {
835  QString r = ui->pte_Remarks->toPlainText();
836  if (!r.isEmpty())
837  {
838  QStringList h = m_remarksHistory.get();
839  if (consolidateRemarks(h, r))
840  {
841  CStatusMessage m = m_remarksHistory.setAndSave(h);
842  CLogMessage::preformatted(m);
843  }
844  }
845 
846  r = ui->pte_AdditionalRemarks->toPlainText();
847  if (!r.isEmpty())
848  {
849  QStringList h = m_remarksHistoryAdditional.get();
850  if (consolidateRemarks(h, r))
851  {
852  CStatusMessage m = m_remarksHistoryAdditional.setAndSave(h);
853  CLogMessage::preformatted(m);
854  }
855  }
856  }
857 
858  void CFlightPlanComponent::setRemarksUIValues(const QString &remarks)
859  {
860  if (remarks.isEmpty()) { return; }
861 
862  if (remarks.contains("/V"))
863  {
864  CGuiUtility::setComboBoxValueByContainingString(ui->cb_VoiceCapabilitiesFirstPage, "FULL");
865  CGuiUtility::setComboBoxValueByContainingString(ui->cb_VoiceCapabilities, "FULL");
866  }
867  else if (remarks.contains("/T"))
868  {
869  CGuiUtility::setComboBoxValueByContainingString(ui->cb_VoiceCapabilitiesFirstPage, "TEXT ONLY");
870  CGuiUtility::setComboBoxValueByContainingString(ui->cb_VoiceCapabilities, "TEXT ONLY");
871  }
872  else if (remarks.contains("/R"))
873  {
874  CGuiUtility::setComboBoxValueByContainingString(ui->cb_VoiceCapabilitiesFirstPage, "RECEIVE");
875  CGuiUtility::setComboBoxValueByContainingString(ui->cb_VoiceCapabilities, "RECEIVE");
876  }
877 
878  const int selcal = remarks.indexOf("SEL/");
879  if (selcal >= 0 && remarks.length() > selcal + 7)
880  {
881  const QString code = remarks.mid(selcal + 4, 4);
882  if (code.length() == 4) { ui->frp_SelcalCode->setSelcal(code); }
883  }
884  }
885 
886  void CFlightPlanComponent::loadFromSimBrief()
887  {
888  if (!sGui || sGui->isShuttingDown()) { return; }
889  if (!m_simBriefDialog) { m_simBriefDialog = new CSimBriefDownloadDialog(this); }
890  const int rv = m_simBriefDialog->exec();
891  if (rv != QDialog::Accepted) { return; }
892 
893  const CUrl url = m_simBriefDialog->getSimBriefData().getUrlAndUsername();
894  sApp->getFromNetwork(url.toNetworkRequest(), { this, &CFlightPlanComponent::handleSimBriefResponse });
895  }
896 
897  void CFlightPlanComponent::handleSimBriefResponse(QNetworkReply *nwReplyPtr)
898  {
899  // wrap pointer, make sure any exit cleans up reply
900  // required to use delete later as object is created in a different thread
901  QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
902  if (!sGui || sGui->isShuttingDown()) { return; }
903 
904  const QUrl url(nwReply->url());
905  const QString urlString(url.toString());
906 
907  if (nwReply->error() == QNetworkReply::NoError)
908  {
909  // const qint64 lastModified = CNetworkUtils::lastModifiedMsSinceEpoch(nwReply.data());
910  const QString simBriefFP(nwReplyPtr->readAll());
911  nwReplyPtr->close();
912  if (simBriefFP.isEmpty()) { this->showOverlayHTMLMessage("No SimBrief data from " % urlString); }
913  else
914  {
915  CFlightPlan fp = CFlightPlan::fromSimBriefFormat(simBriefFP);
916 
917  // Voice capability is not included from SimBrief -> set capability from UI
918  const QString currentVoiceCapability = ui->cb_VoiceCapabilities->currentText();
919  fp.setVoiceCapabilities(CFlightPlanRemarks::textToVoiceCapabilitiesRemarks(currentVoiceCapability));
920 
921  this->fillWithFlightPlanData(fp);
922  }
923  } // no error
924  else
925  {
926  // network error, try next URL
927  nwReply->abort();
928  }
929  }
930 
931  void CFlightPlanComponent::setupNavComContextMenu()
932  {
933  m_navComEquipmentMenu = new QMenu(ui->tb_EditNavComEquipment);
934  auto list = new QListWidget(m_navComEquipmentMenu);
935  list->setSelectionMode(QAbstractItemView::MultiSelection);
937 
938  connect(list, &QListWidget::itemSelectionChanged, this,
939  &CFlightPlanComponent::updateNavComEquipmentFromSelection);
940 
941  auto action = new QWidgetAction(ui->tb_EditNavComEquipment);
942  action->setDefaultWidget(list);
943  m_navComEquipmentMenu->addAction(action);
944 
945  updateNavComEquipmentUi();
946  }
947 
948  void CFlightPlanComponent::setupSsrContextMenu()
949  {
950  m_ssrEquipmentMenu = new QMenu(ui->tb_EditSsrEquipment);
951  auto list = new QListWidget(m_ssrEquipmentMenu);
952  list->setSelectionMode(QAbstractItemView::MultiSelection);
954 
955  connect(list, &QListWidget::itemSelectionChanged, this, &CFlightPlanComponent::updateSsrEquipmentFromSelection);
956 
957  auto action = new QWidgetAction(ui->tb_EditSsrEquipment);
958  action->setDefaultWidget(list);
959  m_ssrEquipmentMenu->addAction(action);
960 
961  updateSsrEquipmentUi();
962  }
963 
964  void CFlightPlanComponent::updateNavComEquipmentFromSelection()
965  {
966  const QListWidget *list = getMenuEquipmentList(m_navComEquipmentMenu);
967 
968  QString equipmentString;
969 
970  for (auto equipment : list->selectedItems()) { equipmentString.append(equipment->text()); }
971 
972  m_navComEquipment = CComNavEquipment(equipmentString);
973  updateNavComEquipmentUi();
974  }
975 
976  void CFlightPlanComponent::updateSsrEquipmentFromSelection()
977  {
978  const QListWidget *list = getMenuEquipmentList(m_ssrEquipmentMenu);
979 
980  QString ssrEquipmentString;
981 
982  for (auto equipment : list->selectedItems()) { ssrEquipmentString.append(equipment->text()); }
983 
984  m_ssrEquipment = CSsrEquipment(ssrEquipmentString);
985  updateSsrEquipmentUi();
986  }
987 
988  QListWidget *CFlightPlanComponent::getMenuEquipmentList(QMenu *menu)
989  {
990  Q_ASSERT_X(menu->actions().size() == 1, Q_FUNC_INFO, "should only contain a single action");
991  const QWidgetAction *action = qobject_cast<QWidgetAction *>(menu->actions().at(0));
992  Q_ASSERT_X(action, Q_FUNC_INFO, "equipment menu contains invalid action item");
993  auto list = qobject_cast<QListWidget *>(action->defaultWidget());
994  Q_ASSERT_X(list, Q_FUNC_INFO, "Action widget contains invalid widget");
995  return list;
996  }
997 
998  void CFlightPlanComponent::updateSsrEquipmentUi()
999  {
1000  ui->le_SsrEquipment->setText(m_ssrEquipment.toQString());
1001  updateListSelection(m_ssrEquipmentMenu, m_ssrEquipment.enabledOptions());
1002  }
1003 
1004  void CFlightPlanComponent::updateNavComEquipmentUi()
1005  {
1006  ui->le_NavComEquipment->setText(m_navComEquipment.toQString());
1007  updateListSelection(m_navComEquipmentMenu, m_navComEquipment.enabledOptions());
1008  }
1009 
1010  void CFlightPlanComponent::updateListSelection(QMenu *menu, const QStringList &enabledOptions)
1011  {
1012  QListWidget *list = getMenuEquipmentList(menu);
1013  list->blockSignals(true);
1014  list->clearSelection();
1015  for (const auto &enabledOption : enabledOptions)
1016  {
1017  auto item = list->findItems(enabledOption, Qt::MatchExactly);
1018  Q_ASSERT_X(item.size() == 1, Q_FUNC_INFO, "Expected exactly one item per option");
1019  item[0]->setSelected(true);
1020  }
1021  list->blockSignals(false);
1022  }
1023 
1024  void CFlightPlanComponent::updateWakeTurbulenceCategorySelector(
1026  {
1027  if (wtc.isUnknown()) return; // Unknown should not be shown to the user
1028  const auto it = std::find_if(m_wakeTurbulenceCategories.cbegin(), m_wakeTurbulenceCategories.cend(),
1029  [&wtc](const WakeTurbulenceEntry &item) { return item.m_wtc == wtc; });
1030  Q_ASSERT_X(it != m_wakeTurbulenceCategories.cend(), Q_FUNC_INFO, "Invalid wake turbulence category selected");
1031  const int newIndex = static_cast<int>(std::distance(m_wakeTurbulenceCategories.cbegin(), it));
1032  ui->cb_Wtc->setCurrentIndex(newIndex);
1033  }
1034 
1035  CWakeTurbulenceCategory CFlightPlanComponent::getSelectedWakeTurbulenceCategory() const
1036  {
1037  return m_wakeTurbulenceCategories.at(ui->cb_Wtc->currentIndex()).m_wtc;
1038  }
1039 
1040  bool CFlightPlanComponent::consolidateRemarks(QStringList &remarks, const QString &newRemarks)
1041  {
1042  if (newRemarks.isEmpty()) { return false; }
1043  remarks.removeAll(newRemarks);
1044  remarks.push_front(newRemarks);
1045  return true;
1046  }
1047 
1048  void CFlightPlanComponent::remarksHistory()
1049  {
1050  const QObject *sender = QObject::sender();
1051  if (!m_fpRemarksDialog)
1052  {
1053  m_fpRemarksDialog = new CStringListDialog(this);
1054  m_fpRemarksDialog->setModal(true);
1055  }
1056  if (sender == ui->pb_Remarks) { m_fpRemarksDialog->setStrings(m_remarksHistory.getThreadLocal()); }
1057  else if (sender == ui->pb_AddRemarks)
1058  {
1059  m_fpRemarksDialog->setStrings(m_remarksHistoryAdditional.getThreadLocal());
1060  }
1061 
1062  const int rv = m_fpRemarksDialog->exec();
1063  if (rv != QDialog::Accepted) { return; }
1064  const QString remarks = m_fpRemarksDialog->getSelectedValue();
1065  if (remarks.isEmpty()) { return; }
1066 
1067  if (sender == ui->pb_Remarks) { ui->pte_Remarks->setPlainText(remarks); }
1068  else if (sender == ui->pb_AddRemarks) { ui->pte_AdditionalRemarks->setPlainText(remarks); }
1069  }
1070 
1071  void CFlightPlanComponent::initCompleters()
1072  {
1073  if (!sGui || !sGui->hasWebDataServices()) { return; }
1074  const QStringList aircraft(sGui->getWebDataServices()->getAircraftIcaoCodes().allDesignators().values());
1075  QCompleter *aircraftCompleter = new QCompleter(aircraft, this);
1076  aircraftCompleter->setMaxVisibleItems(10);
1077  const int w5chars1 = aircraftCompleter->popup()->fontMetrics().size(Qt::TextSingleLine, "FooBa").width();
1078  aircraftCompleter->popup()->setMinimumWidth(w5chars1 * 5);
1079  aircraftCompleter->setCaseSensitivity(Qt::CaseInsensitive);
1080  aircraftCompleter->setCompletionMode(QCompleter::PopupCompletion);
1081  ui->le_AircraftType->setCompleter(aircraftCompleter);
1082 
1083  const QStringList airports = sGui->getWebDataServices()->getAirports().allIcaoCodes(true);
1084  QCompleter *airportCompleter = new QCompleter(airports, this);
1085  airportCompleter->setMaxVisibleItems(10);
1086  const int w5chars2 = airportCompleter->popup()->fontMetrics().size(Qt::TextSingleLine, "FooBa").width();
1087  airportCompleter->popup()->setMinimumWidth(w5chars2 * 5);
1088  airportCompleter->setCaseSensitivity(Qt::CaseInsensitive);
1089  airportCompleter->setCompletionMode(QCompleter::PopupCompletion);
1090  ui->le_AlternateAirport->setCompleter(airportCompleter);
1091  ui->le_DestinationAirport->setCompleter(airportCompleter);
1092  ui->le_OriginAirport->setCompleter(airportCompleter);
1093  }
1094 
1095  QString CFlightPlanComponent::getDefaultFilename(bool load)
1096  {
1097  // some logic to find a useful default name
1098  const QString dir = m_directories.get().getFlightPlanDirectoryOrDefault();
1099  if (load) { return dir; }
1100 
1101  // Save file path
1102  QString name("Flight plan");
1103  if (!ui->le_DestinationAirport->text().isEmpty() && !ui->le_OriginAirport->text().isEmpty())
1104  {
1105  name += u' ' % ui->le_OriginAirport->text() % u'-' % ui->le_DestinationAirport->text();
1106  }
1107 
1108  if (!name.endsWith(CFileUtils::jsonAppendix(), Qt::CaseInsensitive)) { name += CFileUtils::jsonAppendix(); }
1109  return CFileUtils::appendFilePaths(dir, name);
1110  }
1111 
1112  void CFlightPlanComponent::syncVoiceComboBoxes(const QString &text)
1113  {
1114  const QObject *sender = QObject::sender();
1115  if (sender == ui->cb_VoiceCapabilities)
1116  {
1117  const QString ct = ui->cb_VoiceCapabilitiesFirstPage->currentText();
1118  if (stringCompare(ct, text, Qt::CaseInsensitive)) { return; }
1119  ui->cb_VoiceCapabilitiesFirstPage->setCurrentText(text);
1120  }
1121  else
1122  {
1123  const QString ct = ui->cb_VoiceCapabilities->currentText();
1124  if (!stringCompare(ct, text, Qt::CaseInsensitive))
1125  {
1126  // avoid unnecessary roundtrips
1127  ui->cb_VoiceCapabilities->setCurrentText(text);
1128  }
1129  const QString r = CFlightPlanRemarks::replaceVoiceCapabilities(
1130  CFlightPlanRemarks::textToVoiceCapabilitiesRemarks(text), ui->pte_Remarks->toPlainText());
1131  if (ui->pte_Remarks->toPlainText() != r) { ui->pte_Remarks->setPlainText(r); }
1132  }
1133  }
1134 } // namespace swift::gui::components
SWIFT_CORE_EXPORT swift::core::CApplication * sApp
Single instance of application object.
Definition: application.cpp:71
QNetworkReply * getFromNetwork(const swift::misc::network::CUrl &url, const CallbackSlot &callback, int maxRedirects=DefaultMaxRedirects)
Request to get network reply.
const context::IContextOwnAircraft * getIContextOwnAircraft() const
Direct access to contexts if a CCoreFacade has been initialized.
bool hasWebDataServices() const
Web data services available?
const context::IContextNetwork * getIContextNetwork() const
Direct access to contexts if a CCoreFacade has been initialized.
bool isShuttingDown() const
Is application shutting down?
const context::IContextSimulator * getIContextSimulator() const
Direct access to contexts if a CCoreFacade has been initialized.
CWebDataServices * getWebDataServices() const
Get the web data services.
swift::misc::aviation::CAircraftIcaoCode getAircraftIcaoCodeForDesignator(const QString &designator) const
ICAO code for designator.
swift::misc::aviation::CAirportList getAirports() const
Get airports.
bool containsAircraftIcaoDesignator(const QString &designator) const
Contains the given designator?
bool hasDbAircraftData() const
Are all DB data for an aircraft entity available?
swift::misc::aviation::CAircraftIcaoCodeList getAircraftIcaoCodes() const
Aircraft ICAO codes.
virtual void sendFlightPlan(const swift::misc::aviation::CFlightPlan &flightPlan)=0
Send flight plan.
virtual swift::misc::aviation::CFlightPlan loadFlightPlanFromNetwork(const swift::misc::aviation::CCallsign &callsign) const =0
Load flight plan (from network)
virtual bool isConnected() const =0
Network connected?
virtual bool updateSelcal(const swift::misc::aviation::CSelcal &selcal, const swift::misc::CIdentifier &originator)=0
Own SELCAL code.
virtual swift::misc::simulation::CSimulatedAircraft getOwnAircraft() const =0
Get own aircraft.
bool isSimulatorAvailable() const
Simulator avialable (driver available)?
static bool setComboBoxValueByContainingString(QComboBox *box, const QString &candidate, const QString &unspecified=QString())
Find best match in comboBox.
Definition: guiutility.cpp:328
bool showOverlayHTMLMessage(const QString &htmlMessage, std::chrono::milliseconds timeout=std::chrono::milliseconds(0))
HTML message.
bool showOverlayMessage(const swift::misc::CStatusMessage &message, std::chrono::milliseconds timeout=std::chrono::milliseconds(0))
Show single message.
void showOverlayMessages(const swift::misc::CStatusMessageList &messages, bool appendOldMessages=false, std::chrono::milliseconds timeout=std::chrono::milliseconds(0))
Show multiple messages.
void setForceSmall(bool force)
Force small (smaller layout)
void setOverlaySizeFactors(double widthFactor, double heightFactor, double middleFactor=2)
Set the size factors.
void setReducedInfo(bool reduced)
Display reduced information.
Using this class provides a QTabWidget with the overlay functionality already integrated.
const QString & getAltitudeString() const
Altitude string.
static const QStringList & getLogCategories()
Log.categories.
void fillWithFlightPlanData(const swift::misc::aviation::CFlightPlan &flightPlan)
Prefill with aircraft dara.
void valueChanged()
Value has been changed.
swift::misc::aviation::CSimBriefData getSimBriefData() const
SimBrief data.
QString getSelectedValue() const
Selected value.
void setStrings(const QStringList &strings)
Strings.
CStatusMessage setAndSave(const T &value, qint64 timestamp=0)
Write and save in the same step. Must be called from the thread in which the owner lives.
Definition: valuecache.h:417
T get() const
Get a copy of the current value.
Definition: valuecache.h:408
Directories (swift data directories)
Definition: directories.h:25
void setFlightPlanDirectory(const QString &dir)
Flight plan directory.
Definition: directories.h:51
Class for emitting a log message.
Definition: logmessage.h:27
Derived & warning(const char16_t(&format)[N])
Set the severity to warning, providing a format string.
Derived & validationError(const char16_t(&format)[N])
Set the severity to error, providing a format string, and adding the validation category.
Derived & validationWarning(const char16_t(&format)[N])
Set the severity to warning, providing a format string, and adding the validation category.
Derived & validationInfo(const char16_t(&format)[N])
Set the severity to info, providing a format string, and adding the validation category.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition: sequence.h:305
Streamable status message, e.g.
bool isSeverityInfoOrLess() const
Info or debug, no warning or error.
bool isFailure() const
Operation considered unsuccessful.
Status messages, e.g. from Core -> GUI.
bool isFailure() const
Any message is marked as failure.
bool hasWarningOrErrorMessages() const
Warning or error messages.
bool isSuccess() const
All messages are marked as success.
Wrapper around QVariant which provides transparent access to CValueObject methods of the contained ob...
Definition: variant.h:66
QString toJsonString(QJsonDocument::JsonFormat format=QJsonDocument::Indented) const
Convenience function JSON as string.
Value object for ICAO classification.
CWakeTurbulenceCategory getWtc() const
Get WTC.
bool hasValidWtc() const
Valid WTC code?
QSet< QString > allDesignators(bool noUnspecified=true) const
All ICAO codes, no duplicates.
bool hasValidIcaoCode(bool strict) const
Has valid code?
QString getIcaoCode() const
Get ICAO code.
const QString & asString() const
Get code.
QStringList allIcaoCodes(bool sorted) const
All ICAO codes.
Definition: airportlist.cpp:63
Altitude as used in aviation, can be AGL or MSL altitude.
Definition: altitude.h:52
Value object encapsulating information of a callsign.
Definition: callsign.h:30
const QString & asString() const
Get callsign (normalized)
Definition: callsign.h:96
QStringList enabledOptions() const
Get all enabled equipment codes of this object as a list.
static QStringList allEquipmentLetters()
Get all possible equipment code letters.
Flightplan-related information about an aircraft (aircraft ICAO, equipment and WTC)
CWakeTurbulenceCategory getWtc() const
Get Wake Turbulence Category.
CSsrEquipment getSsrEquipment() const
Get SSR equipment.
CComNavEquipment getComNavEquipment() const
Get COM/NAV equipment.
Value object for a flight plan.
Definition: flightplan.h:148
void setAircraftInfo(const CFlightPlanAircraftInfo &aircraftInfo)
Set information about the aircraft used in this flightplan.
Definition: flightplan.cpp:224
bool wasSentOrLoaded() const
Flight plan already sent.
Definition: flightplan.h:369
QString getFlightRulesAsString() const
Get flight rules as in FlightRules as string.
Definition: flightplan.h:360
void setFlightRule(FlightRules flightRule)
Set flight rules (VFR or IFR)
Definition: flightplan.h:285
QString getFuelTimeHourMin() const
Get amount of fuel load in time.
Definition: flightplan.h:342
QString getTakeoffTimePlannedHourMin() const
Get planned takeoff time (planned)
Definition: flightplan.cpp:257
const physical_quantities::CSpeed & getCruiseTrueAirspeed() const
Get planned cruise TAS.
Definition: flightplan.h:354
void setVoiceCapabilities(const QString &capabilities)
Set voice capabilities.
Definition: flightplan.cpp:251
void setOriginAirportIcao(const QString &originAirportIcao)
Set origin airport ICAO code.
Definition: flightplan.h:199
void setTakeoffTimePlanned(const QDateTime &takeoffTimePlanned)
Set planned takeoff time.
Definition: flightplan.cpp:226
void setCruiseTrueAirspeed(const physical_quantities::CSpeed &cruiseTrueAirspeed)
Set planned cruise TAS.
Definition: flightplan.h:279
void setAlternateAirportIcao(const QString &alternateAirportIcao)
Set alternate destination airport ICAO code.
Definition: flightplan.h:220
void setFuelTime(const physical_quantities::CTime &fuelTime)
Set amount of fuel load in time.
Definition: flightplan.h:259
const QString & getRoute() const
Get route string.
Definition: flightplan.h:363
FlightRules
Flight rules (VFR or IFR)
Definition: flightplan.h:155
FlightRules getFlightRules() const
Get flight rules as in FlightRules.
Definition: flightplan.h:357
const QString & getRemarks() const
Get remarks string.
Definition: flightplan.h:375
bool hasCallsign() const
Has callsign?
Definition: flightplan.h:306
QString getEnrouteTimeHourMin() const
Get planned enroute flight time.
Definition: flightplan.h:333
const CAirportIcaoCode & getAlternateAirportIcao() const
Get alternate destination airport ICAO code.
Definition: flightplan.h:315
const CAltitude & getCruiseAltitude() const
Cruising altitudes.
Definition: flightplan.h:348
void setRemarks(const QString &remarks)
Set remarks string (max 100 characters)
Definition: flightplan.cpp:249
void setEnrouteTime(const physical_quantities::CTime &enrouteTime)
Set planned enroute flight time.
Definition: flightplan.h:252
const CAirportIcaoCode & getOriginAirportIcao() const
Get origin airport ICAO code.
Definition: flightplan.h:309
const QDateTime whenLastSentOrLoaded() const
When last sent.
Definition: flightplan.h:366
void setRoute(const QString &route)
Set route string.
Definition: flightplan.cpp:242
void setWhenLastSentOrLoaded(const QDateTime &dateTime)
When last sent.
Definition: flightplan.h:300
const CAirportIcaoCode & getDestinationAirportIcao() const
Get destination airport ICAO code.
Definition: flightplan.h:312
void setDestinationAirportIcao(const QString &destinationAirportIcao)
Set destination airport ICAO code.
Definition: flightplan.h:208
void setCruiseAltitude(const CAltitude &cruiseAltitude)
Set planned cruise altitude.
Definition: flightplan.h:269
CFlightPlanAircraftInfo getAircraftInfo() const
Get ICAO aircraft NAV/COM equipment.
Definition: flightplan.h:384
void setCallsign(const CCallsign &callsign)
Callsign (of aircraft)
Definition: flightplan.cpp:218
network::CUrl getUrlAndUsername() const
Get URL plus username.
ICAO flightplan field 10b.
Definition: ssrequipment.h:16
QStringList enabledOptions() const
Get all enabled SSR equipment codes of this object as a list.
static QStringList allEquipmentLetters()
Get all possible SSR equipment code letters.
bool isUnknown() const
Is the wake turbulence category unknown?
bool isLoadedFromDb() const
Loaded from DB.
Definition: datastore.cpp:49
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:76
Value object encapsulating information of a location, kind of simplified CValueObject compliant versi...
Definition: url.h:27
QNetworkRequest toNetworkRequest() const
To request.
Definition: url.cpp:113
Value object encapsulating information of a user.
Definition: user.h:28
const aviation::CAirportIcaoCode & getHomeBase() const
Homebase.
Definition: user.h:134
bool hasRealName() const
Valid real name?
Definition: user.h:80
const QString & getRealName() const
Get full name.
Definition: user.h:59
const aviation::CCallsign & getCallsign() const
Get associated callsign.
Definition: user.h:140
bool hasHomeBase() const
Has home base?
Definition: user.h:92
bool hasCallsign() const
Has associated callsign?
Definition: user.h:89
void parseFromString(const QString &value)
Parse value from string.
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".
Comprehensive information of an aircraft.
const network::CUser & getPilot() const
Get user.
const aviation::CCallsign & getCallsign() const
Get callsign.
const aviation::CAircraftIcaoCode & getAircraftIcaoCode() const
Get aircraft ICAO info.
const QString & getAircraftIcaoCodeDesignator() const
Aircraft ICAO code designator.
const simulation::CAircraftModel & getModel() const
Get model (model used for mapping)
QString getCallsignAsString() const
Get callsign.
SWIFT_GUI_EXPORT swift::gui::CGuiApplication * sGui
Single instance of GUI application object.
Backend services of the swift project, like dealing with the network or the simulators.
Definition: actionbind.cpp:7
High level reusable GUI components.
Definition: aboutdialog.cpp:13
GUI related classes.
Free functions in swift::misc.
SWIFT_MISC_EXPORT bool stringCompare(const QString &c1, const QString &c2, Qt::CaseSensitivity cs)
String compare.
auto singleShot(int msec, QObject *target, F &&task)
Starts a single-shot timer which will call a task in the thread of the given object when it times out...
Definition: threadutils.h:30