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 =
560  QFileDialog::getOpenFileName(this, tr("Load flight plan"), this->getDefaultFilename(true),
561  "Flight plans (*.json *.sfp *.vfp *.xml);;swift (*.json *.txt);;SimBrief "
562  "(*.xml);;vPilot (*.vfp);;SB4 (*.sfp)");
563  if (fileName.isEmpty()) { return; }
564  CFlightPlan fp = CFlightPlan::loadFromMultipleFormats(fileName, &msgs);
565  if (!fp.hasCallsign()) { fp.setCallsign(ui->le_Callsign->text()); } // set callsign if it wasn't set
566 
567  if (msgs.isSuccess())
568  {
569  this->fillWithFlightPlanData(fp);
570  this->updateDirectorySettings(fileName);
571  }
572  else { this->showOverlayMessages(msgs, true, OverlayTimeout); }
573  }
574 
575  void CFlightPlanComponent::loadTemplateFromDisk()
576  {
577  const QFile f(this->getTemplateName());
578  if (!f.exists()) { return; }
579 
580  CStatusMessageList msgs;
581  CFlightPlan fp = CFlightPlan::loadFromMultipleFormats(f.fileName(), &msgs);
582  if (!fp.hasCallsign()) { fp.setCallsign(ui->le_Callsign->text()); } // set callsign if it wasn't set
583  if (msgs.isSuccess()) { this->fillWithFlightPlanData(fp); }
584  }
585 
586  void CFlightPlanComponent::saveToDisk()
587  {
588  CStatusMessage m;
589  const QString fileName = QFileDialog::getSaveFileName(
590  nullptr, tr("Save flight plan"), this->getDefaultFilename(false), tr("swift (*.json;*.txt)"));
591  do {
592  if (fileName.isEmpty())
593  {
594  m = CStatusMessage(this, CStatusMessage::SeverityDebug, u"Save canceled", true);
595  break;
596  }
597 
598  QFileInfo fi(fileName);
599  QDir fpDir = fi.absoluteDir();
600  if (CDirectoryUtils::isInApplicationDirectory(fpDir.absolutePath()))
601  {
602  const int ret =
603  QMessageBox::warning(this, "swift flight plan",
604  "You try to save inside the swift directory '" + fpDir.absolutePath() +
605  "'\n\nThis is not recommended!"
606  "\n\nDo you want to really do this?",
607  QMessageBox::Save | QMessageBox::Cancel);
608  if (ret != QMessageBox::Save) { return; }
609  }
610 
611  const bool ok = this->saveFPToDisk(fileName);
612  if (ok)
613  {
614  m = CStatusMessage(this, CStatusMessage::SeverityInfo, u"Written " % fileName, true);
615  this->updateDirectorySettings(fileName);
616  }
617  else { m = CStatusMessage(this, CStatusMessage::SeverityError, u"Writing " % fileName % u" failed", true); }
618  }
619  while (false);
620  if (m.isFailure()) { CLogMessage::preformatted(m); }
621  }
622 
623  bool CFlightPlanComponent::saveFPToDisk(const QString &fileName)
624  {
625  CFlightPlan fp;
626  const CStatusMessageList msgs = this->validateAndInitializeFlightPlan(fp); // get data
627  // if (msgs.hasErrorMessages()) { return false; }
628  Q_UNUSED(msgs)
629 
630  // save as CVariant format
631  const CVariant variantFp = CVariant::fromValue(fp);
632  const QString json(variantFp.toJsonString());
633  const bool ok = CFileUtils::writeStringToFile(json, fileName);
634 
635  return ok;
636  }
637 
638  void CFlightPlanComponent::saveTemplateToDisk()
639  {
640  const QString fn = this->getTemplateName();
641  const bool ok = this->saveFPToDisk(fn);
642  if (ok) { CLogMessage(this).info(u"Saved FP template '%1'") << fn; }
643  else { CLogMessage(this).warning(u"Saving FP template '%1' failed") << fn; }
644  }
645 
646  void CFlightPlanComponent::clearTemplate()
647  {
648  QFile f(this->getTemplateName());
649  if (!f.exists()) { return; }
650  const bool r = f.remove();
651  if (r) { CLogMessage(this).info(u"Deleted FP template '%1'") << f.fileName(); }
652  }
653 
654  QString CFlightPlanComponent::getTemplateName() const
655  {
656  const QString fn = CFileUtils::appendFilePathsAndFixUnc(CSwiftDirectories::normalizedApplicationDataDirectory(),
657  QStringLiteral("swiftFlightPlanTemplate.json"));
658  return fn;
659  }
660 
661  void CFlightPlanComponent::setSelcalInOwnAircraft()
662  {
663  if (!sGui || !sGui->getIContextOwnAircraft()) { return; }
664  if (!ui->frp_SelcalCode->hasValidCode()) { return; }
665  sGui->getIContextOwnAircraft()->updateSelcal(ui->frp_SelcalCode->getSelcal(), flightPlanIdentifier());
666  }
667 
668  void CFlightPlanComponent::loadFlightPlanFromNetwork()
669  {
670  if (!sGui || sGui->isShuttingDown() || !sGui->getIContextNetwork() ||
672  {
673  const CStatusMessage m =
674  CLogMessage(this).validationWarning(u"Cannot load network flight plan, network not connected");
675  this->showOverlayHTMLMessage(m, OverlayTimeout);
676  return;
677  }
678 
680  const CFlightPlan loadedPlan = sGui->getIContextNetwork()->loadFlightPlanFromNetwork(ownAircraft.getCallsign());
681  if (loadedPlan.wasSentOrLoaded())
682  {
683  const QMessageBox::StandardButton r = QMessageBox::warning(
684  this, "Loaded FP", "Override current flight plan data?", QMessageBox::Yes | QMessageBox::No);
685  if (r != QMessageBox::Yes) { return; }
686  this->fillWithFlightPlanData(loadedPlan);
687  CLogMessage(this).info(u"Updated with loaded flight plan");
688  }
689  else
690  {
691  const CStatusMessage m = CLogMessage(this).warning(u"No flight plan data in loaded plan");
692  this->showOverlayHTMLMessage(m, OverlayTimeout);
693  }
694  }
695 
696  void CFlightPlanComponent::buildRemarksString()
697  {
698  QString v = ui->cb_VoiceCapabilities->currentText().toUpper();
699  QString rem = CFlightPlanRemarks::textToVoiceCapabilitiesRemarks(v).append(" ");
700 
701  v = ui->le_AirlineOperator->text().trimmed();
702  if (!v.isEmpty()) rem.append("OPR/").append(v).append(" ");
703 
704  v = ui->le_AircraftRegistration->text().trimmed();
705  if (!v.isEmpty()) rem.append("REG/").append(v).append(" ");
706 
707  v = ui->cb_PilotRating->currentText().toUpper();
708  if (v.contains("P1")) { rem.append("PR/P1 "); }
709  else if (v.contains("P2")) { rem.append("PR/P2 "); }
710  else if (v.contains("P3")) { rem.append("PR/P3 "); }
711  else if (v.contains("P4")) { rem.append("PR/P4 "); }
712  else if (v.contains("P5")) { rem.append("PR/P5 "); }
713 
714  v = ui->cb_RequiredNavigationPerformance->currentText().toUpper();
715  if (v.contains("10")) { rem.append("RNP10 "); }
716  else if (v.contains("4")) { rem.append("RNP4 "); }
717 
718  v = ui->cb_PerformanceCategory->currentText().toUpper();
719  if (v.startsWith("A")) { rem.append("PER/A "); }
720  else if (v.startsWith("B")) { rem.append("PER/B "); }
721  else if (v.startsWith("C")) { rem.append("PER/C "); }
722  else if (v.startsWith("D")) { rem.append("PER/D "); }
723  else if (v.startsWith("E")) { rem.append("PER/E "); }
724 
725  if (ui->frp_SelcalCode->hasValidCode())
726  {
727  rem.append("SEL/").append(ui->frp_SelcalCode->getSelcalCode());
728  rem.append(" ");
729  }
730 
731  if (ui->cb_NoSidsStarts->isChecked()) { rem.append("NO SID/STAR "); }
732 
733  v = ui->pte_AdditionalRemarks->toPlainText().trimmed();
734  if (!v.isEmpty()) { rem.append(v); }
735 
736  rem = rem.simplified().trimmed();
737  ui->pte_RemarksGenerated->setPlainText(rem);
738  }
739 
740  void CFlightPlanComponent::copyRemarks(bool confirm)
741  {
742  const QString generated = ui->pte_RemarksGenerated->toPlainText().trimmed();
743  if (confirm && !this->overrideRemarks()) { return; }
744  ui->pte_Remarks->setPlainText(generated);
745  CLogMessage(this).info(u"Copied remarks");
746  }
747 
748  void CFlightPlanComponent::currentTabGenerator() { this->setCurrentWidget(ui->tb_RemarksGenerator); }
749 
750  void CFlightPlanComponent::swiftWebDataRead() { this->initCompleters(); }
751 
752  void CFlightPlanComponent::aircraftTypeChanged()
753  {
754  const CAircraftIcaoCode icao = this->getAircraftIcaoCode();
755  if (!icao.isLoadedFromDb()) { return; }
756  QPointer<CFlightPlanComponent> myself(this);
757  QTimer::singleShot(25, this, [=] {
758  if (!myself || !sGui || sGui->isShuttingDown()) { return; }
759  updateWakeTurbulenceCategorySelector(icao.getWtc());
760  });
761  }
762 
763  void CFlightPlanComponent::syncWithSimulator()
764  {
765  if (!sGui || sGui->isShuttingDown() || !sGui->getIContextOwnAircraft()) { return; }
766  const QMessageBox::StandardButton reply = QMessageBox::question(
767  this, QStringLiteral("Override aircraft data"),
768  QStringLiteral("Override aircraft ICAO data from simulator"), QMessageBox::Yes | QMessageBox::No);
769  if (reply != QMessageBox::Yes) { return; }
770 
772  this->prefillWithAircraftData(aircraft, true);
773  }
774 
775  CAircraftIcaoCode CFlightPlanComponent::getAircraftIcaoCode() const
776  {
777  const QString designator(ui->le_AircraftType->text());
778  if (!CAircraftIcaoCode::isValidDesignator(designator)) { return CAircraftIcaoCode(); }
779  if (sApp && sApp->hasWebDataServices())
780  {
781  const CAircraftIcaoCode designatorFromDb =
783  if (designatorFromDb.isLoadedFromDb()) { return designatorFromDb; }
784  }
785  return designator;
786  }
787 
788  bool CFlightPlanComponent::isVfr() const
789  {
790  const bool vfr = CFlightPlan::isVFRRules(ui->cb_FlightRule->currentText());
791  return vfr;
792  }
793 
794  CFlightPlan::FlightRules CFlightPlanComponent::getFlightRules() const
795  {
796  const CFlightPlan::FlightRules r = CFlightPlan::stringToFlightRules(ui->cb_FlightRule->currentText());
797  return r;
798  }
799 
800  bool CFlightPlanComponent::overrideRemarks()
801  {
802  if (!ui->pte_Remarks->toPlainText().trimmed().isEmpty())
803  {
804  const int reply = QMessageBox::question(this, "Remarks", "Override existing remarks?",
805  QMessageBox::Yes | QMessageBox::No);
806  if (reply != QMessageBox::Yes) { return false; }
807  }
808  return true;
809  }
810 
811  void CFlightPlanComponent::updateDirectorySettings(const QString &fileOrDirectory)
812  {
813  if (fileOrDirectory.isEmpty()) { return; }
814 
815  CDirectories swiftDirs = m_directories.get();
816  swiftDirs.setFlightPlanDirectory(CDirectories::fileNameToDirectory(fileOrDirectory));
817  CStatusMessage saveMsg = m_directories.setAndSave(swiftDirs);
818  CLogMessage::preformatted(saveMsg);
819  }
820 
821  void CFlightPlanComponent::altitudeDialog()
822  {
823  if (!m_altitudeDialog) { m_altitudeDialog = new CAltitudeDialog(this); }
824 
825  const QDialog::DialogCode ret = static_cast<QDialog::DialogCode>(m_altitudeDialog->exec());
826  if (ret != QDialog::Accepted) { return; }
827 
828  if (!m_altitudeDialog->getAltitudeString().isEmpty())
829  {
830  ui->lep_CrusingAltitude->setText(m_altitudeDialog->getAltitudeString());
831  }
832  }
833 
834  void CFlightPlanComponent::updateRemarksHistories()
835  {
836  QString r = ui->pte_Remarks->toPlainText();
837  if (!r.isEmpty())
838  {
839  QStringList h = m_remarksHistory.get();
840  if (consolidateRemarks(h, r))
841  {
842  CStatusMessage m = m_remarksHistory.setAndSave(h);
843  CLogMessage::preformatted(m);
844  }
845  }
846 
847  r = ui->pte_AdditionalRemarks->toPlainText();
848  if (!r.isEmpty())
849  {
850  QStringList h = m_remarksHistoryAdditional.get();
851  if (consolidateRemarks(h, r))
852  {
853  CStatusMessage m = m_remarksHistoryAdditional.setAndSave(h);
854  CLogMessage::preformatted(m);
855  }
856  }
857  }
858 
859  void CFlightPlanComponent::setRemarksUIValues(const QString &remarks)
860  {
861  if (remarks.isEmpty()) { return; }
862 
863  if (remarks.contains("/V"))
864  {
865  CGuiUtility::setComboBoxValueByContainingString(ui->cb_VoiceCapabilitiesFirstPage, "FULL");
866  CGuiUtility::setComboBoxValueByContainingString(ui->cb_VoiceCapabilities, "FULL");
867  }
868  else if (remarks.contains("/T"))
869  {
870  CGuiUtility::setComboBoxValueByContainingString(ui->cb_VoiceCapabilitiesFirstPage, "TEXT ONLY");
871  CGuiUtility::setComboBoxValueByContainingString(ui->cb_VoiceCapabilities, "TEXT ONLY");
872  }
873  else if (remarks.contains("/R"))
874  {
875  CGuiUtility::setComboBoxValueByContainingString(ui->cb_VoiceCapabilitiesFirstPage, "RECEIVE");
876  CGuiUtility::setComboBoxValueByContainingString(ui->cb_VoiceCapabilities, "RECEIVE");
877  }
878 
879  const int selcal = remarks.indexOf("SEL/");
880  if (selcal >= 0 && remarks.length() > selcal + 7)
881  {
882  const QString code = remarks.mid(selcal + 4, 4);
883  if (code.length() == 4) { ui->frp_SelcalCode->setSelcal(code); }
884  }
885  }
886 
887  void CFlightPlanComponent::loadFromSimBrief()
888  {
889  if (!sGui || sGui->isShuttingDown()) { return; }
890  if (!m_simBriefDialog) { m_simBriefDialog = new CSimBriefDownloadDialog(this); }
891  const int rv = m_simBriefDialog->exec();
892  if (rv != QDialog::Accepted) { return; }
893 
894  const CUrl url = m_simBriefDialog->getSimBriefData().getUrlAndUsername();
895  sApp->getFromNetwork(url.toNetworkRequest(), { this, &CFlightPlanComponent::handleSimBriefResponse });
896  }
897 
898  void CFlightPlanComponent::handleSimBriefResponse(QNetworkReply *nwReplyPtr)
899  {
900  // wrap pointer, make sure any exit cleans up reply
901  // required to use delete later as object is created in a different thread
902  QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
903  if (!sGui || sGui->isShuttingDown()) { return; }
904 
905  const QUrl url(nwReply->url());
906  const QString urlString(url.toString());
907 
908  if (nwReply->error() == QNetworkReply::NoError)
909  {
910  // const qint64 lastModified = CNetworkUtils::lastModifiedMsSinceEpoch(nwReply.data());
911  const QString simBriefFP(nwReplyPtr->readAll());
912  nwReplyPtr->close();
913  if (simBriefFP.isEmpty()) { this->showOverlayHTMLMessage("No SimBrief data from " % urlString); }
914  else
915  {
916  CFlightPlan fp = CFlightPlan::fromSimBriefFormat(simBriefFP);
917 
918  // Voice capability is not included from SimBrief -> set capability from UI
919  const QString currentVoiceCapability = ui->cb_VoiceCapabilities->currentText();
920  fp.setVoiceCapabilities(CFlightPlanRemarks::textToVoiceCapabilitiesRemarks(currentVoiceCapability));
921 
922  this->fillWithFlightPlanData(fp);
923  }
924  } // no error
925  else
926  {
927  // network error, try next URL
928  nwReply->abort();
929  }
930  }
931 
932  void CFlightPlanComponent::setupNavComContextMenu()
933  {
934  m_navComEquipmentMenu = new QMenu(ui->tb_EditNavComEquipment);
935  auto list = new QListWidget(m_navComEquipmentMenu);
936  list->setSelectionMode(QAbstractItemView::MultiSelection);
938 
939  connect(list, &QListWidget::itemSelectionChanged, this,
940  &CFlightPlanComponent::updateNavComEquipmentFromSelection);
941 
942  auto action = new QWidgetAction(ui->tb_EditNavComEquipment);
943  action->setDefaultWidget(list);
944  m_navComEquipmentMenu->addAction(action);
945 
946  updateNavComEquipmentUi();
947  }
948 
949  void CFlightPlanComponent::setupSsrContextMenu()
950  {
951  m_ssrEquipmentMenu = new QMenu(ui->tb_EditSsrEquipment);
952  auto list = new QListWidget(m_ssrEquipmentMenu);
953  list->setSelectionMode(QAbstractItemView::MultiSelection);
955 
956  connect(list, &QListWidget::itemSelectionChanged, this, &CFlightPlanComponent::updateSsrEquipmentFromSelection);
957 
958  auto action = new QWidgetAction(ui->tb_EditSsrEquipment);
959  action->setDefaultWidget(list);
960  m_ssrEquipmentMenu->addAction(action);
961 
962  updateSsrEquipmentUi();
963  }
964 
965  void CFlightPlanComponent::updateNavComEquipmentFromSelection()
966  {
967  const QListWidget *list = getMenuEquipmentList(m_navComEquipmentMenu);
968 
969  QString equipmentString;
970 
971  for (auto equipment : list->selectedItems()) { equipmentString.append(equipment->text()); }
972 
973  m_navComEquipment = CComNavEquipment(equipmentString);
974  updateNavComEquipmentUi();
975  }
976 
977  void CFlightPlanComponent::updateSsrEquipmentFromSelection()
978  {
979  const QListWidget *list = getMenuEquipmentList(m_ssrEquipmentMenu);
980 
981  QString ssrEquipmentString;
982 
983  for (auto equipment : list->selectedItems()) { ssrEquipmentString.append(equipment->text()); }
984 
985  m_ssrEquipment = CSsrEquipment(ssrEquipmentString);
986  updateSsrEquipmentUi();
987  }
988 
989  QListWidget *CFlightPlanComponent::getMenuEquipmentList(QMenu *menu)
990  {
991  Q_ASSERT_X(menu->actions().size() == 1, Q_FUNC_INFO, "should only contain a single action");
992  const QWidgetAction *action = qobject_cast<QWidgetAction *>(menu->actions().at(0));
993  Q_ASSERT_X(action, Q_FUNC_INFO, "equipment menu contains invalid action item");
994  auto list = qobject_cast<QListWidget *>(action->defaultWidget());
995  Q_ASSERT_X(list, Q_FUNC_INFO, "Action widget contains invalid widget");
996  return list;
997  }
998 
999  void CFlightPlanComponent::updateSsrEquipmentUi()
1000  {
1001  ui->le_SsrEquipment->setText(m_ssrEquipment.toQString());
1002  updateListSelection(m_ssrEquipmentMenu, m_ssrEquipment.enabledOptions());
1003  }
1004 
1005  void CFlightPlanComponent::updateNavComEquipmentUi()
1006  {
1007  ui->le_NavComEquipment->setText(m_navComEquipment.toQString());
1008  updateListSelection(m_navComEquipmentMenu, m_navComEquipment.enabledOptions());
1009  }
1010 
1011  void CFlightPlanComponent::updateListSelection(QMenu *menu, const QStringList &enabledOptions)
1012  {
1013  QListWidget *list = getMenuEquipmentList(menu);
1014  list->blockSignals(true);
1015  list->clearSelection();
1016  for (const auto &enabledOption : enabledOptions)
1017  {
1018  auto item = list->findItems(enabledOption, Qt::MatchExactly);
1019  Q_ASSERT_X(item.size() == 1, Q_FUNC_INFO, "Expected exactly one item per option");
1020  item[0]->setSelected(true);
1021  }
1022  list->blockSignals(false);
1023  }
1024 
1025  void CFlightPlanComponent::updateWakeTurbulenceCategorySelector(
1027  {
1028  if (wtc.isUnknown()) return; // Unknown should not be shown to the user
1029  const auto it = std::find_if(m_wakeTurbulenceCategories.cbegin(), m_wakeTurbulenceCategories.cend(),
1030  [&wtc](const WakeTurbulenceEntry &item) { return item.m_wtc == wtc; });
1031  Q_ASSERT_X(it != m_wakeTurbulenceCategories.cend(), Q_FUNC_INFO, "Invalid wake turbulence category selected");
1032  const int newIndex = static_cast<int>(std::distance(m_wakeTurbulenceCategories.cbegin(), it));
1033  ui->cb_Wtc->setCurrentIndex(newIndex);
1034  }
1035 
1036  CWakeTurbulenceCategory CFlightPlanComponent::getSelectedWakeTurbulenceCategory() const
1037  {
1038  return m_wakeTurbulenceCategories.at(ui->cb_Wtc->currentIndex()).m_wtc;
1039  }
1040 
1041  bool CFlightPlanComponent::consolidateRemarks(QStringList &remarks, const QString &newRemarks)
1042  {
1043  if (newRemarks.isEmpty()) { return false; }
1044  remarks.removeAll(newRemarks);
1045  remarks.push_front(newRemarks);
1046  return true;
1047  }
1048 
1049  void CFlightPlanComponent::remarksHistory()
1050  {
1051  const QObject *sender = QObject::sender();
1052  if (!m_fpRemarksDialog)
1053  {
1054  m_fpRemarksDialog = new CStringListDialog(this);
1055  m_fpRemarksDialog->setModal(true);
1056  }
1057  if (sender == ui->pb_Remarks) { m_fpRemarksDialog->setStrings(m_remarksHistory.getThreadLocal()); }
1058  else if (sender == ui->pb_AddRemarks)
1059  {
1060  m_fpRemarksDialog->setStrings(m_remarksHistoryAdditional.getThreadLocal());
1061  }
1062 
1063  const int rv = m_fpRemarksDialog->exec();
1064  if (rv != QDialog::Accepted) { return; }
1065  const QString remarks = m_fpRemarksDialog->getSelectedValue();
1066  if (remarks.isEmpty()) { return; }
1067 
1068  if (sender == ui->pb_Remarks) { ui->pte_Remarks->setPlainText(remarks); }
1069  else if (sender == ui->pb_AddRemarks) { ui->pte_AdditionalRemarks->setPlainText(remarks); }
1070  }
1071 
1072  void CFlightPlanComponent::initCompleters()
1073  {
1074  if (!sGui || !sGui->hasWebDataServices()) { return; }
1075  const QStringList aircraft(sGui->getWebDataServices()->getAircraftIcaoCodes().allDesignators().values());
1076  QCompleter *aircraftCompleter = new QCompleter(aircraft, this);
1077  aircraftCompleter->setMaxVisibleItems(10);
1078  const int w5chars1 = aircraftCompleter->popup()->fontMetrics().size(Qt::TextSingleLine, "FooBa").width();
1079  aircraftCompleter->popup()->setMinimumWidth(w5chars1 * 5);
1080  aircraftCompleter->setCaseSensitivity(Qt::CaseInsensitive);
1081  aircraftCompleter->setCompletionMode(QCompleter::PopupCompletion);
1082  ui->le_AircraftType->setCompleter(aircraftCompleter);
1083 
1084  const QStringList airports = sGui->getWebDataServices()->getAirports().allIcaoCodes(true);
1085  QCompleter *airportCompleter = new QCompleter(airports, this);
1086  airportCompleter->setMaxVisibleItems(10);
1087  const int w5chars2 = airportCompleter->popup()->fontMetrics().size(Qt::TextSingleLine, "FooBa").width();
1088  airportCompleter->popup()->setMinimumWidth(w5chars2 * 5);
1089  airportCompleter->setCaseSensitivity(Qt::CaseInsensitive);
1090  airportCompleter->setCompletionMode(QCompleter::PopupCompletion);
1091  ui->le_AlternateAirport->setCompleter(airportCompleter);
1092  ui->le_DestinationAirport->setCompleter(airportCompleter);
1093  ui->le_OriginAirport->setCompleter(airportCompleter);
1094  }
1095 
1096  QString CFlightPlanComponent::getDefaultFilename(bool load)
1097  {
1098  // some logic to find a useful default name
1099  const QString dir = m_directories.get().getFlightPlanDirectoryOrDefault();
1100  if (load) { return dir; }
1101 
1102  // Save file path
1103  QString name("Flight plan");
1104  if (!ui->le_DestinationAirport->text().isEmpty() && !ui->le_OriginAirport->text().isEmpty())
1105  {
1106  name += u' ' % ui->le_OriginAirport->text() % u'-' % ui->le_DestinationAirport->text();
1107  }
1108 
1109  if (!name.endsWith(CFileUtils::jsonAppendix(), Qt::CaseInsensitive)) { name += CFileUtils::jsonAppendix(); }
1110  return CFileUtils::appendFilePaths(dir, name);
1111  }
1112 
1113  void CFlightPlanComponent::syncVoiceComboBoxes(const QString &text)
1114  {
1115  const QObject *sender = QObject::sender();
1116  if (sender == ui->cb_VoiceCapabilities)
1117  {
1118  const QString ct = ui->cb_VoiceCapabilitiesFirstPage->currentText();
1119  if (stringCompare(ct, text, Qt::CaseInsensitive)) { return; }
1120  ui->cb_VoiceCapabilitiesFirstPage->setCurrentText(text);
1121  }
1122  else
1123  {
1124  const QString ct = ui->cb_VoiceCapabilities->currentText();
1125  if (!stringCompare(ct, text, Qt::CaseInsensitive))
1126  {
1127  // avoid unnecessary roundtrips
1128  ui->cb_VoiceCapabilities->setCurrentText(text);
1129  }
1130  const QString r = CFlightPlanRemarks::replaceVoiceCapabilities(
1131  CFlightPlanRemarks::textToVoiceCapabilitiesRemarks(text), ui->pte_Remarks->toPlainText());
1132  if (ui->pte_Remarks->toPlainText() != r) { ui->pte_Remarks->setPlainText(r); }
1133  }
1134  }
1135 } // 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:74
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