2
0

newflightdialog.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. /*
  2. *openPilotLog - A FOSS Pilot Logbook Application
  3. *Copyright (C) 2020-2022 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 "src/classes/time.h"
  20. #include "src/database/databasecache.h"
  21. #include "src/gui/verification/airportinput.h"
  22. #include "src/gui/verification/completerprovider.h"
  23. #include "src/gui/verification/pilotinput.h"
  24. #include "src/gui/verification/tailinput.h"
  25. #include "src/gui/verification/timeinput.h"
  26. #include "ui_newflightdialog.h"
  27. #include "src/opl.h"
  28. #include "src/functions/datetime.h"
  29. #include "src/classes/settings.h"
  30. #include "src/functions/calc.h"
  31. #include "src/gui/dialogues/newtaildialog.h"
  32. #include "src/gui/dialogues/newpilotdialog.h"
  33. #include <QDateTime>
  34. #include <QCompleter>
  35. #include <QKeyEvent>
  36. NewFlightDialog::NewFlightDialog(QWidget *parent)
  37. : QDialog(parent),
  38. ui(new Ui::NewFlightDialog)
  39. {
  40. init();
  41. // Set up UI (New Flight)
  42. LOG << Settings::read(Settings::FlightLogging::Function);
  43. if(Settings::read(Settings::FlightLogging::Function).toInt() == static_cast<int>(OPL::PilotFunction::PIC)){
  44. ui->picNameLineEdit->setText(self);
  45. ui->functionComboBox->setCurrentIndex(0);
  46. emit ui->picNameLineEdit->editingFinished();
  47. }
  48. if (Settings::read(Settings::FlightLogging::Function).toInt() == static_cast<int>(OPL::PilotFunction::SIC)) {
  49. ui->sicNameLineEdit->setText(self);
  50. ui->functionComboBox->setCurrentIndex(2);
  51. emit ui->sicNameLineEdit->editingFinished();
  52. }
  53. ui->doftLineEdit->setText(OPL::DateTime::currentDate());
  54. emit ui->doftLineEdit->editingFinished();
  55. }
  56. NewFlightDialog::NewFlightDialog(int row_id, QWidget *parent)
  57. : QDialog(parent),
  58. ui(new Ui::NewFlightDialog)
  59. {
  60. init();
  61. flightEntry = DB->getFlightEntry(row_id);
  62. fillWithEntryData();
  63. }
  64. NewFlightDialog::~NewFlightDialog()
  65. {
  66. delete ui;
  67. }
  68. void NewFlightDialog::init()
  69. {
  70. // Setup UI
  71. ui->setupUi(this);
  72. // Initialise line edit lists
  73. auto time_line_edits = new QList<QLineEdit*>{ui->tofbTimeLineEdit, ui->tonbTimeLineEdit};
  74. timeLineEdits = time_line_edits;
  75. auto location_line_edits = new QList<QLineEdit*>{ui->deptLocationLineEdit, ui->destLocationLineEdit};
  76. locationLineEdits = location_line_edits;
  77. auto pilot_name_line_edits = new QList<QLineEdit*>{ui->picNameLineEdit, ui->sicNameLineEdit, ui->thirdPilotNameLineEdit};
  78. pilotNameLineEdits = pilot_name_line_edits;
  79. auto mandatory_line_edits = new QList<QLineEdit*>{ui->doftLineEdit, ui->deptLocationLineEdit, ui->destLocationLineEdit,
  80. ui->tofbTimeLineEdit, ui->tonbTimeLineEdit,
  81. ui->picNameLineEdit, ui->acftLineEdit};
  82. // {doft = 0, dept = 1, dest = 2, tofb = 3, tonb = 4, pic = 5, acft = 6}
  83. mandatoryLineEdits = mandatory_line_edits;
  84. for (const auto& line_edit : *mandatoryLineEdits)
  85. line_edit->installEventFilter(this);
  86. // Approach Combo Box and Function Combo Box
  87. OPL::GLOBALS->loadApproachTypes(ui->approachComboBox);
  88. OPL::GLOBALS->loadPilotFunctios(ui->functionComboBox);
  89. setupRawInputValidation();
  90. setupSignalsAndSlots();
  91. readSettings();
  92. }
  93. /*!
  94. * \brief NewFlightDialog::setupRawInputValidation outfits the line edits with QRegularExpresionValidators and QCompleters
  95. */
  96. void NewFlightDialog::setupRawInputValidation()
  97. {
  98. // Time Line Edits
  99. for (const auto& line_edit : *timeLineEdits) {
  100. auto validator = new QRegularExpressionValidator(QRegularExpression("([01]?[0-9]|2[0-3]):?[0-5][0-9]?"), line_edit);
  101. line_edit->setValidator(validator);
  102. }
  103. // Location Line Edits
  104. for (const auto& line_edit : *locationLineEdits) {
  105. auto validator = new QRegularExpressionValidator(QRegularExpression("[a-zA-Z0-9]{1,4}"), line_edit);
  106. line_edit->setValidator(validator);
  107. line_edit->setCompleter(QCompleterProvider.getCompleter(CompleterProvider::Airports));
  108. }
  109. // Name Line Edits
  110. for (const auto& line_edit : *pilotNameLineEdits) {
  111. line_edit->setCompleter(QCompleterProvider.getCompleter(CompleterProvider::Pilots));
  112. }
  113. // Acft Line Edit
  114. ui->acftLineEdit->setCompleter(QCompleterProvider.getCompleter(CompleterProvider::Tails));
  115. }
  116. void NewFlightDialog::setupSignalsAndSlots()
  117. {
  118. for (const auto& line_edit : *timeLineEdits)
  119. QObject::connect(line_edit, &QLineEdit::editingFinished,
  120. this, &NewFlightDialog::onTimeLineEdit_editingFinished);
  121. // Change text to upper case for location and acft line edits
  122. QObject::connect(ui->acftLineEdit, &QLineEdit::textChanged,
  123. this, &NewFlightDialog::toUpper);
  124. for (const auto& line_edit : *locationLineEdits) {
  125. QObject::connect(line_edit, &QLineEdit::textChanged,
  126. this, &NewFlightDialog::toUpper);
  127. QObject::connect(line_edit, &QLineEdit::editingFinished,
  128. this, &NewFlightDialog::onLocationLineEdit_editingFinished);
  129. }
  130. for (const auto& line_edit : *pilotNameLineEdits)
  131. QObject::connect(line_edit, &QLineEdit::editingFinished,
  132. this, &NewFlightDialog::onPilotNameLineEdit_editingFinshed);
  133. }
  134. /*!
  135. * \brief NewFlightDialog::eventFilter invalidates mandatory line edits on focus in.
  136. * \details Some of the QLineEdits have validators set that provide raw input validation. These validators have the side effect
  137. * that if an input does not meet the raw input validation criteria, onEditingFinished() is not emitted when the line edit loses
  138. * focus. This could lead to a line edit that previously had good input to be changed to bad input without the validation bit
  139. * in validationState being unset, because the second step of input validation is only triggered when editingFinished() is emitted.
  140. */
  141. bool NewFlightDialog::eventFilter(QObject *object, QEvent *event)
  142. {
  143. auto line_edit = qobject_cast<QLineEdit*>(object);
  144. if (line_edit != nullptr) {
  145. if (mandatoryLineEdits->contains(line_edit) && event->type() == QEvent::FocusIn) {
  146. // set verification bit to false when entering a mandatory line edit
  147. validationState.invalidate(mandatoryLineEdits->indexOf(line_edit));
  148. return false;
  149. }
  150. }
  151. return false;
  152. }
  153. /*!
  154. * \brief NewFlightDialog::readSettings Reads user-defined settings from an INI file.
  155. */
  156. void NewFlightDialog::readSettings()
  157. {
  158. ui->functionComboBox->setCurrentIndex(Settings::read(Settings::FlightLogging::Function).toInt());
  159. ui->approachComboBox->setCurrentIndex(Settings::read(Settings::FlightLogging::Approach).toInt());
  160. ui->pilotFlyingCheckBox->setChecked(Settings::read(Settings::FlightLogging::PilotFlying).toBool());
  161. ui->ifrCheckBox->setChecked(Settings::read(Settings::FlightLogging::LogIFR).toBool());
  162. ui->flightNumberLineEdit->setText(Settings::read(Settings::FlightLogging::FlightNumberPrefix).toString());
  163. }
  164. /*!
  165. * \brief NewFlightDialog::fillWithEntryData Takes an existing flight entry and fills the UI with its data
  166. * to enable editing an existing flight.
  167. */
  168. void NewFlightDialog::fillWithEntryData()
  169. {
  170. DEB << "Restoring Flight: ";
  171. DEB << flightEntry;
  172. const auto &flight_data = flightEntry.getData();
  173. // Date of Flight
  174. ui->doftLineEdit->setText(flight_data.value(OPL::Db::FLIGHTS_DOFT).toString());
  175. // Location
  176. ui->deptLocationLineEdit->setText(flight_data.value(OPL::Db::FLIGHTS_DEPT).toString());
  177. ui->destLocationLineEdit->setText(flight_data.value(OPL::Db::FLIGHTS_DEST).toString());
  178. // Times
  179. ui->tofbTimeLineEdit->setText(OPL::Time(flight_data.value(OPL::Db::FLIGHTS_TOFB).toInt()).toString());
  180. ui->tonbTimeLineEdit->setText(OPL::Time(flight_data.value(OPL::Db::FLIGHTS_TONB).toInt()).toString());
  181. ui->acftLineEdit->setText(DBCache->getTailsMap().value(flight_data.value(OPL::Db::FLIGHTS_ACFT).toInt()));
  182. ui->picNameLineEdit->setText(DBCache->getPilotNamesMap().value(flight_data.value(OPL::Db::FLIGHTS_PIC).toInt()));
  183. ui->sicNameLineEdit->setText(DBCache->getPilotNamesMap().value(flight_data.value(OPL::Db::FLIGHTS_SECONDPILOT).toInt()));
  184. ui->thirdPilotNameLineEdit->setText(DBCache->getPilotNamesMap().value(flight_data.value(OPL::Db::FLIGHTS_THIRDPILOT).toInt()));
  185. //Function
  186. const QHash<int, QString> functions = {
  187. {0, OPL::Db::FLIGHTS_TPIC},
  188. {1, OPL::Db::FLIGHTS_TPICUS},
  189. {2, OPL::Db::FLIGHTS_TSIC},
  190. {3, OPL::Db::FLIGHTS_TDUAL},
  191. {4, OPL::Db::FLIGHTS_TFI},
  192. };
  193. for (int i = 0; i < 5; i++) { // QHash::iterator not guarenteed to be in ordetr
  194. if(flight_data.value(functions.value(i)).toInt() != 0)
  195. ui->functionComboBox->setCurrentIndex(i);
  196. }
  197. // Approach ComboBox
  198. const QString& app = flight_data.value(OPL::Db::FLIGHTS_APPROACHTYPE).toString();
  199. if(app != QString()){
  200. ui->approachComboBox->setCurrentText(app);
  201. }
  202. // Task
  203. bool PF = flight_data.value(OPL::Db::FLIGHTS_PILOTFLYING).toBool();
  204. ui->pilotFlyingCheckBox->setChecked(PF);
  205. // Flight Rules
  206. bool time_ifr = flight_data.value(OPL::Db::FLIGHTS_TIFR).toBool();
  207. ui->ifrCheckBox->setChecked(time_ifr);
  208. // Take-Off and Landing
  209. int TO = flight_data.value(OPL::Db::FLIGHTS_TODAY).toInt()
  210. + flight_data.value(OPL::Db::FLIGHTS_TONIGHT).toInt();
  211. int LDG = flight_data.value(OPL::Db::FLIGHTS_LDGDAY).toInt()
  212. + flight_data.value(OPL::Db::FLIGHTS_LDGNIGHT).toInt();
  213. ui->takeOffSpinBox->setValue(TO);
  214. ui->landingSpinBox->setValue(LDG);
  215. // Remarks
  216. ui->remarksLineEdit->setText(flight_data.value(OPL::Db::FLIGHTS_REMARKS).toString());
  217. // Flight Number
  218. ui->flightNumberLineEdit->setText(flight_data.value(OPL::Db::FLIGHTS_FLIGHTNUMBER).toString());
  219. for(const auto &line_edit : *mandatoryLineEdits)
  220. emit line_edit->editingFinished();
  221. }
  222. bool NewFlightDialog::verifyUserInput(QLineEdit *line_edit, const UserInput &input)
  223. {
  224. DEB << "Verifying user input: " << line_edit->text();
  225. if(!input.isValid()) {
  226. QString fixed = input.fixup();
  227. if(fixed == QString()) {
  228. onBadInputReceived(line_edit);
  229. return false;
  230. } else {
  231. line_edit->setText(fixed);
  232. onGoodInputReceived(line_edit);
  233. return true;
  234. }
  235. }
  236. onGoodInputReceived(line_edit);
  237. return true;
  238. }
  239. void NewFlightDialog::onGoodInputReceived(QLineEdit *line_edit)
  240. {
  241. DEB << line_edit->objectName() << " - Good input received - " << line_edit->text();
  242. line_edit->setStyleSheet(QString());
  243. if (mandatoryLineEdits->contains(line_edit))
  244. validationState.validate(mandatoryLineEdits->indexOf(line_edit));
  245. if (validationState.timesValid()) {
  246. updateBlockTimeLabel();
  247. if (validationState.nightDataValid())
  248. updateNightCheckBoxes();
  249. }
  250. }
  251. void NewFlightDialog::onBadInputReceived(QLineEdit *line_edit)
  252. {
  253. DEB << line_edit->objectName() << " - Bad input received - " << line_edit->text();
  254. line_edit->setStyleSheet(OPL::Styles::RED_BORDER);
  255. line_edit->setText(QString());
  256. if (mandatoryLineEdits->contains(line_edit))
  257. validationState.invalidate(mandatoryLineEdits->indexOf(line_edit));
  258. validationState.printValidationStatus();
  259. }
  260. void NewFlightDialog::updateBlockTimeLabel()
  261. {
  262. const OPL::Time tofb = OPL::Time::fromString(ui->tofbTimeLineEdit->text());
  263. const OPL::Time tonb = OPL::Time::fromString(ui->tonbTimeLineEdit->text());
  264. const OPL::Time tblk = OPL::Time::blockTime(tofb, tonb);
  265. ui->tblkDisplayLabel->setText(tblk.toString());
  266. }
  267. /*!
  268. * \brief NewFlightDialog::addNewTail If the user input is not in the aircraftList, the user
  269. * is prompted if he wants to add a new entry to the database
  270. */
  271. bool NewFlightDialog::addNewTail(QLineEdit& parent_line_edit)
  272. {
  273. QMessageBox::StandardButton reply;
  274. reply = QMessageBox::question(this, tr("No Aircraft found"),
  275. tr("No aircraft with this registration found.<br>"
  276. "If this is the first time you log a flight with this aircraft, "
  277. "you have to add the registration to the database first."
  278. "<br><br>Would you like to add a new aircraft to the database?"),
  279. QMessageBox::Yes|QMessageBox::No);
  280. if (reply == QMessageBox::Yes) {
  281. // create and open new aircraft dialog
  282. NewTailDialog nt(ui->acftLineEdit->text(), this);
  283. int ret = nt.exec();
  284. // update map and list, set line edit
  285. if (ret == QDialog::Accepted) {
  286. DEB << "New Tail Entry added:";
  287. DEB << DB->getTailEntry(DB->getLastEntry(OPL::DbTable::Tails));
  288. // update Line Edit with newly added tail
  289. parent_line_edit.setText(DBCache->getTailsMap().value(DB->getLastEntry(OPL::DbTable::Tails)));
  290. emit parent_line_edit.editingFinished();
  291. return true;
  292. } else {
  293. return false;
  294. }
  295. } else {
  296. return false;
  297. }
  298. }
  299. /*!
  300. * \brief NewFlightDialog::addNewPilot If the user input is not in the pilotNameList, the user
  301. * is prompted if he wants to add a new entry to the database
  302. */
  303. bool NewFlightDialog::addNewPilot(QLineEdit& parent_line_edit)
  304. {
  305. QMessageBox::StandardButton reply;
  306. reply = QMessageBox::question(this, tr("No Pilot found"),
  307. tr("No pilot found.<br>Please enter the Name as"
  308. "<br><br><center><b>Lastname, Firstname</b></center><br><br>"
  309. "If this is the first time you log a flight with this pilot, "
  310. "you have to add the pilot to the database first."
  311. "<br><br>Would you like to add a new pilot to the database?"),
  312. QMessageBox::Yes|QMessageBox::No);
  313. if (reply == QMessageBox::Yes) {
  314. // create and open new pilot dialog
  315. NewPilotDialog np(this);
  316. int ret = np.exec();
  317. // update map and list, set line edit
  318. if (ret == QDialog::Accepted) {
  319. DEB << "New Pilot Entry added:";
  320. DEB << DB->getPilotEntry(DB->getLastEntry(OPL::DbTable::Pilots));
  321. // update Line Edit with newly added pilot
  322. parent_line_edit.setText(DBCache->getPilotNamesMap().value(DB->getLastEntry(OPL::DbTable::Pilots)));
  323. emit parent_line_edit.editingFinished();
  324. return true;
  325. } else {
  326. return false;
  327. }
  328. } else
  329. return false;
  330. }
  331. /*!
  332. * \brief NewFlightDialog::prepareFlightEntryData reads the user input from the UI and converts it into
  333. * the database format.
  334. * \return OPL::RowData_T a map containing a row ready for database submission
  335. */
  336. OPL::RowData_T NewFlightDialog::prepareFlightEntryData()
  337. {
  338. if(!validationState.allValid())
  339. return {};
  340. // prepare the entry data
  341. OPL::RowData_T new_data;
  342. // Calculate Block and Night Time
  343. const OPL::Time tofb = OPL::Time::fromString(ui->tofbTimeLineEdit->text());
  344. const OPL::Time tonb = OPL::Time::fromString(ui->tonbTimeLineEdit->text());
  345. const int block_minutes = OPL::Time::blockMinutes(tofb, tonb);
  346. QDateTime departure_date_time = OPL::DateTime::fromString(ui->doftLineEdit->text() + ui->tofbTimeLineEdit->text());
  347. const auto night_time_data = OPL::Calc::NightTimeValues(ui->deptLocationLineEdit->text(), ui->destLocationLineEdit->text(),
  348. departure_date_time, block_minutes, Settings::read(Settings::FlightLogging::NightAngle).toInt());
  349. // Mandatory data
  350. new_data.insert(OPL::Db::FLIGHTS_DOFT, ui->doftLineEdit->text());
  351. new_data.insert(OPL::Db::FLIGHTS_DEPT, ui->deptLocationLineEdit->text());
  352. new_data.insert(OPL::Db::FLIGHTS_TOFB, tofb.toMinutes());
  353. new_data.insert(OPL::Db::FLIGHTS_DEST, ui->destLocationLineEdit->text());
  354. new_data.insert(OPL::Db::FLIGHTS_TONB, tonb.toMinutes());
  355. new_data.insert(OPL::Db::FLIGHTS_TBLK, block_minutes);
  356. // Night
  357. new_data.insert(OPL::Db::FLIGHTS_TNIGHT, night_time_data.nightMinutes);
  358. // Aircraft
  359. int acft_id = DBCache->getTailsMap().key(ui->acftLineEdit->text());
  360. new_data.insert(OPL::Db::FLIGHTS_ACFT, acft_id);
  361. const OPL::TailEntry acft_data = DB->getTailEntry(acft_id);
  362. bool multi_pilot = acft_data.getData().value(OPL::Db::TAILS_MULTIPILOT).toBool();
  363. bool multi_engine = acft_data.getData().value(OPL::Db::TAILS_MULTIENGINE).toBool();
  364. if (multi_pilot) {
  365. new_data.insert(OPL::Db::FLIGHTS_TMP, block_minutes);
  366. new_data.insert(OPL::Db::FLIGHTS_TSPSE, QString());
  367. new_data.insert(OPL::Db::FLIGHTS_TSPME, QString());
  368. } else if (!multi_pilot && !multi_engine) {
  369. new_data.insert(OPL::Db::FLIGHTS_TMP, QString());
  370. new_data.insert(OPL::Db::FLIGHTS_TSPSE, block_minutes);
  371. new_data.insert(OPL::Db::FLIGHTS_TSPME, QString());
  372. } else if (!multi_pilot && multi_engine) {
  373. new_data.insert(OPL::Db::FLIGHTS_TMP, QString());
  374. new_data.insert(OPL::Db::FLIGHTS_TSPSE, QString());
  375. new_data.insert(OPL::Db::FLIGHTS_TSPME, block_minutes);
  376. }
  377. // Pilots
  378. new_data.insert(OPL::Db::FLIGHTS_PIC, DBCache->getPilotNamesMap().key(ui->picNameLineEdit->text()));
  379. new_data.insert(OPL::Db::FLIGHTS_SECONDPILOT, DBCache->getPilotNamesMap().key(ui->sicNameLineEdit->text()));
  380. new_data.insert(OPL::Db::FLIGHTS_THIRDPILOT, DBCache->getPilotNamesMap().key(ui->thirdPilotNameLineEdit->text()));
  381. // IFR time
  382. if (ui->ifrCheckBox->isChecked()) {
  383. new_data.insert(OPL::Db::FLIGHTS_TIFR, block_minutes);
  384. } else {
  385. new_data.insert(OPL::Db::FLIGHTS_TIFR, QString());
  386. }
  387. // Function Times
  388. QStringList function_times = {
  389. OPL::Db::FLIGHTS_TPIC,
  390. OPL::Db::FLIGHTS_TPICUS,
  391. OPL::Db::FLIGHTS_TSIC,
  392. OPL::Db::FLIGHTS_TDUAL,
  393. OPL::Db::FLIGHTS_TFI,
  394. };
  395. // Determine function times, zero out all values except one (use QString() iso 0 for correct handling of NULL in DB
  396. // Log Instructor Time as PIC time as well
  397. const int& function_index = ui->functionComboBox->currentIndex();
  398. switch (function_index) {
  399. case 4:
  400. LOG << "Function FI";
  401. for (int i = 0; i < 5; i++){
  402. if(i == 0 || i == 4)
  403. new_data.insert(function_times[i], block_minutes);
  404. else
  405. new_data.insert(function_times[i], QString());
  406. }
  407. break;
  408. default:
  409. for (int i = 0; i < 5; i++){
  410. if(i == function_index)
  411. new_data.insert(function_times[i], block_minutes);
  412. else
  413. new_data.insert(function_times[i], QString());
  414. }
  415. break;
  416. }
  417. // Pilot flying / Pilot monitoring
  418. new_data.insert(OPL::Db::FLIGHTS_PILOTFLYING, ui->pilotFlyingCheckBox->isChecked());
  419. // Take-Off and Landing
  420. if (ui->toNightCheckBox->isChecked()) {
  421. new_data.insert(OPL::Db::FLIGHTS_TONIGHT, ui->takeOffSpinBox->value());
  422. new_data.insert(OPL::Db::FLIGHTS_TODAY, 0);
  423. } else {
  424. new_data.insert(OPL::Db::FLIGHTS_TONIGHT, 0);
  425. new_data.insert(OPL::Db::FLIGHTS_TODAY, ui->takeOffSpinBox->value());
  426. }
  427. if (ui->ldgNightCheckBox->isChecked()) {
  428. new_data.insert(OPL::Db::FLIGHTS_LDGNIGHT, ui->landingSpinBox->value());
  429. new_data.insert(OPL::Db::FLIGHTS_LDGDAY, 0);
  430. } else {
  431. new_data.insert(OPL::Db::FLIGHTS_LDGNIGHT, 0);
  432. new_data.insert(OPL::Db::FLIGHTS_LDGDAY, ui->landingSpinBox->value());
  433. }
  434. if (ui->approachComboBox->currentText() == CAT_3) // ILS CAT III
  435. new_data.insert(OPL::Db::FLIGHTS_AUTOLAND, ui->landingSpinBox->value());
  436. // Additional Data
  437. new_data.insert(OPL::Db::FLIGHTS_APPROACHTYPE, ui->approachComboBox->currentText());
  438. new_data.insert(OPL::Db::FLIGHTS_FLIGHTNUMBER, ui->flightNumberLineEdit->text());
  439. new_data.insert(OPL::Db::FLIGHTS_REMARKS, ui->remarksLineEdit->text());
  440. return new_data;
  441. }
  442. /*!
  443. * \brief NewFlightDialog::updateNightCheckBoxes updates the check boxes for take-off and landing
  444. * at night. Returns the number of minutes of night time.
  445. * \return
  446. */
  447. void NewFlightDialog::updateNightCheckBoxes()
  448. {
  449. // calculate Block Time
  450. const OPL::Time tofb = OPL::Time::fromString(ui->tofbTimeLineEdit->text());
  451. const OPL::Time tonb = OPL::Time::fromString(ui->tonbTimeLineEdit->text());
  452. const int block_minutes = OPL::Time::blockMinutes(tofb, tonb);
  453. // Calculate Night Time
  454. const QString dept_date = (ui->doftLineEdit->text() + ui->tofbTimeLineEdit->text());
  455. const auto dept_date_time = OPL::DateTime::fromString(dept_date);
  456. const int night_angle = Settings::read(Settings::FlightLogging::NightAngle).toInt();
  457. const auto night_values = OPL::Calc::NightTimeValues(
  458. ui->deptLocationLineEdit->text(),
  459. ui->destLocationLineEdit->text(),
  460. dept_date_time,
  461. block_minutes,
  462. night_angle);
  463. // set check boxes
  464. if (night_values.takeOffNight)
  465. ui->toNightCheckBox->setCheckState(Qt::Checked);
  466. else
  467. ui->toNightCheckBox->setCheckState(Qt::Unchecked);
  468. if (night_values.landingNight)
  469. ui->ldgNightCheckBox->setCheckState(Qt::Checked);
  470. else
  471. ui->ldgNightCheckBox->setCheckState(Qt::Unchecked);
  472. }
  473. /*!
  474. * \brief NewFlightDialog::toUpper - changes inserted text to upper case for IATA/ICAO airport codes and registrations.
  475. */
  476. void NewFlightDialog::toUpper(const QString &text)
  477. {
  478. const auto line_edit = this->findChild<QLineEdit*>(sender()->objectName());
  479. {
  480. const QSignalBlocker blocker(line_edit);
  481. line_edit->setText(text.toUpper());
  482. }
  483. }
  484. void NewFlightDialog::onTimeLineEdit_editingFinished()
  485. {
  486. auto line_edit = this->findChild<QLineEdit*>(sender()->objectName());
  487. verifyUserInput(line_edit, TimeInput(line_edit->text()));
  488. }
  489. void NewFlightDialog::onPilotNameLineEdit_editingFinshed()
  490. {
  491. auto line_edit = this->findChild<QLineEdit*>(sender()->objectName());
  492. if(!verifyUserInput(line_edit, PilotInput(line_edit->text()))) {
  493. if(!addNewPilot(*line_edit))
  494. onBadInputReceived(line_edit);
  495. }
  496. }
  497. void NewFlightDialog::onLocationLineEdit_editingFinished()
  498. {
  499. const QString& line_edit_name = sender()->objectName();
  500. const auto line_edit = this->findChild<QLineEdit*>(line_edit_name);
  501. QLabel* name_label;
  502. if (line_edit_name.contains(QLatin1String("dept")))
  503. name_label = ui->deptNameLabel;
  504. else
  505. name_label = ui->destNameLabel;
  506. if(verifyUserInput(line_edit, AirportInput(line_edit->text())) ) {
  507. // Match ICAO code with Airport Name and display on label
  508. name_label->setText(DBCache->getAirportsMapNames().value(
  509. DBCache->getAirportsMapICAO().key(
  510. line_edit->text())));
  511. } else
  512. name_label->setText("Unknown Airport");
  513. }
  514. void NewFlightDialog::on_acftLineEdit_editingFinished()
  515. {
  516. const auto line_edit = ui->acftLineEdit;
  517. if(!verifyUserInput(line_edit, TailInput(line_edit->text())))
  518. if(!addNewTail(*line_edit))
  519. onBadInputReceived(line_edit);
  520. const auto space = QLatin1Char(' ');
  521. if(line_edit->text().contains(space))
  522. line_edit->setText(line_edit->text().split(space)[0]); // strip out autocomplete suggestion
  523. }
  524. void NewFlightDialog::on_doftLineEdit_editingFinished()
  525. {
  526. const auto line_edit = ui->doftLineEdit;
  527. auto text = ui->doftLineEdit->text();
  528. auto label = ui->doftDisplayLabel;
  529. TODO << "Non-default Date formats not implemented yet.";
  530. OPL::DateFormat date_format = OPL::DateFormat::ISODate;
  531. auto date = OPL::DateTime::parseInput(text, date_format);
  532. if (date.isValid()) {
  533. label->setText(date.toString(Qt::TextDate));
  534. line_edit->setText(OPL::DateTime::dateToString(date, date_format));
  535. onGoodInputReceived(line_edit);
  536. return;
  537. }
  538. label->setText(tr("Invalid Date."));
  539. onBadInputReceived(line_edit);
  540. }
  541. void NewFlightDialog::on_pilotFlyingCheckBox_stateChanged(int arg1)
  542. {
  543. if (arg1 == Qt::CheckState::Checked) {
  544. ui->takeOffSpinBox->setValue(1);
  545. ui->landingSpinBox->setValue(1);
  546. } else {
  547. ui->takeOffSpinBox->setValue(0);
  548. ui->landingSpinBox->setValue(0);
  549. }
  550. }
  551. void NewFlightDialog::on_approachComboBox_currentTextChanged(const QString &arg1)
  552. {
  553. if (arg1 == CAT_3)
  554. ui->remarksLineEdit->setText(QStringLiteral("Autoland"));
  555. else if (arg1 == QLatin1String("OTHER"))
  556. INFO(tr("Please specify the approach type in the remarks field."));
  557. }
  558. /*!
  559. * \brief NewFlightDialog::on_functionComboBox_currentIndexChanged
  560. * If the Function Combo Box is selected to be either PIC or SIC, fill the corresponding
  561. * nameLineEdit with the logbook owner's name, then check for duplication.
  562. */
  563. void NewFlightDialog::on_functionComboBox_currentIndexChanged(int index)
  564. {
  565. if (index == static_cast<int>(OPL::PilotFunction::PIC)) {
  566. ui->picNameLineEdit->setText(self);
  567. emit ui->picNameLineEdit->editingFinished();
  568. if (DBCache->getPilotNamesMap().key(ui->picNameLineEdit->text())
  569. == DBCache->getPilotNamesMap().key(ui->sicNameLineEdit->text()))
  570. ui->sicNameLineEdit->setText(QString());
  571. } else if (index == static_cast<int>(OPL::PilotFunction::SIC)) {
  572. ui->sicNameLineEdit->setText(self);
  573. emit ui->sicNameLineEdit->editingFinished();
  574. if (DBCache->getPilotNamesMap().key(ui->picNameLineEdit->text())
  575. == DBCache->getPilotNamesMap().key(ui->sicNameLineEdit->text()))
  576. ui->picNameLineEdit->setText(QString());
  577. }
  578. }
  579. /*!
  580. * \brief NewFlightDialog::checkPilotFunctionsValid checks if there are incompatible selections made on Pilot Function.
  581. * \details Checks for 2 cases in which there might be a discrepancy between the PilotNameLineEdit and the functionComboBox:
  582. * - If the pilotNameLineEdit's value is self, but the functionComboBox has been manually selected to be different from either
  583. * PIC or FI
  584. * - If the functionComboBox has been set to PIC but the pilotNameLineEdit is not self
  585. * \param error_msg - the error string displayed to the user
  586. * \return
  587. */
  588. bool NewFlightDialog::checkPilotFunctionsValid()
  589. {
  590. int pic_id = DBCache->getPilotNamesMap().key(ui->picNameLineEdit->text());
  591. int function_index = ui->functionComboBox->currentIndex();
  592. if (pic_id == 1) {
  593. if (!(function_index == static_cast<int>(OPL::PilotFunction::PIC) || function_index == static_cast<int>(OPL::PilotFunction::FI))) {
  594. INFO(tr("The PIC is set to %1 but the Pilot Function is set to %2")
  595. .arg(ui->picNameLineEdit->text(), ui->functionComboBox->currentText()));
  596. return false;
  597. }
  598. } else {
  599. if (function_index == static_cast<int>(OPL::PilotFunction::PIC) || function_index == static_cast<int>(OPL::PilotFunction::FI)) {
  600. INFO(tr("The Pilot Function is set to %1, but the PIC is set to %2")
  601. .arg(ui->functionComboBox->currentText(), ui->picNameLineEdit->text()));
  602. return false;
  603. }
  604. }
  605. return true;
  606. }
  607. /*!
  608. * \brief NewFlightDialog::on_buttonBox_accepted - checks for validity and commits the form data to the database
  609. * \details When the user is ready to submit a flight entry, a final check for valid entries is made, and the user
  610. * is prompted to correct unsatisfactory inputs. When this is done, prepareFlightEntryData() collects the input from
  611. * the user interface and converts it to database format. The data is then stored in a QHash<QString, QVariant>.
  612. * This data is then used to create a AFlightEntry object, which is then commited to the database by Database::commit()
  613. */
  614. void NewFlightDialog::on_buttonBox_accepted()
  615. {
  616. // Debug
  617. validationState.printValidationStatus();
  618. for (const auto& le : *mandatoryLineEdits)
  619. emit le->editingFinished();
  620. // If input verification is passed, continue, otherwise prompt user to correct
  621. if (!validationState.allValid()) {
  622. const auto display_names = QHash<ValidationState::ValidationItem, QString> {
  623. {ValidationState::ValidationItem::doft, QObject::tr("Date of Flight")}, {ValidationState::ValidationItem::dept, QObject::tr("Departure Airport")},
  624. {ValidationState::ValidationItem::dest, QObject::tr("Destination Airport")}, {ValidationState::ValidationItem::tofb, QObject::tr("Time Off Blocks")},
  625. {ValidationState::ValidationItem::tonb, QObject::tr("Time on Blocks")}, {ValidationState::ValidationItem::pic, QObject::tr("PIC Name")},
  626. {ValidationState::ValidationItem::acft, QObject::tr("Aircraft Registration")}
  627. };
  628. QString missing_items;
  629. for (int i=0; i < mandatoryLineEdits->size(); i++) {
  630. if (!validationState.validAt(i)){
  631. missing_items.append(display_names.value(static_cast<ValidationState::ValidationItem>(i)) + "<br>");
  632. mandatoryLineEdits->at(i)->setStyleSheet(QStringLiteral("border: 1px solid red"));
  633. }
  634. }
  635. INFO(tr("Not all mandatory entries are valid.<br>"
  636. "The following item(s) are empty or invalid:"
  637. "<br><br><center><b>%1</b></center><br>"
  638. "Please go back and fill in the required data."
  639. ).arg(missing_items));
  640. return;
  641. }
  642. if(!checkPilotFunctionsValid())
  643. return;
  644. // If input verification passed, collect input and submit to database
  645. const auto newData = prepareFlightEntryData();
  646. DEB << "Old Data: ";
  647. DEB << flightEntry;
  648. flightEntry.setData(newData);
  649. DEB << "Committing: ";
  650. DEB << flightEntry;
  651. if (!DB->commit(flightEntry)) {
  652. WARN(tr("The following error has ocurred:"
  653. "<br><br>%1<br><br>"
  654. "The entry has not been saved."
  655. ).arg(DB->lastError.text()));
  656. return;
  657. } else {
  658. QDialog::accept();
  659. }
  660. }