newflight.cpp 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073
  1. /*
  2. *openPilot Log - A FOSS Pilot Logbook Application
  3. *Copyright (C) 2020 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 "newflight.h"
  19. #include "ui_newflight.h"
  20. #include "dbman.cpp"
  21. /// =======================================================
  22. /// Debug / WIP section
  23. /// =======================================================
  24. #define DEBUG(expr) \
  25. qDebug() << "~DEBUG" << __func__ << expr
  26. void NewFlight::on_verifyButton_clicked()//debug button
  27. {
  28. //fillExtrasLineEdits();
  29. //qDebug() << testlist;
  30. qDebug() << parent();
  31. }
  32. /*!
  33. * \brief NewFlight::nope for features that are not yet implemented
  34. */
  35. void NewFlight::nope()
  36. {
  37. QMessageBox nope(this); //error box
  38. nope.setText("This feature is not yet available!");
  39. nope.exec();
  40. }
  41. /// =======================================================
  42. /// Initialising variables used for storing user input
  43. /// Variables are initalised invalid to later fill them with
  44. /// meaningful inputs once they have been validated
  45. QVector<QString> flight;
  46. QDate date(QDate::currentDate());
  47. QString doft(QDate::currentDate().toString(Qt::ISODate));
  48. QString dept = "INVA";
  49. QString dest = "INVA";
  50. QTime tofb;
  51. QTime tonb;
  52. QTime tblk;
  53. QString pic = "-1";
  54. QString acft = "-1";
  55. // extras
  56. QString secondPilot = "-1";
  57. QString thirdPilot = "-1";
  58. QString pilotFunction = "-1";
  59. QString pilotTask = "-1";
  60. QString takeoff = "0";
  61. QString landing = "0";
  62. QString autoland = "0";
  63. QString approachType = "-1";
  64. // extra times
  65. QString tSPSE = "00:00";
  66. QString tSPME = "00:00";
  67. QString tMP = "00:00";
  68. /* If on submitting, not all checks are passed, user input is stored
  69. * in the scratchpad table to later re-fill the form enabling correction.*/
  70. bool hasOldInput = dbFlight::checkScratchpad();
  71. /// Raw Input validation
  72. const QString TIME_REGEX_PATTERN = "([01]?[0-9]?|2[0-3]):?[0-5][0-9]?";// We only want to allow inputs that make sense as a time, e.g. 99:99 is not a valid time
  73. const QString IATA = "[a-zA-Z0-9]{3}";
  74. const QString ICAO = "[a-zA-Z0-9]{4}";
  75. const QString LOC_REGEX_PATTERN = IATA + "|" + ICAO;
  76. const QString AIRCRAFT_REGEX_PATTERN = "[\\w0-9]+-?([\\w0-9]?)+";
  77. const QString PILOT_NAME_REGEX_PATTERN = "[\\w]+,? ?[\\w]+";
  78. /// Invalid characters (validators keep text even if it returns Invalid, see `onInputRejected` below)
  79. const QString TIME_INVALID_RGX = "[^\\d:]";
  80. const QString LOC_INVALID_RGX = "[^a-zA-Z0-9]";
  81. const QString AIRCRAFT_INVALID_RGX = "[^A-Z0-9\\-]";
  82. const QString PILOT_NAME_INVALID_RGX = "[^a-zA-Z, ]";
  83. /// Input max lengths
  84. const qint8 TIME_MAX_LENGTH = 5; //to allow for ':' e.g. "08:45"
  85. const qint8 LOC_MAX_LENGTH = 4;
  86. const qint8 AIRCRAFT_MAX_LENGTH = 10;
  87. const qint8 PILOT_NAME_MAX_LENGTH = 15;
  88. /*!
  89. * \brief setLineEditValidator set Validators for QLineEdits that end with Time, Loc,
  90. * Aircraft or Name
  91. */
  92. static inline void setupLineEdit(QLineEdit* line_edit, QVector<QStringList> completionLists)
  93. {
  94. auto line_edit_objectName = line_edit->objectName();
  95. DEBUG("Setting validators for " << line_edit_objectName);
  96. static const
  97. QVector<std::tuple<QRegularExpression, QRegularExpression,
  98. qint8, QStringList>> objectName_inputValidation_rgxs = {
  99. {QRegularExpression("\\w+Time"), QRegularExpression(TIME_REGEX_PATTERN),
  100. TIME_MAX_LENGTH, completionLists[0]},
  101. {QRegularExpression("\\w+Loc"), QRegularExpression(LOC_REGEX_PATTERN),
  102. LOC_MAX_LENGTH, completionLists[1]},
  103. {QRegularExpression("\\w+Acft"), QRegularExpression(AIRCRAFT_REGEX_PATTERN),
  104. AIRCRAFT_MAX_LENGTH, completionLists[2]},
  105. {QRegularExpression("\\w+Name"), QRegularExpression(PILOT_NAME_REGEX_PATTERN),
  106. PILOT_NAME_MAX_LENGTH, completionLists[3]},
  107. };
  108. auto validator = new StrictRegularExpressionValidator();
  109. for(auto tuple : objectName_inputValidation_rgxs)
  110. {
  111. auto objName_rgx = std::get<0>(tuple);
  112. auto input_rgx = std::get<1>(tuple);
  113. auto max_length = std::get<2>(tuple);
  114. auto completer_list = std::get<3>(tuple);
  115. if(objName_rgx.match(line_edit_objectName).hasMatch())
  116. {
  117. validator->setRegularExpression(input_rgx);
  118. line_edit->setValidator(validator);
  119. line_edit->setMaxLength(max_length);
  120. QCompleter* completer = new QCompleter(completer_list, line_edit);
  121. completer->setCaseSensitivity(Qt::CaseInsensitive);
  122. completer->setCompletionMode(QCompleter::PopupCompletion);
  123. if(objName_rgx != QRegularExpression("\\w+Loc")){
  124. completer->setFilterMode(Qt::MatchContains);
  125. }
  126. line_edit->setCompleter(completer);
  127. return;
  128. }
  129. }
  130. DEBUG("Couldnt find QLineEdit" << line_edit_objectName);
  131. }
  132. /*!
  133. * \brief NewFlight::storeSettings Commits current selection for auto-logging
  134. * to the database.
  135. */
  136. void NewFlight::storeSettings()
  137. {
  138. qDebug() << "Storing Settings...";
  139. dbSettings::storeSetting(100, ui->FunctionComboBox->currentText());
  140. dbSettings::storeSetting(101, ui->ApproachComboBox->currentText());
  141. dbSettings::storeSetting(102, QString::number(ui->PilotFlyingCheckBox->isChecked()));
  142. dbSettings::storeSetting(103, QString::number(ui->PilotMonitoringCheckBox->isChecked()));
  143. dbSettings::storeSetting(104, QString::number(ui->TakeoffSpinBox->value()));
  144. dbSettings::storeSetting(105, QString::number(ui->TakeoffCheckBox->isChecked()));
  145. dbSettings::storeSetting(106, QString::number(ui->LandingSpinBox->value()));
  146. dbSettings::storeSetting(107, QString::number(ui->LandingCheckBox->isChecked()));
  147. dbSettings::storeSetting(108, QString::number(ui->AutolandSpinBox->value()));
  148. dbSettings::storeSetting(109, QString::number(ui->AutolandCheckBox->isChecked()));
  149. dbSettings::storeSetting(110, QString::number(ui->IfrCheckBox->isChecked()));
  150. dbSettings::storeSetting(111, QString::number(ui->VfrCheckBox->isChecked()));
  151. }
  152. /*!
  153. * \brief NewFlight::restoreSettings Retreives auto-logging settings from database
  154. * and sets up ui accordingly
  155. */
  156. void NewFlight::restoreSettings()
  157. {
  158. qDebug() << "Restoring Settings...";//crashes if db is empty due to QVector index out of range.
  159. ui->FunctionComboBox->setCurrentText(dbSettings::retreiveSetting(100));
  160. ui->ApproachComboBox->setCurrentText(dbSettings::retreiveSetting(101));
  161. ui->PilotFlyingCheckBox->setChecked(dbSettings::retreiveSetting(102).toInt());
  162. ui->PilotMonitoringCheckBox->setChecked(dbSettings::retreiveSetting(103).toInt());
  163. ui->TakeoffSpinBox->setValue(dbSettings::retreiveSetting(104).toInt());
  164. ui->TakeoffCheckBox->setChecked(dbSettings::retreiveSetting(105).toInt());
  165. ui->LandingSpinBox->setValue(dbSettings::retreiveSetting(106).toInt());
  166. ui->LandingCheckBox->setChecked(dbSettings::retreiveSetting(107).toInt());
  167. ui->AutolandSpinBox->setValue(dbSettings::retreiveSetting(108).toInt());
  168. ui->AutolandCheckBox->setChecked(dbSettings::retreiveSetting(109).toInt());
  169. ui->IfrCheckBox->setChecked(dbSettings::retreiveSetting(110).toInt());
  170. ui->VfrCheckBox->setChecked(dbSettings::retreiveSetting(111).toInt());
  171. ui->flightNumberPrefixLabel->setText(dbSettings::retreiveSetting(50) + QLatin1Char('-'));
  172. }
  173. /*
  174. * Window Construction
  175. */
  176. NewFlight::NewFlight(QWidget *parent, QVector<QStringList> completionLists) :
  177. QDialog(parent),
  178. ui(new Ui::NewFlight)
  179. {
  180. ui->setupUi(this);
  181. // Set up Line Edits with QValidators, QCompleters and set Max length
  182. auto line_edits = ui->flightDataTab->findChildren<QLineEdit*>() +
  183. ui->extraTimes->findChildren<QLineEdit*>();
  184. for(auto line_edit : line_edits)
  185. {
  186. setupLineEdit(line_edit, completionLists);
  187. }
  188. // Groups for CheckBoxes
  189. QButtonGroup *FlightRulesGroup = new QButtonGroup(this);
  190. FlightRulesGroup->addButton(ui->IfrCheckBox);
  191. FlightRulesGroup->addButton(ui->VfrCheckBox);
  192. QButtonGroup *PilotTaskGroup = new QButtonGroup(this);
  193. PilotTaskGroup->addButton(ui->PilotFlyingCheckBox);
  194. PilotTaskGroup->addButton(ui->PilotMonitoringCheckBox);
  195. ui->deptTZ->setFocusPolicy(Qt::NoFocus);
  196. ui->destTZ->setFocusPolicy(Qt::NoFocus);
  197. ui->newDoft->setDate(QDate::currentDate());
  198. // Visually mark mandatory fields
  199. ui->newDeptLocLineEdit->setStyleSheet("border: 1px solid orange");
  200. ui->newDestLocLineEdit->setStyleSheet("border: 1px solid orange");
  201. ui->newDeptTimeLineEdit->setStyleSheet("border: 1px solid orange");
  202. ui->newDestTimeLineEdit->setStyleSheet("border: 1px solid orange");
  203. ui->newPicNameLineEdit->setStyleSheet("border: 1px solid orange");
  204. ui->newAcft->setStyleSheet("border: 1px solid orange");
  205. restoreSettings(); // settings for auto-logging are stored in the database.
  206. //Restore inputs if commiting to DB has been rejected.
  207. qDebug() << "Hasoldinput? = " << hasOldInput;
  208. if(hasOldInput) // Re-populate the Form
  209. {
  210. flight = dbFlight::retreiveScratchpad();
  211. qDebug() << "Re-Filling Form from Scratchpad";
  212. returnInput(flight);
  213. }
  214. ui->newDeptLocLineEdit->setFocus();
  215. }
  216. NewFlight::~NewFlight()
  217. {
  218. delete ui;
  219. }
  220. /*
  221. * Slots
  222. */
  223. /*!
  224. * \brief onInputRejected Set `line_edit`'s border to red and check if `rgx` matches
  225. * in order to keep text on line.
  226. */
  227. static void onInputRejected(QLineEdit* line_edit, QRegularExpression rgx){
  228. DEBUG("Input rejected" << line_edit->text());
  229. line_edit->setStyleSheet("border: 1px solid red");
  230. if(auto text = line_edit->text();
  231. rgx.match(text).hasMatch() == false)
  232. {
  233. line_edit->setText(line_edit->text());
  234. }
  235. }
  236. /*!
  237. * \brief onEditingFinished signal is emitted if input passed raw validation
  238. */
  239. static void onEditingFinished(QLineEdit* line_edit){
  240. DEBUG("Input accepted" << line_edit->text() << line_edit->metaObject()->className());
  241. line_edit->setStyleSheet("");
  242. }
  243. void NewFlight::on_deptTZ_currentTextChanged(const QString &arg1)
  244. {
  245. if(arg1 == "Local"){nope();} // currently only UTC time logging is supported
  246. ui->deptTZ->setCurrentIndex(0);
  247. }
  248. void NewFlight::on_destTZ_currentIndexChanged(const QString &arg1)
  249. {
  250. if(arg1 == "Local"){nope();} // currently only UTC time logging is supported
  251. ui->destTZ->setCurrentIndex(0);
  252. }
  253. /// Departure
  254. void NewFlight::on_newDeptLocLineEdit_inputRejected()
  255. {
  256. ui->newDeptLocLineEdit->setText(ui->newDeptLocLineEdit->text().toUpper());
  257. onInputRejected(ui->newDeptLocLineEdit, QRegularExpression(LOC_INVALID_RGX));
  258. }
  259. void NewFlight::on_newDeptLocLineEdit_textEdited(const QString &arg1)
  260. {
  261. ui->newDeptLocLineEdit->setText(arg1.toUpper());
  262. }
  263. void NewFlight::on_newDeptLocLineEdit_editingFinished()
  264. {
  265. QStringList locationList = dbAirport::retreiveIataIcaoList(); //To be moved outside of dialog eventually
  266. //DEBUG(locationList);
  267. auto line_edit = ui->newDeptLocLineEdit;
  268. onEditingFinished(line_edit); //reset style sheet
  269. dept = line_edit->text();
  270. // check if iata exists, replace with icao code if it does.
  271. if(dept.length() == 3){
  272. int index = locationList.indexOf(dept);
  273. if(index == -1){// Not in locationList
  274. DEBUG("Airport not found.");
  275. emit line_edit->inputRejected();
  276. }else{
  277. dept = locationList[index -1];
  278. }
  279. }
  280. // Check if 4-letter code is in locationList
  281. if(dept.length() == 4 && locationList.indexOf(dept) == -1){
  282. DEBUG("Airport not found.");
  283. emit line_edit->inputRejected();
  284. }
  285. line_edit->setText(dept);
  286. DEBUG("Departure set: " << dept);
  287. }
  288. void NewFlight::on_newDeptTimeLineEdit_inputRejected()
  289. {
  290. onInputRejected(ui->newDeptTimeLineEdit, QRegularExpression(TIME_INVALID_RGX));
  291. }
  292. void NewFlight::on_newDeptTimeLineEdit_editingFinished()
  293. {
  294. ui->newDeptTimeLineEdit->setText(calc::formatTimeInput(ui->newDeptTimeLineEdit->text()));
  295. tofb = QTime::fromString(ui->newDeptTimeLineEdit->text(),"hh:mm");
  296. auto line_edit = ui->newDeptTimeLineEdit;
  297. onEditingFinished(line_edit);
  298. if(tofb.isValid()){
  299. // continue
  300. DEBUG("Time Off Blocks is valid:" << tofb);
  301. }else{
  302. emit line_edit->inputRejected();
  303. }
  304. }
  305. /// Destination
  306. void NewFlight::on_newDestLocLineEdit_inputRejected()
  307. {
  308. ui->newDestLocLineEdit->setText(ui->newDestLocLineEdit->text().toUpper());
  309. onInputRejected(ui->newDestLocLineEdit, QRegularExpression(LOC_INVALID_RGX));
  310. }
  311. void NewFlight::on_newDestLocLineEdit_textEdited(const QString &arg1)
  312. {
  313. ui->newDestLocLineEdit->setText(arg1.toUpper());
  314. }
  315. void NewFlight::on_newDestLocLineEdit_editingFinished()
  316. {
  317. QStringList locationList = dbAirport::retreiveIataIcaoList(); //To be moved outside of dialog eventually
  318. auto line_edit = ui->newDestLocLineEdit;
  319. onEditingFinished(line_edit); //reset style sheet
  320. dest = line_edit->text();
  321. // check if iata exists, replace with icao code if it does.
  322. if(dest.length() == 3){
  323. int index = locationList.indexOf(dest);
  324. if(index == -1){// Not in locationList
  325. DEBUG("Airport not found.");
  326. emit line_edit->inputRejected();
  327. }else{
  328. dest = locationList[index -1];
  329. }
  330. }
  331. // Check if 4-letter code is in locationList
  332. if(dest.length() == 4 && locationList.indexOf(dest) == -1){
  333. DEBUG("Airport not found.");
  334. emit line_edit->inputRejected();
  335. }
  336. line_edit->setText(dest);
  337. DEBUG("Destination set: " << dest);
  338. }
  339. void NewFlight::on_newDestTimeLineEdit_inputRejected()
  340. {
  341. onInputRejected(ui->newDestTimeLineEdit, QRegularExpression(TIME_INVALID_RGX));
  342. }
  343. void NewFlight::on_newDestTimeLineEdit_editingFinished()
  344. {
  345. ui->newDestTimeLineEdit->setText(calc::formatTimeInput(ui->newDestTimeLineEdit->text()));
  346. tonb = QTime::fromString(ui->newDestTimeLineEdit->text(),"hh:mm");
  347. auto line_edit = ui->newDestTimeLineEdit;
  348. onEditingFinished(line_edit);
  349. if(tonb.isValid()){
  350. // continue
  351. DEBUG("Time On Blocks is valid:" << tonb);
  352. }else{
  353. emit line_edit->inputRejected();
  354. }
  355. }
  356. /// Date
  357. void NewFlight::on_newDoft_editingFinished()
  358. {
  359. date = ui->newDoft->date();
  360. doft = date.toString(Qt::ISODate);
  361. }
  362. /// Aircraft
  363. void NewFlight::on_newAcft_inputRejected()
  364. {
  365. onInputRejected(ui->newAcft, QRegularExpression(AIRCRAFT_INVALID_RGX));
  366. }
  367. void NewFlight::on_newAcft_editingFinished()
  368. {
  369. acft = "-1";// set invalid
  370. auto registrationList = dbAircraft::retreiveRegistrationList();
  371. auto line_edit = ui->newAcft;
  372. QStringList match = registrationList.filter(line_edit->text(), Qt::CaseInsensitive);
  373. if(match.length() != 0) {
  374. acft = match[0];
  375. line_edit->setText(acft);
  376. onEditingFinished(line_edit);
  377. }else{
  378. DEBUG("Registration not in List!");
  379. emit line_edit->inputRejected();
  380. QMessageBox::StandardButton reply;
  381. reply = QMessageBox::question(this, "No aircraft found", "No aircraft found.\n Would you like to add a new aircraft to the database?",
  382. QMessageBox::Yes|QMessageBox::No);
  383. if (reply == QMessageBox::Yes) {
  384. NewAcft na(this);
  385. na.exec();
  386. ui->newAcft->setText("");
  387. ui->newAcft->setFocus();
  388. }
  389. }
  390. }
  391. /// Pilot(s)
  392. /*!
  393. * \brief NewFlight::addNewPilotMessageBox If the user input is not in the pilotNameList, the user
  394. * is prompted if he wants to add a new entry to the database
  395. */
  396. void NewFlight::addNewPilotMessageBox()
  397. {
  398. QMessageBox::StandardButton reply;
  399. reply = QMessageBox::question(this, "No Pilot found",
  400. "No pilot found.\n Would you like to add a new pilot to the database?",
  401. QMessageBox::Yes|QMessageBox::No);
  402. if (reply == QMessageBox::Yes)
  403. {
  404. qDebug() << "Add new pilot selected";
  405. DEBUG("This feature is not yet available.");
  406. // create and open new pilot dialog
  407. }
  408. }
  409. void NewFlight::on_newPicNameLineEdit_inputRejected()
  410. {
  411. onInputRejected(ui->newPicNameLineEdit, QRegularExpression(PILOT_NAME_INVALID_RGX));
  412. }
  413. void NewFlight::on_newPicNameLineEdit_editingFinished()
  414. {
  415. auto line_edit = ui->newPicNameLineEdit;
  416. pic = "-1"; // set invalid
  417. if(ui->newPicNameLineEdit->text() == "self") // Logbook owner is PIC
  418. {
  419. pic = "self";
  420. DEBUG("Pilot selected: " << pic);
  421. }else //check if entry is in pilotList
  422. {
  423. QStringList pilotList = dbPilots::retreivePilotList();
  424. QStringList match = pilotList.filter(line_edit->text(), Qt::CaseInsensitive);
  425. if(match.length()!= 0)
  426. {
  427. pic = match[0];
  428. line_edit->setText(pic);
  429. DEBUG("Pilot selected: " << pic);
  430. onEditingFinished(line_edit);
  431. }else
  432. {
  433. DEBUG("Pilot not found.");
  434. emit line_edit->inputRejected();
  435. addNewPilotMessageBox();
  436. }
  437. }
  438. }
  439. /*!
  440. * ============================================================================
  441. * The above entris are mandatory for logging a flight,
  442. * the rest of the entries are either optional or can
  443. * be determined from the entries already made.
  444. * ============================================================================
  445. */
  446. void NewFlight::on_secondPilotNameLineEdit_inputRejected()
  447. {
  448. onInputRejected(ui->secondPilotNameLineEdit, QRegularExpression(PILOT_NAME_INVALID_RGX));
  449. }
  450. void NewFlight::on_secondPilotNameLineEdit_editingFinished()
  451. {
  452. auto line_edit = ui->secondPilotNameLineEdit;
  453. secondPilot = "-1"; // set invalid
  454. if(ui->newPicNameLineEdit->text() == "self") // Logbook owner is PIC
  455. {
  456. secondPilot = "self";
  457. DEBUG("Pilot selected: " << secondPilot);
  458. }else //check if entry is in pilotList
  459. {
  460. QStringList pilotList = dbPilots::retreivePilotList();
  461. QStringList match = pilotList.filter(line_edit->text(), Qt::CaseInsensitive);
  462. if(match.length()!= 0)
  463. {
  464. secondPilot = match[0];
  465. line_edit->setText(secondPilot);
  466. DEBUG("Pilot selected: " << secondPilot);
  467. onEditingFinished(line_edit);
  468. }else
  469. {
  470. DEBUG("Pilot not found.");
  471. emit line_edit->inputRejected();
  472. addNewPilotMessageBox();
  473. }
  474. }
  475. }
  476. void NewFlight::on_thirdPilotNameLineEdit_inputRejected()
  477. {
  478. onInputRejected(ui->thirdPilotNameLineEdit, QRegularExpression(PILOT_NAME_INVALID_RGX));
  479. }
  480. void NewFlight::on_thirdPilotNameLineEdit_editingFinished()
  481. {
  482. auto line_edit = ui->thirdPilotNameLineEdit;
  483. thirdPilot = "-1"; // set invalid
  484. if(ui->newPicNameLineEdit->text() == "self") // Logbook owner is PIC
  485. {
  486. thirdPilot = "self";
  487. DEBUG("Pilot selected: " << thirdPilot);
  488. }else //check if entry is in pilotList
  489. {
  490. QStringList pilotList = dbPilots::retreivePilotList();
  491. QStringList match = pilotList.filter(line_edit->text(), Qt::CaseInsensitive);
  492. if(match.length()!= 0)
  493. {
  494. thirdPilot = match[0];
  495. line_edit->setText(thirdPilot);
  496. DEBUG("Pilot selected: " << thirdPilot);
  497. onEditingFinished(line_edit);
  498. }else
  499. {
  500. DEBUG("Pilot not found.");
  501. emit line_edit->inputRejected();
  502. addNewPilotMessageBox();
  503. }
  504. }
  505. }
  506. void NewFlight::on_FlightNumberLineEdit_editingFinished()
  507. {
  508. qDebug() << "tbd: FlightNumber slot";
  509. // To Do: Store input in variable, perform some checks
  510. // Setting for optional Prefix (e.g. LH, LX etc.)
  511. }
  512. /*
  513. * ============================================================================
  514. * Extras Tab - These are for user convenience. From many of
  515. * these selections, determinations can be made on how to log
  516. * details, so that the user does not have to enter each item
  517. * manually. See also fillExtrasLineEdits()
  518. * ============================================================================
  519. */
  520. void NewFlight::on_setAsDefaultButton_clicked()
  521. {
  522. storeSettings();
  523. }
  524. void NewFlight::on_restoreDefaultButton_clicked()
  525. {
  526. restoreSettings();
  527. }
  528. /*!
  529. * \brief On a given flight, time can either be logged as Pilot Flying (PF) or
  530. * Pilot Monitoring (PM). Cases where controls are changed during the flight
  531. * are rare and can be logged by manually editing the extras.
  532. */
  533. void NewFlight::on_PilotFlyingCheckBox_stateChanged(int)
  534. {
  535. if(ui->PilotFlyingCheckBox->isChecked()){
  536. ui->TakeoffSpinBox->setValue(1);
  537. ui->TakeoffCheckBox->setCheckState(Qt::Checked);
  538. ui->LandingSpinBox->setValue(1);
  539. ui->LandingCheckBox->setCheckState(Qt::Checked);
  540. }else if(!ui->PilotFlyingCheckBox->isChecked()){
  541. ui->TakeoffSpinBox->setValue(0);
  542. ui->TakeoffCheckBox->setCheckState(Qt::Unchecked);
  543. ui->LandingSpinBox->setValue(0);
  544. ui->LandingCheckBox->setCheckState(Qt::Unchecked);
  545. }
  546. }
  547. void NewFlight::on_ApproachComboBox_currentTextChanged(const QString &arg1)
  548. {
  549. if(arg1 == "ILS CAT III"){ //for a CAT III approach an Autoland is mandatory, so we can preselect it.
  550. ui->AutolandCheckBox->setCheckState(Qt::Checked);
  551. ui->AutolandSpinBox->setValue(1);
  552. }else{
  553. ui->AutolandCheckBox->setCheckState(Qt::Unchecked);
  554. ui->AutolandSpinBox->setValue(0);
  555. }
  556. approachType = arg1;
  557. qDebug() << "Approach Type: " << approachType;
  558. }
  559. /*
  560. * Extra Times - These line edits should be filled out automatically,
  561. * based on the ui selections and the user provided input. However,
  562. * manual adjustments are possible to cater for situations where for
  563. * example one portion of the flight is logged under different rules
  564. * than the rest of it.
  565. *
  566. * For example,
  567. * if we know the aircraft details we can determine how to log these times.
  568. * Some times are mutually exclusive, others can be combined.
  569. *
  570. * For example,
  571. * for a commercial Passenger flight, the commander can log all time as
  572. * Total Time and PIC time. If the aircraft is a multi-engine jet he can
  573. * also log Multi-Pilot time, and if he is an instructor, instructor time.
  574. *
  575. * It is not possible, however to log flight time as VFR or IFR time
  576. * simultaneously, as a flight at any given point in time can only follow
  577. * one set of rules. It is possible, to change flight rules and log the first
  578. * x minutes as VFR and the rest of it as IFR, for example. Hence the need
  579. * for the possibility to edit these times manually.
  580. *
  581. * The most complex time to determine is night time, see documentation of
  582. * the calc class for details.
  583. *
  584. * In General, the idea is to automatically fill as much as possible, but
  585. * if the user decides to change these times, accept the inputs, as long as
  586. * they are generally valid. We cannot cater for all possibilities, so as long
  587. * as the time the user has input is a valid time <= Total Time, it can be
  588. * accepted to the database.
  589. */
  590. void NewFlight::on_spseTimeLineEdit_editingFinished()
  591. {
  592. ui->spseTimeLineEdit->setText(calc::formatTimeInput(ui->spseTimeLineEdit->text()));
  593. if(ui->spseTimeLineEdit->text() == ""){
  594. ui->spseTimeLineEdit->setStyleSheet("border: 1px solid red");
  595. ui->spseTimeLineEdit->setFocus();
  596. }else{
  597. ui->spseTimeLineEdit->setStyleSheet("");
  598. }
  599. }
  600. void NewFlight::on_spmeTimeLineEdit_editingFinished()
  601. {
  602. ui->spmeTimeLineEdit->setText(calc::formatTimeInput(ui->spmeTimeLineEdit->text()));
  603. if(ui->spmeTimeLineEdit->text() == ""){
  604. ui->spmeTimeLineEdit->setStyleSheet("border: 1px solid red");
  605. ui->spmeTimeLineEdit->setFocus();
  606. }else{
  607. ui->spmeTimeLineEdit->setStyleSheet("");
  608. }
  609. }
  610. void NewFlight::on_mpTimeLineEdit_editingFinished()
  611. {
  612. ui->mpTimeLineEdit->setText(calc::formatTimeInput(ui->mpTimeLineEdit->text()));
  613. if(ui->mpTimeLineEdit->text() == ""){
  614. ui->mpTimeLineEdit->setStyleSheet("border: 1px solid red");
  615. ui->mpTimeLineEdit->setFocus();
  616. }else{
  617. ui->mpTimeLineEdit->setStyleSheet("");
  618. }
  619. }
  620. void NewFlight::on_totalTimeLineEdit_editingFinished()
  621. {
  622. ui->totalTimeLineEdit->setText(calc::formatTimeInput(ui->totalTimeLineEdit->text()));
  623. if(ui->totalTimeLineEdit->text() == ""){
  624. ui->totalTimeLineEdit->setStyleSheet("border: 1px solid red");
  625. ui->totalTimeLineEdit->setFocus();
  626. }else{
  627. ui->totalTimeLineEdit->setStyleSheet("");
  628. }
  629. }
  630. void NewFlight::on_ifrTimeLineEdit_editingFinished()
  631. {
  632. ui->ifrTimeLineEdit->setText(calc::formatTimeInput(ui->ifrTimeLineEdit->text()));
  633. if(ui->ifrTimeLineEdit->text() == ""){
  634. ui->ifrTimeLineEdit->setStyleSheet("border: 1px solid red");
  635. ui->ifrTimeLineEdit->setFocus();
  636. }else{
  637. ui->ifrTimeLineEdit->setStyleSheet("");
  638. }
  639. }
  640. void NewFlight::on_vfrTimeLineEdit_editingFinished()
  641. {
  642. ui->vfrTimeLineEdit->setText(calc::formatTimeInput(ui->vfrTimeLineEdit->text()));
  643. if(ui->vfrTimeLineEdit->text() == ""){
  644. ui->vfrTimeLineEdit->setStyleSheet("border: 1px solid red");
  645. ui->vfrTimeLineEdit->setFocus();
  646. }else{
  647. ui->vfrTimeLineEdit->setStyleSheet("");
  648. }
  649. }
  650. void NewFlight::on_nightTimeLineEdit_editingFinished()
  651. {
  652. ui->nightTimeLineEdit->setText(calc::formatTimeInput(ui->nightTimeLineEdit->text()));
  653. if(ui->nightTimeLineEdit->text() == ""){
  654. ui->nightTimeLineEdit->setStyleSheet("border: 1px solid red");
  655. ui->nightTimeLineEdit->setFocus();
  656. }else{
  657. ui->nightTimeLineEdit->setStyleSheet("");
  658. }
  659. }
  660. void NewFlight::on_xcTimeLineEdit_editingFinished()
  661. {
  662. ui->xcTimeLineEdit->setText(calc::formatTimeInput(ui->xcTimeLineEdit->text()));
  663. if(ui->xcTimeLineEdit->text() == ""){
  664. ui->xcTimeLineEdit->setStyleSheet("border: 1px solid red");
  665. ui->xcTimeLineEdit->setFocus();
  666. }else{
  667. ui->xcTimeLineEdit->setStyleSheet("");
  668. }
  669. }
  670. void NewFlight::on_picTimeLineEdit_editingFinished()
  671. {
  672. ui->picTimeLineEdit->setText(calc::formatTimeInput(ui->picTimeLineEdit->text()));
  673. if(ui->picTimeLineEdit->text() == ""){
  674. ui->picTimeLineEdit->setStyleSheet("border: 1px solid red");
  675. ui->picTimeLineEdit->setFocus();
  676. }else{
  677. ui->picTimeLineEdit->setStyleSheet("");
  678. }
  679. }
  680. void NewFlight::on_copTimeLineEdit_editingFinished()
  681. {
  682. ui->copTimeLineEdit->setText(calc::formatTimeInput(ui->copTimeLineEdit->text()));
  683. if(ui->copTimeLineEdit->text() == ""){
  684. ui->copTimeLineEdit->setStyleSheet("border: 1px solid red");
  685. ui->copTimeLineEdit->setFocus();
  686. }else{
  687. ui->copTimeLineEdit->setStyleSheet("");
  688. }
  689. }
  690. void NewFlight::on_dualTimeLineEdit_editingFinished()
  691. {
  692. ui->dualTimeLineEdit->setText(calc::formatTimeInput(ui->dualTimeLineEdit->text()));
  693. if(ui->dualTimeLineEdit->text() == ""){
  694. ui->dualTimeLineEdit->setStyleSheet("border: 1px solid red");
  695. ui->dualTimeLineEdit->setFocus();
  696. }else{
  697. ui->dualTimeLineEdit->setStyleSheet("");
  698. }
  699. }
  700. void NewFlight::on_fiTimeLineEdit_editingFinished()
  701. {
  702. ui->fiTimeLineEdit->setText(calc::formatTimeInput(ui->fiTimeLineEdit->text()));
  703. if(ui->fiTimeLineEdit->text() == ""){
  704. ui->fiTimeLineEdit->setStyleSheet("border: 1px solid red");
  705. ui->fiTimeLineEdit->setFocus();
  706. }else{
  707. ui->fiTimeLineEdit->setStyleSheet("");
  708. }
  709. }
  710. void NewFlight::on_simTimeLineEdit_editingFinished()
  711. {
  712. ui->simTimeLineEdit->setText(calc::formatTimeInput(ui->simTimeLineEdit->text()));
  713. if(ui->simTimeLineEdit->text() == ""){
  714. ui->simTimeLineEdit->setStyleSheet("border: 1px solid red");
  715. ui->simTimeLineEdit->setFocus();
  716. }else{
  717. ui->simTimeLineEdit->setStyleSheet("");
  718. }
  719. }
  720. /*
  721. * Control Buttons
  722. */
  723. void NewFlight::on_buttonBox_accepted()
  724. {
  725. on_newDoft_editingFinished();// - activate slots in case user has been active in last input before clicking submit
  726. //on_newTonb_editingFinished();
  727. //on_newTofb_editingFinished();
  728. //on_newDept_editingFinished();
  729. //on_newDest_editingFinished();
  730. on_newAcft_editingFinished();
  731. on_newPicNameLineEdit_editingFinished();
  732. QVector<QString> flight;
  733. flight = collectInput();
  734. if(verifyInput())
  735. {
  736. dbFlight::commitFlight(flight);
  737. qDebug() << flight << "Has been commited.";
  738. QMessageBox msgBox(this);
  739. msgBox.setText("Flight has been commited.");
  740. msgBox.exec();
  741. }else
  742. {
  743. qDebug() << "Invalid Input. No entry has been made in the database.";
  744. dbFlight::commitToScratchpad(flight);
  745. QMessageBox msgBox(this);
  746. msgBox.setText("Invalid entries detected. Please check your input.");
  747. msgBox.exec();
  748. //NewFlight nf(this);
  749. //nf.exec();
  750. }
  751. }
  752. void NewFlight::on_buttonBox_rejected()
  753. {
  754. qDebug() << "NewFlight: Rejected\n";
  755. }
  756. /// Input Verification and Collection
  757. QVector<QString> NewFlight::collectInput()
  758. {
  759. // Collect input from the user fields and return a vector containing the flight information
  760. QString doft = date.toString(Qt::ISODate); // Format Date
  761. QTime tblk = calc::blocktime(tofb,tonb); // Calculate Blocktime
  762. // Prepare Vector for commit to database
  763. flight = dbFlight::createFlightVectorFromInput(doft, dept, tofb, dest, tonb, tblk, pic, acft);
  764. qDebug() << "Created flight vector:" << flight;
  765. return flight;
  766. }
  767. /*!
  768. * \brief NewFlight::verifyInput checks if input exists in database.
  769. * \return
  770. */
  771. bool NewFlight::verifyInput()
  772. {
  773. bool deptValid = false;
  774. bool tofbValid = false;
  775. bool destValid = false;
  776. bool tonbValid = false;
  777. bool tblkValid = false;
  778. bool picValid = false;
  779. bool acftValid = false;
  780. QTime tblk = calc::blocktime(tofb,tonb);
  781. int checktblk = calc::time_to_minutes(tblk);
  782. bool doftValid = true; //doft assumed to be always valid due to QDateTimeEdit constraints
  783. qDebug() << "NewFlight::verifyInput() says: Date:" << doft << " - Valid?\t" << doftValid;
  784. deptValid = dbAirport::checkICAOValid(dept);
  785. qDebug() << "NewFlight::verifyInput() says: Departure is:\t" << dept << " - Valid?\t" << deptValid;
  786. destValid = dbAirport::checkICAOValid(dest);
  787. qDebug() << "NewFlight::verifyInput() says: Destination is:\t" << dest << " - Valid?\t" << destValid;
  788. tofbValid = (unsigned)(calc::time_to_minutes(tofb)-0) <= (1440-0) && tofb.toString("hh:mm") != ""; // Make sure time is within range, DB 1 day = 1440 minutes. 0 is allowed (midnight) & that it is not empty.
  789. qDebug() << "NewFlight::verifyInput() says: tofb is:\t\t" << tofb.toString("hh:mm") << " - Valid?\t" << tofbValid;
  790. tonbValid = (unsigned)(calc::time_to_minutes(tonb)-0) <= (1440-0) && tonb.toString("hh:mm") != ""; // Make sure time is within range, DB 1 day = 1440 minutes
  791. qDebug() << "NewFlight::verifyInput() says: tonb is:\t\t" << tonb.toString("hh:mm")<< " - Valid?\t" << tonbValid;
  792. picValid = (pic.toInt() > 0); // pic should be a pilot_id retreived from the database
  793. qDebug() << "NewFlight::verifyInput() says: pic is pilotd_id:\t" << pic << " - Valid?\t" << picValid;
  794. if(!picValid)
  795. {
  796. QMessageBox msgBox(this);
  797. msgBox.setText("You cannot log a flight without a valid pilot");
  798. msgBox.exec();
  799. }
  800. acftValid = (acft.toInt() > 0);
  801. qDebug() << "NewFlight::verifyInput() says: acft is tail_id:\t" << acft << " - Valid?\t" << acftValid;
  802. if(!acftValid)
  803. {
  804. QMessageBox msgBox(this);
  805. msgBox.setText("You cannot log a flight without a valid aircraft");
  806. msgBox.exec();
  807. }
  808. tblkValid = checktblk != 0;
  809. qDebug() << "NewFlight::verifyInput() says: tblk is:\t\t" << tblk.toString("hh:mm") << " - Valid?\t" << tblkValid;
  810. if(deptValid && tofbValid && destValid && tonbValid
  811. && tblkValid && picValid && acftValid)
  812. {
  813. qDebug() << "====================================================";
  814. qDebug() << "NewFlight::verifyInput() says: All checks passed! Very impressive. ";
  815. return 1;
  816. }else
  817. {
  818. qDebug() << "====================================================";
  819. qDebug() << "NewFlight::verifyInput() says: Flight is invalid.";
  820. return 0;
  821. }
  822. return 0;
  823. }
  824. void NewFlight::returnInput(QVector<QString> flight)
  825. {
  826. // Re-populates the input masks with the selected fields if input was erroneous to allow for corrections to be made
  827. qDebug() << "return Input: " << flight;
  828. ui->newDoft->setDate(QDate::fromString(flight[1],Qt::ISODate));
  829. ui->newDeptLocLineEdit->setText(flight[2]);
  830. ui->newDeptTimeLineEdit->setText(flight[3]);
  831. ui->newDestLocLineEdit->setText(flight[4]);
  832. ui->newDestTimeLineEdit->setText(flight[5]);
  833. // flight[6] is blocktime
  834. ui->newPicNameLineEdit->setText(dbPilots::retreivePilotNameFromID(flight[7]));
  835. ui->newAcft->setText(dbAircraft::retreiveRegistration(flight[8]));
  836. }
  837. /*!
  838. * \brief NewFlight::fillExtrasLineEdits Fills the flight time line edits according to ui selections.
  839. * Neccessary inputs are valid Date, Departure Time and Place, Destination Time and Place,
  840. * PIC name (pilot_id) and Aircraft (tail_id)
  841. */
  842. void NewFlight::fillExtrasLineEdits()
  843. {
  844. QString blockTime = calc::blocktime(tofb,tonb).toString("hh:mm");
  845. DEBUG(blockTime);
  846. QVector<QString> aircraftDetails = dbAircraft::retreiveAircraftDetails(dbAircraft::retreiveAircraftId(acft));
  847. DEBUG("aircraftDetails: " << aircraftDetails);
  848. if(!aircraftDetails.isEmpty()){// valid aircraft
  849. // SP SE
  850. if(aircraftDetails[0] == "1" && aircraftDetails[2] == "1"){
  851. DEBUG("SPSE yes");
  852. tSPSE = blockTime;
  853. ui->spseTimeLineEdit->setText(blockTime);
  854. }
  855. // SP ME
  856. if(aircraftDetails[0] == "1" && aircraftDetails[3] == "1"){
  857. DEBUG("SP ME yes");
  858. tSPME = blockTime;
  859. ui->spmeTimeLineEdit->setText(blockTime);
  860. }
  861. // MP
  862. if(aircraftDetails[1] == "1"){
  863. DEBUG("Multipilot yes");
  864. tMP = blockTime;
  865. ui->mpTimeLineEdit->setText(blockTime);
  866. }
  867. }else{DEBUG("Aircraft Details Empty");}//invalid aircraft
  868. // TOTAL
  869. ui->totalTimeLineEdit->setText(blockTime);
  870. // IFR
  871. if(ui->IfrCheckBox->isChecked()){
  872. ui->ifrTimeLineEdit->setText(blockTime);
  873. ui->vfrTimeLineEdit->setText("");
  874. }
  875. // VFR
  876. if(ui->VfrCheckBox->isChecked()){
  877. ui->vfrTimeLineEdit->setText(blockTime);
  878. ui->ifrTimeLineEdit->setText("");
  879. }
  880. // Night
  881. QString deptDate = date.toString(Qt::ISODate) + 'T' + tofb.toString("hh:mm");
  882. QDateTime deptDateTime = QDateTime::fromString(deptDate,"yyyy-MM-ddThh:mm");
  883. int tblk = calc::time_to_minutes(calc::blocktime(tofb,tonb));
  884. QString nightTime = calc::minutes_to_string(
  885. QString::number(
  886. calc::calculateNightTime(
  887. dept, dest, deptDateTime, tblk)));
  888. ui->nightTimeLineEdit->setText(nightTime);
  889. // XC - Cross-country flight, if more than 50nm long
  890. if(calc::greatCircleDistanceBetweenAirports(dept,dest) >= 50){
  891. qDebug() << "Cross-country Flight: nm = " << calc::greatCircleDistanceBetweenAirports(dept,dest);
  892. ui->xcTimeLineEdit->setText(blockTime);
  893. }else{ui->xcTimeLineEdit->setText("00:00");}
  894. // Function times
  895. switch (ui->FunctionComboBox->currentIndex()) {
  896. case 0://PIC
  897. ui->picTimeLineEdit->setText(blockTime);
  898. ui->copTimeLineEdit->setText("");
  899. ui->dualTimeLineEdit->setText("");
  900. ui->fiTimeLineEdit->setText("");
  901. break;
  902. case 1://Co-Pilot
  903. ui->picTimeLineEdit->setText("");
  904. ui->copTimeLineEdit->setText(blockTime);
  905. ui->dualTimeLineEdit->setText("");
  906. ui->fiTimeLineEdit->setText("");
  907. break;
  908. case 2://Dual
  909. ui->picTimeLineEdit->setText("");
  910. ui->copTimeLineEdit->setText("");
  911. ui->dualTimeLineEdit->setText(blockTime);
  912. ui->fiTimeLineEdit->setText("");
  913. break;
  914. case 3://Instructor
  915. ui->picTimeLineEdit->setText("");
  916. ui->copTimeLineEdit->setText("");
  917. ui->dualTimeLineEdit->setText("");
  918. ui->fiTimeLineEdit->setText(blockTime);
  919. }
  920. // SIM
  921. }
  922. /*!
  923. * \brief In case the user wants to manually edit the extra times
  924. * he can do so in this tab.
  925. */
  926. void NewFlight::on_tabWidget_currentChanged(int index)
  927. {
  928. if(index == 1){// Edit Details tab
  929. if(verifyInput()){
  930. fillExtrasLineEdits();
  931. }else{
  932. auto errorbox = new QMessageBox;
  933. errorbox->setText("Invalid Inputs. Unable to determine times automatically.\nPlease verify your inputs.");
  934. errorbox->exec();
  935. }
  936. }
  937. }