newflightdialog.cpp 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
  1. /*
  2. *openPilotLog - A FOSS Pilot Logbook Application
  3. *Copyright (C) 2020-2023 Felix Turowsky
  4. *
  5. *This program is free software: you can redistribute it and/or modify
  6. *it under the terms of the GNU General Public License as published by
  7. *the Free Software Foundation, either version 3 of the License, or
  8. *(at your option) any later version.
  9. *
  10. *This program is distributed in the hope that it will be useful,
  11. *but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. *GNU General Public License for more details.
  14. *
  15. *You should have received a copy of the GNU General Public License
  16. *along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. #include "newflightdialog.h"
  19. #include "QtWidgets/qcalendarwidget.h"
  20. #include "src/classes/time.h"
  21. #include "src/database/database.h"
  22. #include "src/database/databasecache.h"
  23. #include "src/gui/dialogues/newairportdialog.h"
  24. #include "src/gui/verification/airportinput.h"
  25. #include "src/gui/verification/completerprovider.h"
  26. #include "src/gui/verification/pilotinput.h"
  27. #include "src/gui/verification/tailinput.h"
  28. #include "src/gui/verification/timeinput.h"
  29. #include "ui_newflightdialog.h"
  30. #include "src/opl.h"
  31. #include "src/functions/datetime.h"
  32. #include "src/classes/settings.h"
  33. #include "src/classes/date.h"
  34. #include "src/functions/calc.h"
  35. #include "src/gui/dialogues/newtaildialog.h"
  36. #include "src/gui/dialogues/newpilotdialog.h"
  37. #include <QDateTime>
  38. #include <QCompleter>
  39. #include <QKeyEvent>
  40. NewFlightDialog::NewFlightDialog(QWidget *parent)
  41. : EntryEditDialog(parent),
  42. ui(new Ui::NewFlightDialog)
  43. {
  44. init();
  45. setPilotFunction();
  46. ui->doftLineEdit->setText(OPL::Date::today(m_format).toString());
  47. emit ui->doftLineEdit->editingFinished();
  48. }
  49. NewFlightDialog::NewFlightDialog(int row_id, QWidget *parent)
  50. : EntryEditDialog(parent),
  51. ui(new Ui::NewFlightDialog)
  52. {
  53. init();
  54. flightEntry = DB->getFlightEntry(row_id);
  55. fillWithEntryData();
  56. }
  57. NewFlightDialog::~NewFlightDialog()
  58. {
  59. delete ui;
  60. }
  61. void NewFlightDialog::setPilotFunction()
  62. {
  63. const QString &self = DBCache->getPilotNamesMap().value(1);
  64. if(Settings::getPilotFunction() == OPL::PilotFunction::PIC){
  65. ui->picNameLineEdit->setText(self);
  66. ui->functionComboBox->setCurrentIndex(0);
  67. }
  68. if (Settings::getPilotFunction() == OPL::PilotFunction::SIC) {
  69. ui->sicNameLineEdit->setText(self);
  70. ui->functionComboBox->setCurrentIndex(2);
  71. }
  72. ui->pilotFlyingCheckBox->setCheckState(Qt::Checked);
  73. }
  74. void NewFlightDialog::init()
  75. {
  76. // Setup UI
  77. ui->setupUi(this);
  78. // Initialise line edit lists
  79. auto time_line_edits = new QList<QLineEdit*>{ui->tofbTimeLineEdit, ui->tonbTimeLineEdit};
  80. timeLineEdits = time_line_edits;
  81. auto location_line_edits = new QList<QLineEdit*>{ui->deptLocationLineEdit, ui->destLocationLineEdit};
  82. locationLineEdits = location_line_edits;
  83. auto pilot_name_line_edits = new QList<QLineEdit*>{ui->picNameLineEdit, ui->sicNameLineEdit, ui->thirdPilotNameLineEdit};
  84. pilotNameLineEdits = pilot_name_line_edits;
  85. auto mandatory_line_edits = new QList<QLineEdit*>{ui->doftLineEdit, ui->deptLocationLineEdit, ui->destLocationLineEdit,
  86. ui->tofbTimeLineEdit, ui->tonbTimeLineEdit,
  87. ui->picNameLineEdit, ui->acftLineEdit};
  88. // {doft = 0, dept = 1, dest = 2, tofb = 3, tonb = 4, pic = 5, acft = 6}
  89. mandatoryLineEdits = mandatory_line_edits;
  90. for (const auto& line_edit : *mandatoryLineEdits)
  91. line_edit->installEventFilter(this);
  92. // Approach Combo Box and Function Combo Box
  93. OPL::GLOBALS->loadApproachTypes(ui->approachComboBox);
  94. OPL::GLOBALS->loadPilotFunctios(ui->functionComboBox);
  95. // allocate a widget for date selection
  96. calendar = new QCalendarWidget(this);
  97. calendar->setVisible(false);
  98. calendar->setWindowFlags(Qt::Dialog); // pop-up calendar
  99. setupRawInputValidation();
  100. setupSignalsAndSlots();
  101. readSettings();
  102. }
  103. /*!
  104. * \brief NewFlightDialog::setupRawInputValidation outfits the line edits with QRegularExpresionValidators and QCompleters
  105. */
  106. void NewFlightDialog::setupRawInputValidation()
  107. {
  108. // Time Line Edits
  109. for (const auto& line_edit : *timeLineEdits) {
  110. const auto validator = new QRegularExpressionValidator(OPL::RegEx::RX_TIME_ENTRY, line_edit);
  111. line_edit->setValidator(validator);
  112. }
  113. // Location Line Edits
  114. for (const auto& line_edit : *locationLineEdits) {
  115. const auto validator = new QRegularExpressionValidator(OPL::RegEx::RX_AIRPORT_CODE, line_edit);
  116. line_edit->setValidator(validator);
  117. line_edit->setCompleter(QCompleterProvider.getCompleter(CompleterProvider::Airports));
  118. }
  119. // Name Line Edits
  120. for (const auto& line_edit : *pilotNameLineEdits) {
  121. line_edit->setCompleter(QCompleterProvider.getCompleter(CompleterProvider::Pilots));
  122. }
  123. // Acft Line Edit
  124. ui->acftLineEdit->setCompleter(QCompleterProvider.getCompleter(CompleterProvider::Tails));
  125. }
  126. void NewFlightDialog::setupSignalsAndSlots()
  127. {
  128. for (const auto& line_edit : *timeLineEdits)
  129. QObject::connect(line_edit, &QLineEdit::editingFinished,
  130. this, &NewFlightDialog::onTimeLineEdit_editingFinished);
  131. // Change text to upper case for location and acft line edits
  132. QObject::connect(ui->acftLineEdit, &QLineEdit::textChanged,
  133. this, &NewFlightDialog::toUpper);
  134. for (const auto& line_edit : *locationLineEdits) {
  135. QObject::connect(line_edit, &QLineEdit::textChanged,
  136. this, &NewFlightDialog::toUpper);
  137. QObject::connect(line_edit, &QLineEdit::editingFinished,
  138. this, &NewFlightDialog::onLocationLineEdit_editingFinished);
  139. }
  140. for (const auto& line_edit : *pilotNameLineEdits)
  141. QObject::connect(line_edit, &QLineEdit::editingFinished,
  142. this, &NewFlightDialog::onPilotNameLineEdit_editingFinshed);
  143. QObject::connect(calendar, &QCalendarWidget::selectionChanged,
  144. this, &NewFlightDialog::calendarDateSelected);
  145. QObject::connect(calendar, &QCalendarWidget::clicked,
  146. this, &NewFlightDialog::calendarDateSelected);
  147. }
  148. /*!
  149. * \brief NewFlightDialog::eventFilter invalidates mandatory line edits on focus in.
  150. * \details Some of the QLineEdits have validators set that provide raw input validation. These validators have the side effect
  151. * that if an input does not meet the raw input validation criteria, onEditingFinished() is not emitted when the line edit loses
  152. * focus. This could lead to a line edit that previously had good input to be changed to bad input without the validation bit
  153. * in validationState being unset, because the second step of input validation is only triggered when editingFinished() is emitted.
  154. */
  155. bool NewFlightDialog::eventFilter(QObject *object, QEvent *event)
  156. {
  157. auto line_edit = qobject_cast<QLineEdit*>(object);
  158. if (line_edit != nullptr) {
  159. if (mandatoryLineEdits->contains(line_edit) && event->type() == QEvent::FocusIn) {
  160. // set verification bit to false when entering a mandatory line edit
  161. validationState.invalidate(mandatoryLineEdits->indexOf(line_edit));
  162. return false;
  163. }
  164. }
  165. return false;
  166. }
  167. /*!
  168. * \brief NewFlightDialog::readSettings Reads user-defined settings from an INI file.
  169. */
  170. void NewFlightDialog::readSettings()
  171. {
  172. ui->functionComboBox->setCurrentIndex(static_cast<int>(Settings::getPilotFunction()));
  173. ui->approachComboBox->setCurrentText(Settings::getApproachType());
  174. ui->flightRulesComboBox->setCurrentIndex(Settings::getLogIfr());
  175. ui->flightNumberLineEdit->setText(Settings::getFlightNumberPrefix());
  176. m_format = Settings::getDisplayFormat();
  177. }
  178. /*!
  179. * \brief NewFlightDialog::fillWithEntryData Takes an existing flight entry and fills the UI with its data
  180. * to enable editing an existing flight.
  181. */
  182. void NewFlightDialog::fillWithEntryData()
  183. {
  184. DEB << "Restoring Flight: ";
  185. DEB << flightEntry;
  186. using namespace OPL;
  187. const auto &flight_data = flightEntry.getData();
  188. // Date of Flight
  189. const QDate date = QDate::fromJulianDay(flight_data.value(FlightEntry::DOFT).toInt());
  190. calendar->setSelectedDate(date);
  191. ui->doftLineEdit->setText(Date(date, m_format).toString());
  192. // Location
  193. ui->deptLocationLineEdit->setText(flight_data.value(OPL::FlightEntry::DEPT).toString());
  194. ui->destLocationLineEdit->setText(flight_data.value(OPL::FlightEntry::DEST).toString());
  195. // Times
  196. ui->tofbTimeLineEdit->setText(OPL::Time(flight_data.value(OPL::FlightEntry::TOFB).toInt(), m_format).toString());
  197. ui->tonbTimeLineEdit->setText(OPL::Time(flight_data.value(OPL::FlightEntry::TONB).toInt(), m_format).toString());
  198. ui->acftLineEdit->setText(DBCache->getTailsMap().value(flight_data.value(OPL::FlightEntry::ACFT).toInt()));
  199. ui->picNameLineEdit->setText(DBCache->getPilotNamesMap().value(flight_data.value(OPL::FlightEntry::PIC).toInt()));
  200. ui->sicNameLineEdit->setText(DBCache->getPilotNamesMap().value(flight_data.value(OPL::FlightEntry::SECONDPILOT).toInt()));
  201. ui->thirdPilotNameLineEdit->setText(DBCache->getPilotNamesMap().value(flight_data.value(OPL::FlightEntry::THIRDPILOT).toInt()));
  202. //Function
  203. for (int i = 0; i < 5; i++) { // QHash::iterator not guarenteed to be in ordetr
  204. if(flight_data.value(pilotFuncionsMap.value(i)).toInt() != 0)
  205. ui->functionComboBox->setCurrentIndex(i);
  206. }
  207. // Approach ComboBox
  208. const QString app = flight_data.value(OPL::FlightEntry::APPROACHTYPE).toString();
  209. if(app != QString()) {
  210. ui->approachComboBox->setCurrentText(app);
  211. }
  212. // Flight Rules, check if tIFR > 0
  213. bool time_ifr = flight_data.value(OPL::FlightEntry::TIFR).toBool();
  214. ui->flightRulesComboBox->setCurrentIndex(time_ifr);
  215. // Take-Off and Landing
  216. int takeOffCount = flight_data.value(OPL::FlightEntry::TODAY).toInt()
  217. + flight_data.value(OPL::FlightEntry::TONIGHT).toInt();
  218. int landingCount = flight_data.value(OPL::FlightEntry::LDGDAY).toInt()
  219. + flight_data.value(OPL::FlightEntry::LDGNIGHT).toInt();
  220. ui->takeOffSpinBox->setValue(takeOffCount);
  221. ui->landingSpinBox->setValue(landingCount);
  222. ui->pilotFlyingCheckBox->setChecked(flight_data.value(OPL::FlightEntry::PILOTFLYING).toBool());
  223. // Remarks and Flight Number
  224. ui->remarksLineEdit->setText(flight_data.value(OPL::FlightEntry::REMARKS).toString());
  225. ui->flightNumberLineEdit->setText(flight_data.value(OPL::FlightEntry::FLIGHTNUMBER).toString());
  226. // re-trigger input verification
  227. for(const auto &line_edit : *mandatoryLineEdits)
  228. emit line_edit->editingFinished();
  229. }
  230. bool NewFlightDialog::verifyUserInput(QLineEdit *line_edit, const UserInput &input)
  231. {
  232. DEB << "Verifying user input: " << line_edit->text();
  233. if(!input.isValid()) {
  234. QString fixed = input.fixup();
  235. if(fixed == QString()) {
  236. onBadInputReceived(line_edit);
  237. return false;
  238. } else {
  239. line_edit->setText(fixed);
  240. onGoodInputReceived(line_edit);
  241. return true;
  242. }
  243. }
  244. onGoodInputReceived(line_edit);
  245. return true;
  246. }
  247. void NewFlightDialog::onGoodInputReceived(QLineEdit *line_edit)
  248. {
  249. DEB << line_edit->objectName() << " - Good input received - " << line_edit->text();
  250. line_edit->setStyleSheet(QString());
  251. if (mandatoryLineEdits->contains(line_edit))
  252. validationState.validate(mandatoryLineEdits->indexOf(line_edit));
  253. if (validationState.timesValid()) {
  254. const OPL::Time tofb = OPL::Time::fromString(ui->tofbTimeLineEdit->text(), m_format);
  255. const OPL::Time tonb = OPL::Time::fromString(ui->tonbTimeLineEdit->text(), m_format);
  256. const OPL::Time tblk = OPL::Time::blockTime(tofb, tonb);
  257. ui->tblkDisplayLabel->setText(tblk.toString());
  258. }
  259. }
  260. void NewFlightDialog::onBadInputReceived(QLineEdit *line_edit)
  261. {
  262. DEB << line_edit->objectName() << " - Bad input received - " << line_edit->text();
  263. line_edit->setStyleSheet(OPL::CssStyles::RED_BORDER);
  264. line_edit->setText(QString());
  265. if (mandatoryLineEdits->contains(line_edit))
  266. validationState.invalidate(mandatoryLineEdits->indexOf(line_edit));
  267. validationState.printValidationStatus();
  268. }
  269. bool NewFlightDialog::addNewDatabaseElement(QLineEdit *parent, OPL::DbTable table)
  270. {
  271. QDialog *dialog = nullptr;
  272. if(userWantsToAddNewEntry(table)) {
  273. switch (table) {
  274. case OPL::DbTable::Pilots:
  275. dialog = new NewPilotDialog(parent->text(), this);
  276. break;
  277. case OPL::DbTable::Tails:
  278. dialog = new NewTailDialog(ui->acftLineEdit->text(), this);
  279. break;
  280. case OPL::DbTable::Airports:
  281. dialog = new NewAirportDialog(this);
  282. break;
  283. default:
  284. return false;
  285. break;
  286. }
  287. } else
  288. return false;
  289. // execute the dialog and check for success. Set the line edit to the newly created entry.
  290. if(dialog->exec() == QDialog::Accepted) {
  291. delete dialog;
  292. int latestEntry = DB->getLastEntry(table);
  293. switch (table) {
  294. case OPL::DbTable::Pilots:
  295. parent->setText(DBCache->getPilotNamesMap().value(latestEntry));
  296. break;
  297. case OPL::DbTable::Tails:
  298. parent->setText(DBCache->getTailsMap().value(latestEntry));
  299. break;
  300. case OPL::DbTable::Airports:
  301. parent->setText(DBCache->getAirportsMapICAO().value(latestEntry));
  302. break;
  303. default:
  304. return false;
  305. break;
  306. }
  307. } else {
  308. delete dialog;
  309. return false;
  310. }
  311. // re-emit editing finished to trigger input validation
  312. emit parent->editingFinished();
  313. return true;
  314. }
  315. bool NewFlightDialog::userWantsToAddNewEntry(OPL::DbTable table)
  316. {
  317. QMessageBox::StandardButton reply;
  318. switch (table) {
  319. case OPL::DbTable::Pilots:
  320. reply = QMessageBox::question(this, tr("No Pilot found"),
  321. tr("No pilot found.<br>Please enter the Name as"
  322. "<br><br><center><b>Lastname, Firstname</b></center><br><br>"
  323. "If this is the first time you log a flight with this pilot, "
  324. "you have to add the pilot to the database first."
  325. "<br><br>Would you like to add a new pilot to the database?"),
  326. QMessageBox::Yes|QMessageBox::No, QMessageBox::StandardButton::Yes);
  327. break;
  328. case OPL::DbTable::Tails:
  329. reply = QMessageBox::question(this, tr("No Aircraft found"),
  330. tr("No aircraft with this registration found.<br>"
  331. "If this is the first time you log a flight with this aircraft, "
  332. "you have to add the registration to the database first."
  333. "<br><br>Would you like to add a new aircraft to the database?"),
  334. QMessageBox::Yes|QMessageBox::No, QMessageBox::StandardButton::Yes);
  335. break;
  336. case OPL::DbTable::Airports:
  337. reply = QMessageBox::question(this, tr("No Airport found"),
  338. tr("No Airport with this identifier found.<br>"
  339. "If this is the first time you log a flight to this airport, "
  340. "you have to add the airport to the database first."
  341. "<br><br>Would you like to add a new airport to the database?"),
  342. QMessageBox::Yes|QMessageBox::No, QMessageBox::StandardButton::Yes);
  343. break;
  344. default:
  345. reply = QMessageBox::No;
  346. break;
  347. }
  348. return reply == QMessageBox::Yes;
  349. }
  350. void NewFlightDialog::informUserAboutMissingItems()
  351. {
  352. QString missing_items;
  353. for (int i=0; i < mandatoryLineEdits->size(); i++) {
  354. if (!validationState.validAt(i)){
  355. missing_items.append(validationItemsDisplayNames.value(static_cast<ValidationState::ValidationItem>(i)) + QStringLiteral("<br>"));
  356. mandatoryLineEdits->at(i)->setStyleSheet(OPL::CssStyles::RED_BORDER);
  357. }
  358. }
  359. if(missing_items.isEmpty()) {
  360. return;
  361. }
  362. INFO(tr("Not all mandatory entries are valid.<br>"
  363. "The following item(s) are empty or invalid:"
  364. "<br><br><center><b>%1</b></center><br>"
  365. "Please go back and fill in the required data."
  366. ).arg(missing_items));
  367. }
  368. /*!
  369. * \brief NewFlightDialog::prepareFlightEntryData reads the user input from the UI and converts it into
  370. * the database format.
  371. * \return OPL::RowData_T a map containing a row ready for database submission
  372. */
  373. OPL::RowData_T NewFlightDialog::prepareFlightEntryData()
  374. {
  375. if(!validationState.allValid())
  376. return {};
  377. // prepare the entry data
  378. OPL::RowData_T new_data;
  379. // Calculate Block and Night Time
  380. const OPL::Time tofb = OPL::Time::fromString(ui->tofbTimeLineEdit->text(), m_format);
  381. const OPL::Time tonb = OPL::Time::fromString(ui->tonbTimeLineEdit->text(), m_format);
  382. const int block_minutes = OPL::Time::blockMinutes(tofb, tonb);
  383. const QDateTime departure_date_time = OPL::DateTime::fromString(ui->doftLineEdit->text() + ui->tofbTimeLineEdit->text());
  384. const auto night_time_data = OPL::Calc::NightTimeValues(ui->deptLocationLineEdit->text(), ui->destLocationLineEdit->text(),
  385. departure_date_time, block_minutes, Settings::getNightAngle());
  386. // Mandatory data
  387. new_data.insert(OPL::FlightEntry::DOFT, QDate::fromString(ui->doftLineEdit->text(), Qt::ISODate).toJulianDay());
  388. new_data.insert(OPL::FlightEntry::DEPT, ui->deptLocationLineEdit->text());
  389. new_data.insert(OPL::FlightEntry::TOFB, tofb.toMinutes());
  390. new_data.insert(OPL::FlightEntry::DEST, ui->destLocationLineEdit->text());
  391. new_data.insert(OPL::FlightEntry::TONB, tonb.toMinutes());
  392. new_data.insert(OPL::FlightEntry::TBLK, block_minutes);
  393. // Night
  394. new_data.insert(OPL::FlightEntry::TNIGHT, night_time_data.nightMinutes);
  395. // Aircraft
  396. int acft_id = DBCache->getTailsMap().key(ui->acftLineEdit->text());
  397. new_data.insert(OPL::FlightEntry::ACFT, acft_id);
  398. const OPL::TailEntry acft_data = DB->getTailEntry(acft_id);
  399. bool multi_pilot = acft_data.getData().value(OPL::TailEntry::MULTI_PILOT).toBool();
  400. bool multi_engine = acft_data.getData().value(OPL::TailEntry::MULTI_ENGINE).toBool();
  401. if (multi_pilot) {
  402. new_data.insert(OPL::FlightEntry::TMP, block_minutes);
  403. new_data.insert(OPL::FlightEntry::TSPSE, QString());
  404. new_data.insert(OPL::FlightEntry::TSPME, QString());
  405. } else if (!multi_pilot && !multi_engine) {
  406. new_data.insert(OPL::FlightEntry::TMP, QString());
  407. new_data.insert(OPL::FlightEntry::TSPSE, block_minutes);
  408. new_data.insert(OPL::FlightEntry::TSPME, QString());
  409. } else if (!multi_pilot && multi_engine) {
  410. new_data.insert(OPL::FlightEntry::TMP, QString());
  411. new_data.insert(OPL::FlightEntry::TSPSE, QString());
  412. new_data.insert(OPL::FlightEntry::TSPME, block_minutes);
  413. }
  414. // Pilots
  415. new_data.insert(OPL::FlightEntry::PIC, DBCache->getPilotNamesMap().key(ui->picNameLineEdit->text()));
  416. new_data.insert(OPL::FlightEntry::SECONDPILOT, DBCache->getPilotNamesMap().key(ui->sicNameLineEdit->text()));
  417. new_data.insert(OPL::FlightEntry::THIRDPILOT, DBCache->getPilotNamesMap().key(ui->thirdPilotNameLineEdit->text()));
  418. // IFR time
  419. if (ui->flightRulesComboBox->currentIndex() > 0) {
  420. new_data.insert(OPL::FlightEntry::TIFR, block_minutes);
  421. } else {
  422. new_data.insert(OPL::FlightEntry::TIFR, QString());
  423. }
  424. // Function Times
  425. QStringList function_times = {
  426. OPL::FlightEntry::TPIC,
  427. OPL::FlightEntry::TPICUS,
  428. OPL::FlightEntry::TSIC,
  429. OPL::FlightEntry::TDUAL,
  430. OPL::FlightEntry::TFI,
  431. };
  432. // Determine function times, zero out all values except one (use QString() iso 0 for correct handling of NULL in DB
  433. // Log Instructor Time as PIC time as well
  434. const int& function_index = ui->functionComboBox->currentIndex();
  435. switch (function_index) {
  436. case 4:
  437. LOG << "Function FI";
  438. for (int i = 0; i < 5; i++){
  439. if(i == 0 || i == 4)
  440. new_data.insert(function_times[i], block_minutes);
  441. else
  442. new_data.insert(function_times[i], QString());
  443. }
  444. break;
  445. default:
  446. for (int i = 0; i < 5; i++){
  447. if(i == function_index)
  448. new_data.insert(function_times[i], block_minutes);
  449. else
  450. new_data.insert(function_times[i], QString());
  451. }
  452. break;
  453. }
  454. // Take-Off and Landing
  455. int toDay = night_time_data.takeOffNight ? 0 : ui->takeOffSpinBox->value();
  456. int toNight = night_time_data.takeOffNight ? ui->takeOffSpinBox->value() : 0;
  457. int ldgDay = night_time_data.landingNight ? 0 : ui->landingSpinBox->value();
  458. int ldgNight = night_time_data.landingNight ? ui->landingSpinBox->value() : 0;
  459. new_data.insert(OPL::FlightEntry::TODAY, toDay);
  460. new_data.insert(OPL::FlightEntry::TONIGHT, toNight);
  461. new_data.insert(OPL::FlightEntry::LDGDAY, ldgDay);
  462. new_data.insert(OPL::FlightEntry::LDGNIGHT, ldgNight);
  463. if (ui->approachComboBox->currentText() == CAT_3) // ILS CAT III
  464. new_data.insert(OPL::FlightEntry::AUTOLAND, ui->landingSpinBox->value());
  465. // Pilot flying / Pilot monitoring
  466. bool isPilotFlying = toDay + toNight + ldgDay + ldgNight;
  467. new_data.insert(OPL::FlightEntry::PILOTFLYING, isPilotFlying);
  468. // Additional Data
  469. new_data.insert(OPL::FlightEntry::APPROACHTYPE, ui->approachComboBox->currentText());
  470. new_data.insert(OPL::FlightEntry::FLIGHTNUMBER, ui->flightNumberLineEdit->text());
  471. new_data.insert(OPL::FlightEntry::REMARKS, ui->remarksLineEdit->text());
  472. return new_data;
  473. }
  474. /*!
  475. * \brief NewFlightDialog::toUpper - changes inserted text to upper case for IATA/ICAO airport codes and registrations.
  476. */
  477. void NewFlightDialog::toUpper(const QString &text)
  478. {
  479. const auto line_edit = this->findChild<QLineEdit*>(sender()->objectName());
  480. {
  481. const QSignalBlocker blocker(line_edit);
  482. line_edit->setText(text.toUpper());
  483. }
  484. }
  485. void NewFlightDialog::onTimeLineEdit_editingFinished()
  486. {
  487. const auto line_edit = this->findChild<QLineEdit*>(sender()->objectName());
  488. if(!verifyUserInput(line_edit, TimeInput(line_edit->text(), m_format))) {
  489. onBadInputReceived(line_edit);
  490. }
  491. }
  492. void NewFlightDialog::onPilotNameLineEdit_editingFinshed()
  493. {
  494. auto line_edit = this->findChild<QLineEdit*>(sender()->objectName());
  495. if(line_edit->text() == QString())
  496. return;
  497. // create a copy to refill line edit and pass through to creation dialog if verification fails
  498. QString userInput = line_edit->text();
  499. if(!verifyUserInput(line_edit, PilotInput(line_edit->text()))) {
  500. {
  501. QSignalBlocker blocker(line_edit);
  502. line_edit->setText(userInput);
  503. }
  504. if(!addNewDatabaseElement(line_edit, OPL::DbTable::Pilots))
  505. onBadInputReceived(line_edit);
  506. }
  507. }
  508. void NewFlightDialog::onLocationLineEdit_editingFinished()
  509. {
  510. const QString& line_edit_name = sender()->objectName();
  511. const auto line_edit = this->findChild<QLineEdit*>(line_edit_name);
  512. QLabel* name_label;
  513. if (line_edit_name.contains(QLatin1String("dept")))
  514. name_label = ui->deptNameLabel;
  515. else
  516. name_label = ui->destNameLabel;
  517. if(verifyUserInput(line_edit, AirportInput(line_edit->text())) ) {
  518. // Match ICAO code with Airport Name and display on label
  519. name_label->setText(DBCache->getAirportsMapNames().value(
  520. DBCache->getAirportsMapICAO().key(
  521. line_edit->text())));
  522. } else {
  523. name_label->setText("Unknown Airport");
  524. addNewDatabaseElement(line_edit, OPL::DbTable::Airports);
  525. }
  526. }
  527. void NewFlightDialog::on_acftLineEdit_editingFinished()
  528. {
  529. const auto line_edit = ui->acftLineEdit;
  530. if(line_edit->text().isEmpty()){
  531. return;
  532. }
  533. const QString user_input = line_edit->text(); // keep around for adding new tail if needed
  534. if(!verifyUserInput(line_edit, TailInput(line_edit->text()))) {
  535. // re-populate user input to hand through to NewTailDialog
  536. {
  537. QSignalBlocker blocker(line_edit);
  538. line_edit->setText(user_input);
  539. }
  540. if(!addNewDatabaseElement(line_edit, OPL::DbTable::Tails)) {
  541. onBadInputReceived(line_edit);
  542. }
  543. }
  544. const auto space = QLatin1Char(' ');
  545. if(line_edit->text().contains(space))
  546. line_edit->setText(line_edit->text().split(space)[0]); // strip out autocomplete suggestion
  547. }
  548. void NewFlightDialog::on_doftLineEdit_editingFinished()
  549. {
  550. const auto line_edit = ui->doftLineEdit;
  551. OPL::Date date(ui->doftLineEdit->text(), m_format);
  552. LOG << "Date: " << date.toString();
  553. LOG << "is valid? " << date.isValid();
  554. line_edit->setText(date.toString());
  555. if(ui->doftLineEdit->text().isEmpty()) {
  556. onBadInputReceived(line_edit);
  557. return;
  558. }
  559. onGoodInputReceived(line_edit);
  560. }
  561. void NewFlightDialog::on_pilotFlyingCheckBox_stateChanged(int arg1)
  562. {
  563. if (arg1 == Qt::CheckState::Checked) {
  564. ui->takeOffSpinBox->setValue(1);
  565. ui->landingSpinBox->setValue(1);
  566. } else {
  567. ui->takeOffSpinBox->setValue(0);
  568. ui->landingSpinBox->setValue(0);
  569. }
  570. }
  571. void NewFlightDialog::on_approachComboBox_currentTextChanged(const QString &arg1)
  572. {
  573. if (arg1 == CAT_3)
  574. ui->remarksLineEdit->setText(QStringLiteral("Autoland"));
  575. else if (arg1 == QLatin1String("OTHER"))
  576. INFO(tr("Please specify the approach type in the remarks field."));
  577. }
  578. /*!
  579. * \brief NewFlightDialog::on_functionComboBox_currentIndexChanged
  580. * If the Function Combo Box is selected to be either PIC or SIC, fill the corresponding
  581. * nameLineEdit with the logbook owner's name, then check for duplication.
  582. */
  583. void NewFlightDialog::on_functionComboBox_currentIndexChanged(int index)
  584. {
  585. int picPilotId = DBCache->getPilotNamesMap().key(ui->picNameLineEdit->text());
  586. int sicPilotId = DBCache->getPilotNamesMap().key(ui->sicNameLineEdit->text());
  587. int thirdPilotId = DBCache->getPilotNamesMap().key(ui->thirdPilotNameLineEdit->text());
  588. const QString &self = DBCache->getPilotNamesMap().value(1);
  589. switch (index) {
  590. case static_cast<int>(OPL::PilotFunction::PIC):
  591. DEB << "PIC selected...";
  592. ui->picNameLineEdit->setText(self);
  593. emit ui->picNameLineEdit->editingFinished();
  594. picPilotId = DBCache->getPilotNamesMap().key(ui->picNameLineEdit->text());
  595. if (picPilotId == sicPilotId) {
  596. ui->sicNameLineEdit->setText(QString());
  597. onBadInputReceived(ui->sicNameLineEdit);
  598. }
  599. if (picPilotId == thirdPilotId) {
  600. ui->thirdPilotNameLineEdit->setText(QString());
  601. onBadInputReceived(ui->thirdPilotNameLineEdit);
  602. }
  603. break;
  604. case static_cast<int>(OPL::PilotFunction::SIC):
  605. DEB << "SIC selected...";
  606. ui->sicNameLineEdit->setText(self);
  607. emit ui->sicNameLineEdit->editingFinished();
  608. sicPilotId = DBCache->getPilotNamesMap().key(ui->sicNameLineEdit->text());
  609. if (sicPilotId == picPilotId) {
  610. ui->picNameLineEdit->setText(QString());
  611. onBadInputReceived(ui->picNameLineEdit);
  612. }
  613. if (sicPilotId == thirdPilotId) {
  614. ui->thirdPilotNameLineEdit->setText(QString());
  615. onBadInputReceived(ui->thirdPilotNameLineEdit);
  616. }
  617. break;
  618. default:
  619. break;
  620. }
  621. }
  622. /*!
  623. * \brief NewFlightDialog::checkPilotFunctionsValid checks if there are incompatible selections made on Pilot Function.
  624. * \details Checks for 2 cases in which there might be a discrepancy between the PilotNameLineEdit and the functionComboBox:
  625. * - If the pilotNameLineEdit's value is self, but the functionComboBox has been manually selected to be different from either
  626. * PIC or FI
  627. * - If the functionComboBox has been set to PIC but the pilotNameLineEdit is not self
  628. * \param error_msg - the error string displayed to the user
  629. * \return
  630. */
  631. bool NewFlightDialog::pilotFunctionsInvalid()
  632. {
  633. int pic_id = DBCache->getPilotNamesMap().key(ui->picNameLineEdit->text());
  634. int function_index = ui->functionComboBox->currentIndex();
  635. if (pic_id == 1) {
  636. if (!(function_index == static_cast<int>(OPL::PilotFunction::PIC) || function_index == static_cast<int>(OPL::PilotFunction::FI))) {
  637. INFO(tr("The PIC is set to %1 but the Pilot Function is set to %2")
  638. .arg(ui->picNameLineEdit->text(), ui->functionComboBox->currentText()));
  639. return true;
  640. }
  641. } else {
  642. if (function_index == static_cast<int>(OPL::PilotFunction::PIC) || function_index == static_cast<int>(OPL::PilotFunction::FI)) {
  643. INFO(tr("The Pilot Function is set to %1, but the PIC is set to %2")
  644. .arg(ui->functionComboBox->currentText(), ui->picNameLineEdit->text()));
  645. return true;
  646. }
  647. }
  648. return false;
  649. }
  650. bool NewFlightDialog::duplicateNamesPresent()
  651. {
  652. const int picId = DBCache->getPilotNamesMap().key(ui->picNameLineEdit->text());
  653. const int sicId = DBCache->getPilotNamesMap().key(ui->sicNameLineEdit->text());
  654. const int thirdPilotId = DBCache->getPilotNamesMap().key(ui->thirdPilotNameLineEdit->text());
  655. // this is a bit explicit but better point out to the user what the case is
  656. if (picId == sicId) {
  657. INFO(tr("PIC and SIC names are the same."));
  658. return true;
  659. }
  660. if (picId == thirdPilotId && picId > 0) {
  661. INFO(tr("PIC and third Pilot names are the same."));
  662. return true;
  663. }
  664. if (sicId == thirdPilotId && sicId > 0) {
  665. INFO(tr("SIC and third Pilot names are the same."));
  666. return true;
  667. }
  668. return false;
  669. }
  670. bool NewFlightDialog::flightTimeIsZero()
  671. {
  672. const OPL::Time tofb = OPL::Time::fromString(ui->tofbTimeLineEdit->text(), m_format);
  673. const OPL::Time tonb = OPL::Time::fromString(ui->tonbTimeLineEdit->text(), m_format);
  674. const int block_minutes = OPL::Time::blockMinutes(tofb, tonb);
  675. if(block_minutes == 0) {
  676. INFO(tr("Total time of flight is zero."));
  677. return true;
  678. }
  679. return false;
  680. }
  681. /*!
  682. * \brief NewFlightDialog::on_buttonBox_accepted - checks for validity and commits the form data to the database
  683. * \details When the user is ready to submit a flight entry, a final check for valid entries is made, and the user
  684. * is prompted to correct unsatisfactory inputs. When this is done, prepareFlightEntryData() collects the input from
  685. * the user interface and converts it to database format. The data is then stored in a QHash<QString, QVariant>.
  686. * This data is then used to create a AFlightEntry object, which is then commited to the database by Database::commit()
  687. */
  688. void NewFlightDialog::on_buttonBox_accepted()
  689. {
  690. // one item is always invalid if the user accepts when the currently edited line edit is mandatory (invalidation on focus in event)
  691. if(!validationState.allButOneValid()) {
  692. informUserAboutMissingItems();
  693. return;
  694. }
  695. // trigger validation for all mandatory items to toggle verification state
  696. for (const auto& le : *mandatoryLineEdits)
  697. emit le->editingFinished();
  698. if (!validationState.allValid()) {
  699. informUserAboutMissingItems();
  700. return;
  701. }
  702. // run a couple of reasonableness checks
  703. if(pilotFunctionsInvalid())
  704. return;
  705. if(duplicateNamesPresent())
  706. return;
  707. if(flightTimeIsZero())
  708. return;
  709. // collect input and submit to database
  710. const auto newData = prepareFlightEntryData();
  711. DEB << "Old Data: ";
  712. DEB << flightEntry;
  713. flightEntry.setData(newData);
  714. DEB << "Committing: ";
  715. DEB << flightEntry;
  716. if (!DB->commit(flightEntry)) {
  717. WARN(tr("The following error has ocurred:"
  718. "<br><br>%1<br><br>"
  719. "The entry has not been saved."
  720. ).arg(DB->lastError.text()));
  721. return;
  722. } else {
  723. QDialog::accept();
  724. }
  725. }
  726. void NewFlightDialog::on_calendarPushButton_clicked()
  727. {
  728. calendar->setVisible(true);
  729. }
  730. void NewFlightDialog::calendarDateSelected()
  731. {
  732. calendar->setVisible(false);
  733. ui->doftLineEdit->setText(OPL::Date(calendar->selectedDate(), m_format).toString());
  734. emit ui->doftLineEdit->editingFinished();
  735. }
  736. // EntryEditDialog interface
  737. void NewFlightDialog::loadEntry(int rowID)
  738. {
  739. flightEntry = DB->getFlightEntry(rowID);
  740. fillWithEntryData();
  741. }
  742. bool NewFlightDialog::deleteEntry(int rowID)
  743. {
  744. const auto entry = DB->getFlightEntry(rowID);
  745. return DB->remove(entry);
  746. }