newflightdialog.cpp 33 KB

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