newflightdialog.cpp 54 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316
  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 "newflightdialog.h"
  19. #include "ui_newflight.h"
  20. #include "src/gui/dialogues/newtaildialog.h"
  21. #include "src/gui/dialogues/newpilotdialog.h"
  22. #include "src/functions/acalc.h"
  23. #include "src/testing/atimer.h"
  24. #include "src/database/adatabase.h"
  25. #include "src/oplconstants.h"
  26. #include "src/testing/adebug.h"
  27. ///////////////////////////////////////////////////////////////////////////////////////////////////
  28. /// constants ///
  29. ///////////////////////////////////////////////////////////////////////////////////////////////////
  30. static const auto EMPTY_STRING=QStringLiteral("");
  31. static const auto NAME_RX = QLatin1String("((\\p{L}+)?('|\\-|,)?(\\p{L}+)?)");
  32. static const auto ADD_NAME_RX = QLatin1String("(\\s?(\\p{L}+('|\\-|,)?\\p{L}+?))?");
  33. static const auto TIME_VALID_RGX = QRegularExpression(
  34. "([01]?[0-9]|2[0-3]):?[0-5][0-9]?");
  35. static const auto LOC_VALID_RGX = QRegularExpression(
  36. "[a-zA-Z0-9]{1,4}");
  37. static const auto AIRCRAFT_VALID_RGX = QRegularExpression(
  38. "\\w+\\-?(\\w+)?");
  39. static const auto NAME_VALID_RGX = QRegularExpression(
  40. NAME_RX + ADD_NAME_RX + ADD_NAME_RX + ADD_NAME_RX + ",?\\s?" // up to 4 first names
  41. + NAME_RX + ADD_NAME_RX + ADD_NAME_RX + ADD_NAME_RX ); // up to 4 last names
  42. static const auto DATE_VALID_RGX = QRegularExpression(
  43. "^([1-9][0-9]{3}).?(1[0-2]|0[1-9]).?(3[01]|0[1-9]|[12][0-9])?$");
  44. static const auto SELF_RX = QRegularExpression(
  45. "self", QRegularExpression::CaseInsensitiveOption);
  46. static const auto MANDATORY_LINE_EDITS_DISPLAY_NAMES = QMap<int, QString> {
  47. {0, QStringLiteral("Date of Flight")}, {1, QStringLiteral("Departure Airport")},
  48. {2, QStringLiteral("Destination Airport")}, {3, QStringLiteral("Time Off Blocks")},
  49. {4, QStringLiteral("Time on Blocks")}, {5, QStringLiteral("PIC Name")},
  50. {6, QStringLiteral("Aircraft Registration")}
  51. };
  52. ///////////////////////////////////////////////////////////////////////////////////////////////////
  53. /// Construction ///
  54. ///////////////////////////////////////////////////////////////////////////////////////////////////
  55. /// [F] The general idea for this dialog is this:
  56. /// - Most line edits have validators and completers.
  57. /// - Validators are based on regular expressions, serving as raw input validation
  58. /// - The Completers are based off the database and provide auto-completion
  59. /// - mandatory line edits only emit editing finished if their content has passed
  60. /// raw input validation or focus is lost.
  61. /// - Editing finished triggers validating inputs by mapping them to Database values
  62. /// where required and results in either pass or fail.
  63. /// - A QBitArray is mainained containing the state of validity of the mandatory line edits
  64. /// - The deducted entries are automatically filled if all mandatory entries
  65. /// are valid.
  66. /// - Comitting an entry to the database is only allowed if all mandatory inputs are valid.
  67. ///
  68. /// if the user presses "OK", check if all mandatory inputs are valid,
  69. /// check if optional user inputs are valid and commit.
  70. ///
  71. /// For the completion and mapping, I have settled on a more low-level approach using
  72. /// Completers based on QStringLists and mapping with QMaps.
  73. ///
  74. /// I implemented the Completers and mapping based on a QSqlTableModel which would
  75. /// have been quite nice, since it would keep all data in one place, providing both completion
  76. /// and mapping in one model.
  77. /// But as we have seen before with the more high-level qt classes, they are quite slow on execution
  78. /// when used for tasks they were probably not designed to do.
  79. /// Mapping a registration to an ID for example took around 300ms, which is very
  80. /// noticeable in the UI and not an acceptable user experience. Using QStringLists and QMaps
  81. /// this goes down to around 5ms.
  82. NewFlightDialog::NewFlightDialog(QWidget *parent) :
  83. QDialog(parent),
  84. ui(new Ui::NewFlight)
  85. {
  86. ui->setupUi(this);
  87. flightEntry = AFlightEntry();
  88. setup();
  89. if (ASettings::read(ASettings::NewFlight::FunctionComboBox).toString() == "SIC") {
  90. ui->picNameLineEdit->setText(QStringLiteral(""));
  91. ui->secondPilotNameLineEdit->setText("self");
  92. }
  93. if(ASettings::read(ASettings::NewFlight::FunctionComboBox).toString() == "PIC"){
  94. ui->picNameLineEdit->setText("self");
  95. emit ui->picNameLineEdit->editingFinished();
  96. }
  97. }
  98. NewFlightDialog::NewFlightDialog(int row_id, QWidget *parent) :
  99. QDialog(parent),
  100. ui(new Ui::NewFlight)
  101. {
  102. ui->setupUi(this);
  103. flightEntry = aDB->getFlightEntry(row_id);
  104. setup();
  105. formFiller();
  106. }
  107. NewFlightDialog::~NewFlightDialog()
  108. {
  109. delete ui;
  110. }
  111. ///////////////////////////////////////////////////////////////////////////////////////////////////
  112. /// Methods - setup and maintenance of dialog ///
  113. ///////////////////////////////////////////////////////////////////////////////////////////////////
  114. void NewFlightDialog::setup()
  115. {
  116. for (const auto & approach : Opl::ApproachTypes){
  117. ui->ApproachComboBox->addItem(approach);
  118. }
  119. updateEnabled = true;
  120. setupButtonGroups();
  121. setupRawInputValidation();
  122. setupSignalsAndSlots();
  123. readSettings();
  124. // Visually mark mandatory fields
  125. ui->deptLocLineEdit->setStyleSheet("border: 0.1ex solid #3daee9");
  126. ui->destLocLineEdit->setStyleSheet("border: 0.1ex solid #3daee9");
  127. ui->tofbTimeLineEdit->setStyleSheet("border: 0.1ex solid #3daee9");
  128. ui->tonbTimeLineEdit->setStyleSheet("border: 0.1ex solid #3daee9");
  129. ui->picNameLineEdit->setStyleSheet("border: 0.1ex solid #3daee9");
  130. ui->acftLineEdit->setStyleSheet("border: 0.1ex solid #3daee9");
  131. ui->doftLineEdit->setText(QDate::currentDate().toString(Qt::ISODate));
  132. emit ui->doftLineEdit->editingFinished();
  133. ui->deptLocLineEdit->setFocus();
  134. }
  135. void NewFlightDialog::readSettings()
  136. {
  137. DEB << "Reading Settings...";
  138. QSettings settings;
  139. ui->FunctionComboBox->setCurrentText(ASettings::read(ASettings::FlightLogging::Function).toString());
  140. ui->ApproachComboBox->setCurrentIndex(ASettings::read(ASettings::FlightLogging::Approach).toInt());
  141. ASettings::read(ASettings::FlightLogging::PilotFlying).toBool() ? ui->PilotFlyingCheckBox->setChecked(true)
  142. : ui->PilotMonitoringCheckBox->setChecked(true);
  143. ui->TakeoffSpinBox->setValue(ASettings::read(ASettings::FlightLogging::NumberTakeoffs).toInt());
  144. ui->TakeoffSpinBox->value() > 0 ? ui->TakeoffCheckBox->setChecked(true)
  145. : ui->TakeoffCheckBox->setChecked(false);
  146. ui->LandingSpinBox->setValue(ASettings::read(ASettings::FlightLogging::NumberLandings).toInt());
  147. ui->LandingSpinBox->value() > 0 ? ui->LandingCheckBox->setChecked(true)
  148. : ui->LandingCheckBox->setChecked(false);
  149. if (ASettings::read(ASettings::FlightLogging::LogIFR).toBool()) {
  150. ui->IfrCheckBox->setChecked(true);
  151. } else {
  152. ui->VfrCheckBox->setChecked(true);
  153. }
  154. ui->FlightNumberLineEdit->setText(ASettings::read(ASettings::FlightLogging::FlightNumberPrefix).toString());
  155. ui->calendarCheckBox->setChecked(ASettings::read(ASettings::FlightLogging::PopupCalendar).toBool());
  156. // Debug
  157. ASettings::write(ASettings::FlightLogging::FlightTimeFormat, Opl::Time::Default);
  158. //[F]: Support for Decimal Logging is not implemented yet.
  159. flightTimeFormat = static_cast<Opl::Time::FlightTimeFormat>(
  160. ASettings::read(ASettings::FlightLogging::FlightTimeFormat).toInt());
  161. }
  162. void NewFlightDialog::writeSettings()
  163. {
  164. DEB << "Writing Settings...";
  165. ASettings::write(ASettings::FlightLogging::Function, ui->FunctionComboBox->currentText());
  166. ASettings::write(ASettings::FlightLogging::Approach, ui->ApproachComboBox->currentIndex());
  167. ASettings::write(ASettings::FlightLogging::PilotFlying, ui->PilotFlyingCheckBox->isChecked());
  168. ASettings::write(ASettings::FlightLogging::NumberTakeoffs, ui->TakeoffSpinBox->value());
  169. ASettings::write(ASettings::FlightLogging::NumberLandings, ui->LandingSpinBox->value());
  170. ASettings::write(ASettings::FlightLogging::LogIFR, ui->IfrCheckBox->isChecked());
  171. ASettings::write(ASettings::FlightLogging::PopupCalendar, ui->calendarCheckBox->isChecked());
  172. }
  173. void NewFlightDialog::setupButtonGroups()
  174. {
  175. QButtonGroup *FlightRulesGroup = new QButtonGroup(this);
  176. FlightRulesGroup->addButton(ui->IfrCheckBox);
  177. FlightRulesGroup->addButton(ui->VfrCheckBox);
  178. QButtonGroup *PilotTaskGroup = new QButtonGroup(this);
  179. PilotTaskGroup->addButton(ui->PilotFlyingCheckBox);
  180. PilotTaskGroup->addButton(ui->PilotMonitoringCheckBox);
  181. }
  182. void NewFlightDialog::setupRawInputValidation()
  183. {
  184. // get Maps
  185. pilotsIdMap = aDB->getIdMap(ADatabaseTarget::pilots);
  186. tailsIdMap = aDB->getIdMap(ADatabaseTarget::tails);
  187. airportIcaoIdMap = aDB->getIdMap(ADatabaseTarget::airport_identifier_icao);
  188. airportIataIdMap = aDB->getIdMap(ADatabaseTarget::airport_identifier_iata);
  189. airportNameIdMap = aDB->getIdMap(ADatabaseTarget::airport_names);
  190. //get Completer Lists
  191. pilotList = aDB->getCompletionList(ADatabaseTarget::pilots);
  192. tailsList = aDB->getCompletionList(ADatabaseTarget::registrations);
  193. airportList = aDB->getCompletionList(ADatabaseTarget::airport_identifier_all);
  194. auto tempList = QStringList();
  195. // define tuples
  196. const std::tuple<QString, QStringList*, QRegularExpression>
  197. location_line_edit_settings {QStringLiteral("Loc"), &airportList, LOC_VALID_RGX};
  198. const std::tuple<QString, QStringList*, QRegularExpression>
  199. name_line_edit_settings {QStringLiteral("Name"), &pilotList, NAME_VALID_RGX};
  200. const std::tuple<QString, QStringList*, QRegularExpression>
  201. acft_line_edit_settings {QStringLiteral("acft"), &tailsList, AIRCRAFT_VALID_RGX};
  202. const std::tuple<QString, QStringList*, QRegularExpression>
  203. time_line_edit_settings {QStringLiteral("Time"), &tempList, TIME_VALID_RGX};
  204. const QList<std::tuple<QString, QStringList*, QRegularExpression>> line_edit_settings = {
  205. location_line_edit_settings,
  206. name_line_edit_settings,
  207. acft_line_edit_settings,
  208. time_line_edit_settings
  209. };
  210. //get line edits, set up completers and validators
  211. auto line_edits = ui->flightDataTab->findChildren<QLineEdit*>();
  212. for (const auto &item : line_edit_settings) {
  213. for (const auto &line_edit : line_edits) {
  214. if(line_edit->objectName().contains(std::get<0>(item))) {
  215. DEB << "Setting up: " << line_edit->objectName();
  216. // Set Validator
  217. auto validator = new QRegularExpressionValidator(std::get<2>(item), line_edit);
  218. line_edit->setValidator(validator);
  219. // Set Completer
  220. auto completer = new QCompleter(*std::get<1>(item), line_edit);
  221. completer->setCaseSensitivity(Qt::CaseInsensitive);
  222. completer->setCompletionMode(QCompleter::PopupCompletion);
  223. completer->setFilterMode(Qt::MatchContains);
  224. line_edit->setCompleter(completer);
  225. }
  226. }
  227. }
  228. // populate Mandatory Line Edits list and prepare QBitArray
  229. mandatoryLineEdits = {
  230. ui->doftLineEdit,
  231. ui->deptLocLineEdit,
  232. ui->destLocLineEdit,
  233. ui->tofbTimeLineEdit,
  234. ui->tonbTimeLineEdit,
  235. ui->picNameLineEdit,
  236. ui->acftLineEdit,
  237. };
  238. primaryTimeLineEdits = {
  239. ui->tofbTimeLineEdit,
  240. ui->tonbTimeLineEdit
  241. };
  242. pilotsLineEdits = {
  243. ui->picNameLineEdit,
  244. ui->secondPilotNameLineEdit,
  245. ui->thirdPilotNameLineEdit
  246. };
  247. mandatoryLineEditsGood.resize(mandatoryLineEdits.size());
  248. }
  249. void NewFlightDialog::setupSignalsAndSlots()
  250. {
  251. auto line_edits = this->findChildren<QLineEdit*>();
  252. for (const auto &line_edit : line_edits){
  253. line_edit->installEventFilter(this);
  254. if(line_edit->objectName().contains(QStringLiteral("Loc"))){
  255. QObject::connect(line_edit, &QLineEdit::textChanged,
  256. this, &NewFlightDialog::onToUpperTriggered_textChanged);
  257. }
  258. if(line_edit->objectName().contains(QStringLiteral("acft"))){
  259. QObject::connect(line_edit, &QLineEdit::textChanged,
  260. this, &NewFlightDialog::onToUpperTriggered_textChanged);
  261. }
  262. if(line_edit->objectName().contains(QStringLiteral("Name"))){
  263. QObject::connect(line_edit, &QLineEdit::editingFinished,
  264. this, &NewFlightDialog::onPilotNameLineEdit_editingFinished);
  265. }
  266. if(line_edit->objectName().contains(QStringLiteral("Time"))){
  267. QObject::connect(line_edit, &QLineEdit::editingFinished,
  268. this, &NewFlightDialog::onTimeLineEdit_editingFinished);
  269. }
  270. }
  271. #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
  272. for (const auto &line_edit : qAsConst(mandatoryLineEdits)) {
  273. if(line_edit->objectName().contains(QStringLiteral("doft")))
  274. break;
  275. QObject::connect(line_edit->completer(), QOverload<const QString &>::of(&QCompleter::highlighted),
  276. this, &NewFlightDialog::onCompleter_highlighted);
  277. QObject::connect(line_edit->completer(), QOverload<const QString &>::of(&QCompleter::activated),
  278. this, &NewFlightDialog::onCompleter_activated);
  279. }
  280. #endif
  281. }
  282. void NewFlightDialog::setPopUpCalendarEnabled(bool state)
  283. {
  284. ui->flightDataTabWidget->setCurrentIndex(0);
  285. ui->flightDataTabWidget->removeTab(2); // hide calendar widget
  286. if (state) {
  287. DEB << "Enabling pop-up calendar widget...";
  288. ui->calendarWidget->installEventFilter(this);
  289. ui->placeLabel1->installEventFilter(this);
  290. ui->doftLineEdit->installEventFilter(this);
  291. QObject::connect(ui->calendarWidget, &QCalendarWidget::clicked,
  292. this, &NewFlightDialog::onCalendarWidget_clicked);
  293. QObject::connect(ui->calendarWidget, &QCalendarWidget::activated,
  294. this, &NewFlightDialog::onCalendarWidget_selected);
  295. } else {
  296. DEB << "Disabling pop-up calendar widget...";
  297. ui->calendarWidget->removeEventFilter(this);
  298. ui->placeLabel1->removeEventFilter(this);
  299. ui->doftLineEdit->removeEventFilter(this);
  300. QObject::disconnect(ui->calendarWidget, &QCalendarWidget::clicked,
  301. this, &NewFlightDialog::onCalendarWidget_clicked);
  302. QObject::disconnect(ui->calendarWidget, &QCalendarWidget::activated,
  303. this, &NewFlightDialog::onCalendarWidget_selected);
  304. }
  305. }
  306. bool NewFlightDialog::eventFilter(QObject* object, QEvent* event)
  307. {
  308. if (object == ui->doftLineEdit && event->type() == QEvent::MouseButtonPress) {
  309. onDoftLineEdit_entered();
  310. return false; // let the event continue to the edit
  311. }
  312. auto line_edit = qobject_cast<QLineEdit*>(object);
  313. if (line_edit != nullptr) {
  314. if (mandatoryLineEdits.contains(line_edit) && event->type() == QEvent::FocusIn) {
  315. mandatoryLineEditsGood.setBit(mandatoryLineEdits.indexOf(line_edit), false);
  316. DEB << "Editing " << line_edit->objectName();
  317. // set verification bit to false when entering a mandatory line edit
  318. return false;
  319. }
  320. if (mandatoryLineEdits.contains(line_edit) && event->type() == QEvent::KeyPress) {
  321. // show completion menu when pressing down arrow
  322. QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
  323. if (keyEvent->key() == Qt::Key_Down) {
  324. DEB << "Key down event.";
  325. line_edit->completer()->complete();
  326. }
  327. return false;
  328. }
  329. if (line_edit->objectName().contains("Name") && event->type() == QEvent::KeyPress) {
  330. // show completion menu when pressing down arrow
  331. QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
  332. if (keyEvent->key() == Qt::Key_Down) {
  333. DEB << "Key down event.";
  334. line_edit->completer()->complete();
  335. }
  336. return false;
  337. }
  338. }
  339. return false;
  340. }
  341. ///////////////////////////////////////////////////////////////////////////////////////////////////
  342. /// Methods - Input Processing ///
  343. ///////////////////////////////////////////////////////////////////////////////////////////////////
  344. /*!
  345. * \brief Fills the deductable items in the Dialog based on mandatory data ui selections.
  346. */
  347. void NewFlightDialog::fillDeductibleData()
  348. {
  349. // check if mandatory line edits are valid
  350. if (mandatoryLineEditsGood.count(true) != 7) {
  351. return;
  352. }
  353. //zero out labels and line edits to delete previous calculations
  354. QList<QLineEdit*> LE = {ui->tSPSETimeLineEdit, ui->tSPMETimeLineEdit, ui->tMPTimeLineEdit, ui->tIFRTimeLineEdit,
  355. ui->tNIGHTTimeLineEdit,ui->tPICTimeLineEdit, ui->tPICUSTimeLineEdit, ui->tSICTimeLineEdit,
  356. ui->tDUALTimeLineEdit, ui->tFITimeLineEdit,};
  357. QList<QLabel*> LB = {ui->tSPSELabel, ui->tSPMELabel, ui->tMPLabel, ui->tIFRLabel, ui->tNIGHTLabel,
  358. ui->tPICLabel, ui->tPICUSLabel, ui->tSICLabel, ui->tDUALLabel, ui->tFILabel};
  359. for(const auto& widget : LE) {widget->setText(EMPTY_STRING);}
  360. for(const auto& widget : LB) {widget->setText(Opl::Db::NULL_TIME_hhmm);}
  361. //Calculate block time
  362. const auto tofb = ATime::fromString(ui->tofbTimeLineEdit->text());
  363. const auto tonb = ATime::fromString(ui->tonbTimeLineEdit->text());
  364. const auto tblk = ATime::blocktime(tofb, tonb);
  365. const auto block_time_string = ATime::toString(tblk, flightTimeFormat);
  366. const auto block_minutes = ATime::toMinutes(tblk);
  367. ui->tblkTimeLineEdit->setText(block_time_string);
  368. // get acft data and fill deductible entries
  369. auto acft = aDB->getTailEntry(tailsIdMap.value(ui->acftLineEdit->text()));
  370. if (acft.getData().isEmpty())
  371. DEB << "Error: No valid aircraft object available, unable to deterime auto times.";
  372. // SP SE
  373. if(acft.getData().value(Opl::Db::TAILS_MULTIPILOT).toInt() == 0
  374. && acft.getData().value(Opl::Db::TAILS_MULTIENGINE).toInt() == 0){
  375. ui->tSPSETimeLineEdit->setText(block_time_string);
  376. ui->tSPSELabel->setText(block_time_string);
  377. }
  378. // SP ME
  379. if(acft.getData().value(Opl::Db::TAILS_MULTIPILOT).toInt() == 0
  380. && acft.getData().value(Opl::Db::TAILS_MULTIENGINE).toInt() == 1){
  381. ui->tSPMETimeLineEdit->setText(block_time_string);
  382. ui->tSPMELabel->setText(block_time_string);
  383. }
  384. // MP
  385. if(acft.getData().value(Opl::Db::TAILS_MULTIPILOT).toInt() == 1){
  386. ui->tMPTimeLineEdit->setText(block_time_string);
  387. ui->tMPLabel->setText(block_time_string);
  388. }
  389. // TOTAL
  390. ui->tblkLabel->setText("<b>" + block_time_string + "</b>");
  391. // IFR
  392. if(ui->IfrCheckBox->isChecked()){
  393. ui->tIFRTimeLineEdit->setText(block_time_string);
  394. ui->tIFRLabel->setText(block_time_string);
  395. }
  396. // Night
  397. auto dept_date = ui->doftLineEdit->text() + 'T'
  398. + ATime::toString(tofb);
  399. auto dept_date_time = QDateTime::fromString(dept_date, QStringLiteral("yyyy-MM-ddThh:mm"));
  400. const int night_angle = ASettings::read(ASettings::FlightLogging::NightAngle).toInt();
  401. auto night_time = ATime::fromMinutes(ACalc::calculateNightTime(
  402. ui->deptLocLineEdit->text(),
  403. ui->destLocLineEdit->text(),
  404. dept_date_time,
  405. block_minutes,
  406. night_angle));
  407. ui->tNIGHTTimeLineEdit->setText(ATime::toString(night_time, flightTimeFormat));
  408. ui->tNIGHTLabel->setText(ATime::toString(night_time, flightTimeFormat));
  409. // Function times
  410. switch (ui->FunctionComboBox->currentIndex()) {
  411. case 0://PIC
  412. ui->tPICTimeLineEdit->setText(block_time_string);
  413. ui->tPICLabel->setText(block_time_string);
  414. break;
  415. case 1://PICus
  416. ui->tPICUSTimeLineEdit->setText(block_time_string);
  417. ui->tPICUSLabel->setText(block_time_string);
  418. break;
  419. case 2://Co-Pilot
  420. ui->tSICTimeLineEdit->setText(block_time_string);
  421. ui->tSICLabel->setText(block_time_string);
  422. break;
  423. case 3://Dual
  424. ui->tDUALTimeLineEdit->setText(block_time_string);
  425. ui->tDUALLabel->setText(block_time_string);
  426. break;
  427. case 4://Instructor
  428. ui->tFITimeLineEdit->setText(block_time_string);
  429. ui->tFILabel->setText(block_time_string);
  430. ui->tPICTimeLineEdit->setText(block_time_string);
  431. ui->tPICLabel->setText(block_time_string);
  432. }
  433. }
  434. /*!
  435. * \brief Collect input and create a Data map for the entry object.
  436. *
  437. * This function should only be called if input validation has been passed, since
  438. * no input validation is done in this step and input data is assumed to be valid.
  439. * \return
  440. */
  441. RowData NewFlightDialog::collectInput()
  442. {
  443. RowData newData;
  444. DEB << "Collecting Input...";
  445. //Block Time
  446. const auto tofb = ATime::fromString(ui->tofbTimeLineEdit->text());
  447. const auto tonb = ATime::fromString(ui->tonbTimeLineEdit->text());
  448. const auto tblk = ATime::blocktime(tofb, tonb);
  449. const auto block_minutes = ATime::toMinutes(tblk);
  450. // Mandatory data
  451. newData.insert(Opl::Db::FLIGHTS_DOFT, ui->doftLineEdit->text());
  452. newData.insert(Opl::Db::FLIGHTS_DEPT, ui->deptLocLineEdit->text());
  453. newData.insert(Opl::Db::FLIGHTS_TOFB, ATime::toMinutes(tofb));
  454. newData.insert(Opl::Db::FLIGHTS_DEST, ui->destLocLineEdit->text());
  455. newData.insert(Opl::Db::FLIGHTS_TONB, ATime::toMinutes(tonb));
  456. newData.insert(Opl::Db::FLIGHTS_TBLK, block_minutes);
  457. // Aircraft
  458. newData.insert(Opl::Db::FLIGHTS_ACFT, tailsIdMap.value(ui->acftLineEdit->text()));
  459. // Pilots
  460. newData.insert(Opl::Db::FLIGHTS_PIC, pilotsIdMap.value(ui->picNameLineEdit->text()));
  461. newData.insert(Opl::Db::FLIGHTS_SECONDPILOT, pilotsIdMap.value(ui->secondPilotNameLineEdit->text()));
  462. newData.insert(Opl::Db::FLIGHTS_THIRDPILOT, pilotsIdMap.value(ui->thirdPilotNameLineEdit->text()));
  463. // Extra Times
  464. ui->tSPSETimeLineEdit->text().isEmpty() ?
  465. newData.insert(Opl::Db::FLIGHTS_TSPSE, EMPTY_STRING)
  466. : newData.insert(Opl::Db::FLIGHTS_TSPSE, stringToMinutes(
  467. ui->tSPSETimeLineEdit->text(), flightTimeFormat));
  468. ui->tSPMETimeLineEdit->text().isEmpty() ?
  469. newData.insert(Opl::Db::FLIGHTS_TSPME, EMPTY_STRING)
  470. : newData.insert(Opl::Db::FLIGHTS_TSPME, stringToMinutes(
  471. ui->tSPMETimeLineEdit->text(), flightTimeFormat));
  472. ui->tMPTimeLineEdit->text().isEmpty() ?
  473. newData.insert(Opl::Db::FLIGHTS_TMP, EMPTY_STRING)
  474. : newData.insert(Opl::Db::FLIGHTS_TMP, stringToMinutes(
  475. ui->tMPTimeLineEdit->text(), flightTimeFormat));
  476. if (ui->IfrCheckBox->isChecked()) {
  477. newData.insert(Opl::Db::FLIGHTS_TIFR, block_minutes);
  478. } else {
  479. newData.insert(Opl::Db::FLIGHTS_TIFR, EMPTY_STRING);
  480. }
  481. // Night
  482. const auto dept_date = ui->doftLineEdit->text() + 'T'
  483. + ATime::toString(tofb);
  484. const auto dept_date_time = QDateTime::fromString(dept_date, QStringLiteral("yyyy-MM-ddThh:mm"));
  485. const int night_angle = ASettings::read(ASettings::FlightLogging::NightAngle).toInt();
  486. const auto night_time = ATime::fromMinutes(ACalc::calculateNightTime(
  487. ui->deptLocLineEdit->text(),
  488. ui->destLocLineEdit->text(),
  489. dept_date_time,
  490. block_minutes,
  491. night_angle));
  492. const auto night_minutes = ATime::toMinutes(night_time);
  493. newData.insert(Opl::Db::FLIGHTS_TNIGHT, night_minutes);
  494. // Function times - This is a little explicit but these are mutually exclusive so its better to be safe than sorry here.
  495. switch (ui->FunctionComboBox->currentIndex()) {
  496. case 0://PIC
  497. newData.insert(Opl::Db::FLIGHTS_TPIC, block_minutes);
  498. newData.insert(Opl::Db::FLIGHTS_TPICUS, EMPTY_STRING);
  499. newData.insert(Opl::Db::FLIGHTS_TSIC, EMPTY_STRING);
  500. newData.insert(Opl::Db::FLIGHTS_TDUAL, EMPTY_STRING);
  501. newData.insert(Opl::Db::FLIGHTS_TFI, EMPTY_STRING);
  502. break;
  503. case 1://PICUS
  504. newData.insert(Opl::Db::FLIGHTS_TPIC, EMPTY_STRING);
  505. newData.insert(Opl::Db::FLIGHTS_TPICUS, block_minutes);
  506. newData.insert(Opl::Db::FLIGHTS_TSIC, EMPTY_STRING);
  507. newData.insert(Opl::Db::FLIGHTS_TDUAL, EMPTY_STRING);
  508. newData.insert(Opl::Db::FLIGHTS_TFI, EMPTY_STRING);
  509. break;
  510. case 2://Co-Pilot
  511. newData.insert(Opl::Db::FLIGHTS_TPIC, EMPTY_STRING);
  512. newData.insert(Opl::Db::FLIGHTS_TPICUS, EMPTY_STRING);
  513. newData.insert(Opl::Db::FLIGHTS_TSIC, block_minutes);
  514. newData.insert(Opl::Db::FLIGHTS_TDUAL, EMPTY_STRING);
  515. newData.insert(Opl::Db::FLIGHTS_TFI, EMPTY_STRING);
  516. break;
  517. case 3://Dual
  518. newData.insert(Opl::Db::FLIGHTS_TPIC, EMPTY_STRING);
  519. newData.insert(Opl::Db::FLIGHTS_TPICUS, EMPTY_STRING);
  520. newData.insert(Opl::Db::FLIGHTS_TSIC, EMPTY_STRING);
  521. newData.insert(Opl::Db::FLIGHTS_TDUAL, block_minutes);
  522. newData.insert(Opl::Db::FLIGHTS_TFI, EMPTY_STRING);
  523. break;
  524. case 4://Instructor
  525. newData.insert(Opl::Db::FLIGHTS_TPIC, block_minutes);
  526. newData.insert(Opl::Db::FLIGHTS_TPICUS, EMPTY_STRING);
  527. newData.insert(Opl::Db::FLIGHTS_TSIC, EMPTY_STRING);
  528. newData.insert(Opl::Db::FLIGHTS_TDUAL, EMPTY_STRING);
  529. newData.insert(Opl::Db::FLIGHTS_TFI, block_minutes);
  530. }
  531. // Pilot Flying
  532. newData.insert(Opl::Db::FLIGHTS_PILOTFLYING, ui->PilotFlyingCheckBox->isChecked());
  533. // TO and LDG - again a bit explicit, but we need to check for both night to day as well as day to night transitions.
  534. if (ui->TakeoffCheckBox->isChecked()) {
  535. if (night_minutes == 0) { // all day
  536. newData.insert(Opl::Db::FLIGHTS_TODAY, ui->TakeoffSpinBox->value());
  537. newData.insert(Opl::Db::FLIGHTS_TONIGHT, 0);
  538. } else if (night_minutes == block_minutes) { // all night
  539. newData.insert(Opl::Db::FLIGHTS_TODAY, 0);
  540. newData.insert(Opl::Db::FLIGHTS_TONIGHT, ui->TakeoffSpinBox->value());
  541. } else {
  542. if(ACalc::isNight(ui->deptLocLineEdit->text(), dept_date_time, night_angle)) {
  543. newData.insert(Opl::Db::FLIGHTS_TODAY, 0);
  544. newData.insert(Opl::Db::FLIGHTS_TONIGHT, ui->TakeoffSpinBox->value());
  545. } else {
  546. newData.insert(Opl::Db::FLIGHTS_TODAY, ui->TakeoffSpinBox->value());
  547. newData.insert(Opl::Db::FLIGHTS_TONIGHT, 0);
  548. }
  549. }
  550. } else {
  551. newData.insert(Opl::Db::FLIGHTS_TODAY, 0);
  552. newData.insert(Opl::Db::FLIGHTS_TONIGHT, 0);
  553. }
  554. if (ui->LandingCheckBox->isChecked()) {
  555. if (night_minutes == 0) { // all day
  556. newData.insert(Opl::Db::FLIGHTS_LDGDAY, ui->LandingSpinBox->value());
  557. newData.insert(Opl::Db::FLIGHTS_LDGNIGHT, 0);
  558. } else if (night_minutes == block_minutes) { // all night
  559. newData.insert(Opl::Db::FLIGHTS_LDGDAY, 0);
  560. newData.insert(Opl::Db::FLIGHTS_LDGNIGHT, ui->LandingSpinBox->value());
  561. } else { //check
  562. const auto dest_date = ui->doftLineEdit->text() + 'T'
  563. + ATime::toString(tonb);
  564. const auto dest_date_time = QDateTime::fromString(dest_date, QStringLiteral("yyyy-MM-ddThh:mm"));
  565. if (ACalc::isNight(ui->destLocLineEdit->text(), dest_date_time, night_angle)) {
  566. newData.insert(Opl::Db::FLIGHTS_LDGDAY, 0);
  567. newData.insert(Opl::Db::FLIGHTS_LDGNIGHT, ui->LandingSpinBox->value());
  568. } else {
  569. newData.insert(Opl::Db::FLIGHTS_LDGDAY, ui->LandingSpinBox->value());
  570. newData.insert(Opl::Db::FLIGHTS_LDGNIGHT, 0);
  571. }
  572. }
  573. } else {
  574. newData.insert(Opl::Db::FLIGHTS_LDGDAY, 0);
  575. newData.insert(Opl::Db::FLIGHTS_LDGNIGHT, 0);
  576. }
  577. newData.insert(Opl::Db::FLIGHTS_AUTOLAND, ui->AutolandSpinBox->value());
  578. newData.insert(Opl::Db::FLIGHTS_APPROACHTYPE, ui->ApproachComboBox->currentText());
  579. newData.insert(Opl::Db::FLIGHTS_FLIGHTNUMBER, ui->FlightNumberLineEdit->text());
  580. newData.insert(Opl::Db::FLIGHTS_REMARKS, ui->RemarksLineEdit->text());
  581. DEB << "New Flight Data: " << newData;
  582. return newData;
  583. }
  584. void NewFlightDialog::formFiller()
  585. {
  586. DEB << "Filling Line Edits...";
  587. // get Line Edits
  588. auto line_edits = this->findChildren<QLineEdit *>();
  589. QStringList line_edits_names;
  590. for (const auto& le : line_edits) {
  591. line_edits_names << le->objectName();
  592. }
  593. ui->acftLineEdit->setText(flightEntry.getRegistration());
  594. line_edits_names.removeOne("acftLineEdit");
  595. for (const auto& data_key : flightEntry.getData().keys()) {
  596. auto rx = QRegularExpression(data_key + "LineEdit");//acftLineEdit
  597. for(const auto& leName : line_edits_names){
  598. if(rx.match(leName).hasMatch()) {
  599. //DEB << "Loc Match found: " << key << " - " << leName);
  600. auto line_edit = this->findChild<QLineEdit *>(leName);
  601. if(line_edit != nullptr){
  602. line_edit->setText(flightEntry.getData().value(data_key).toString());
  603. line_edits_names.removeOne(leName);
  604. }
  605. break;
  606. }
  607. }
  608. rx = QRegularExpression(data_key + "Loc\\w+?");
  609. for(const auto& leName : line_edits_names){
  610. if(rx.match(leName).hasMatch()) {
  611. //DEB << "Loc Match found: " << key << " - " << leName);
  612. auto line_edit = this->findChild<QLineEdit *>(leName);
  613. if(line_edit != nullptr){
  614. line_edit->setText(flightEntry.getData().value(data_key).toString());
  615. line_edits_names.removeOne(leName);
  616. }
  617. break;
  618. }
  619. }
  620. rx = QRegularExpression(data_key + "Time\\w+?");
  621. for(const auto& leName : line_edits_names){
  622. if(rx.match(leName).hasMatch()) {
  623. //DEB << "Time Match found: " << key << " - " << leName);
  624. auto line_edits = this->findChild<QLineEdit *>(leName);
  625. if(line_edits != nullptr){
  626. DEB << "Setting " << line_edits->objectName() << " to " << ATime::toString(flightEntry.getData().value(data_key).toInt(), flightTimeFormat);
  627. line_edits->setText(ATime::toString(flightEntry.getData().value(data_key).toInt(),
  628. flightTimeFormat));
  629. line_edits_names.removeOne(leName);
  630. }
  631. break;
  632. }
  633. }
  634. rx = QRegularExpression(data_key + "Name\\w+?");
  635. for(const auto& leName : line_edits_names){
  636. if(rx.match(leName).hasMatch()) {
  637. auto line_edits = this->findChild<QLineEdit *>(leName);
  638. if(line_edits != nullptr){
  639. DEB << pilotsIdMap.key(1);
  640. line_edits->setText(pilotsIdMap.key(flightEntry.getData().value(data_key).toInt()));
  641. line_edits_names.removeOne(leName);
  642. }
  643. break;
  644. }
  645. }
  646. }
  647. //FunctionComboBox
  648. QList<QLineEdit*> function_combo_boxes = {ui->tPICTimeLineEdit, ui->tPICUSTimeLineEdit,
  649. ui->tSICTimeLineEdit, ui->tDUALTimeLineEdit,
  650. ui->tFITimeLineEdit};
  651. for(const auto& line_edit : function_combo_boxes){
  652. if(line_edit->text() != "00:00"){
  653. QString name = line_edit->objectName();
  654. name.chop(12);
  655. name.remove(0,1);
  656. ui->FunctionComboBox->setCurrentText(name);
  657. }
  658. }
  659. // Approach Combo Box
  660. const QString& app = flightEntry.getData().value(Opl::Db::FLIGHTS_APPROACHTYPE).toString();
  661. if(app != EMPTY_STRING){
  662. ui->ApproachComboBox->setCurrentText(app);
  663. }
  664. // Task and Rules
  665. qint8 PF = flightEntry.getData().value(Opl::Db::FLIGHTS_PILOTFLYING).toInt();
  666. if (PF > 0) {
  667. ui->PilotFlyingCheckBox->setChecked(true);
  668. } else {
  669. ui->PilotMonitoringCheckBox->setChecked(true);
  670. }
  671. qint8 FR = flightEntry.getData().value(Opl::Db::FLIGHTS_TIFR).toInt();
  672. if (FR > 0) {
  673. ui->IfrCheckBox->setChecked(true);
  674. } else {
  675. ui->tIFRTimeLineEdit->setText(EMPTY_STRING);
  676. ui->VfrCheckBox->setChecked(true);
  677. }
  678. // Take Off and Landing
  679. qint8 TO = flightEntry.getData().value(Opl::Db::FLIGHTS_TODAY).toInt()
  680. + flightEntry.getData().value(Opl::Db::FLIGHTS_TONIGHT).toInt();
  681. qint8 LDG = flightEntry.getData().value(Opl::Db::FLIGHTS_LDGDAY).toInt()
  682. + flightEntry.getData().value(Opl::Db::FLIGHTS_LDGNIGHT).toInt();
  683. if(TO > 0) {
  684. ui->TakeoffCheckBox->setChecked(true);
  685. ui->TakeoffSpinBox->setValue(TO);
  686. } else {
  687. ui->TakeoffCheckBox->setChecked(false);
  688. ui->TakeoffSpinBox->setValue(0);
  689. }
  690. if(LDG > 0) {
  691. ui->LandingCheckBox->setChecked(true);
  692. ui->LandingSpinBox->setValue(LDG);
  693. } else {
  694. ui->LandingCheckBox->setChecked(false);
  695. ui->LandingSpinBox->setValue(0);
  696. }
  697. qint8 AL = flightEntry.getData().value(Opl::Db::FLIGHTS_AUTOLAND).toInt();
  698. if(AL > 0) {
  699. ui->AutolandCheckBox->setChecked(true);
  700. ui->AutolandSpinBox->setValue(AL);
  701. }
  702. for(const auto& le : mandatoryLineEdits){
  703. emit le->editingFinished();
  704. }
  705. }
  706. bool NewFlightDialog::isLessOrEqualThanBlockTime(const QString time_string)
  707. {
  708. if (mandatoryLineEditsGood.count(true) != 7){
  709. QMessageBox message_box(this);
  710. message_box.setText("Unable to determine total block time.<br>"
  711. "Please fill out all Mandatory Fields<br>"
  712. "before manually editing these times.");
  713. message_box.exec();
  714. return false;
  715. }
  716. auto extra_time = ATime::fromString(time_string, flightTimeFormat);
  717. auto block_time = ATime::blocktime(ATime::fromString(
  718. ui->tofbTimeLineEdit->text(), flightTimeFormat),
  719. ATime::fromString(
  720. ui->tonbTimeLineEdit->text(), flightTimeFormat));
  721. if (extra_time <= block_time) {
  722. return true;
  723. } else {
  724. QMessageBox message_box(this);
  725. message_box.setWindowTitle("Error");
  726. message_box.setText("The flight time you have entered is longer than the total blocktime:<br><center><b>"
  727. + ATime::toString(block_time, flightTimeFormat)
  728. + "</b></center>");
  729. message_box.exec();
  730. return false;
  731. }
  732. }
  733. /*!
  734. * \brief NewFlight::addNewTailMessageBox If the user input is not in the aircraftList, the user
  735. * is prompted if he wants to add a new entry to the database
  736. */
  737. void NewFlightDialog::addNewTail(QLineEdit *parent_line_edit)
  738. {
  739. QMessageBox::StandardButton reply;
  740. reply = QMessageBox::question(this, "No Aircraft found",
  741. "No aircraft with this registration found.<br>"
  742. "If this is the first time you log a flight with this aircraft, you have to "
  743. "add the registration to the database first.<br><br>Would you like to add a new aircraft to the database?",
  744. QMessageBox::Yes|QMessageBox::No);
  745. if (reply == QMessageBox::Yes) {
  746. DEB << "Add new aircraft selected";
  747. // create and open new aircraft dialog
  748. NewTailDialog na(ui->acftLineEdit->text(), this);
  749. na.exec();
  750. // update map and list, set line edit
  751. tailsIdMap = aDB->getIdMap(ADatabaseTarget::tails);
  752. tailsList = aDB->getCompletionList(ADatabaseTarget::registrations);
  753. DEB << "New Entry added. Id:" << aDB->getLastEntry(ADatabaseTarget::tails);
  754. DEB << "AC Map: " << tailsIdMap;
  755. parent_line_edit->setText(tailsIdMap.key(aDB->getLastEntry(ADatabaseTarget::tails)));
  756. emit parent_line_edit->editingFinished();
  757. } else {
  758. parent_line_edit->setText(EMPTY_STRING);
  759. }
  760. }
  761. /*!
  762. * \brief NewFlight::addNewPilot If the user input is not in the pilotNameList, the user
  763. * is prompted if he wants to add a new entry to the database
  764. */
  765. void NewFlightDialog::addNewPilot(QLineEdit *parent_line_edit)
  766. {
  767. QMessageBox::StandardButton reply;
  768. reply = QMessageBox::question(this, "No Pilot found",
  769. "No pilot found.<br>Please enter the Name as"
  770. "<br><br><center><b>Lastname, Firstname</b></center><br><br>"
  771. "If this is the first time you log a flight with this pilot, you have to "
  772. "add the name to the database first.<br><br>Would you like to add a new pilot to the database?",
  773. QMessageBox::Yes|QMessageBox::No);
  774. if (reply == QMessageBox::Yes) {
  775. DEB << "Add new pilot selected";
  776. // create and open new pilot dialog
  777. NewPilotDialog np(this);
  778. np.exec();
  779. // update map and list, set line edit
  780. pilotsIdMap = aDB->getIdMap(ADatabaseTarget::pilots);
  781. pilotList = aDB->getCompletionList(ADatabaseTarget::pilots);
  782. DEB << "Setting new entry: " << pilotsIdMap.key(aDB->getLastEntry(ADatabaseTarget::pilots));
  783. parent_line_edit->setText(pilotsIdMap.key(aDB->getLastEntry(ADatabaseTarget::pilots)));
  784. emit parent_line_edit->editingFinished();
  785. } else {
  786. parent_line_edit->setText(EMPTY_STRING);
  787. }
  788. }
  789. ///////////////////////////////////////////////////////////////////////////////////////////////////
  790. /// Flight Data Tab Slots ///
  791. ///////////////////////////////////////////////////////////////////////////////////////////////////
  792. void NewFlightDialog::on_cancelButton_clicked()
  793. {
  794. DEB << "Cancel Button clicked.";
  795. reject();
  796. }
  797. void NewFlightDialog::on_submitButton_clicked()
  798. {
  799. for (const auto &line_edit : mandatoryLineEdits) {
  800. emit line_edit->editingFinished();
  801. }
  802. DEB << "editing finished emitted. good count: " << mandatoryLineEditsGood.count(true);
  803. if (mandatoryLineEditsGood.count(true) != 7) {
  804. QString error_message = "Not all mandatory entries are valid.<br>The following"
  805. " item(s) are empty or missing:<br><br><center><b>";
  806. for (int i=0; i < mandatoryLineEditsGood.size(); i++) {
  807. if (!mandatoryLineEditsGood[i]){
  808. error_message.append(MANDATORY_LINE_EDITS_DISPLAY_NAMES.value(i) + "<br>");
  809. mandatoryLineEdits[i]->setStyleSheet("border: 1px solid red");
  810. }
  811. }
  812. error_message.append("</b></center><br>Please go back and fill in the required data.");
  813. QMessageBox message_box(this);
  814. message_box.setText(error_message);
  815. message_box.exec();
  816. return;
  817. }
  818. DEB << "Submit Button clicked. Mandatory good (out of 7): " << mandatoryLineEditsGood.count(true);
  819. auto newData = collectInput();
  820. DEB << "Setting Data for flightEntry...";
  821. flightEntry.setData(newData);
  822. DEB << "Committing...";
  823. if (!aDB->commit(flightEntry)) {
  824. QMessageBox message_box(this);
  825. message_box.setText("The following error has ocurred:\n\n"
  826. + aDB->lastError.text()
  827. + "\n\nYour entry has not been saved.");
  828. message_box.setIcon(QMessageBox::Warning);
  829. message_box.exec();
  830. return;
  831. } else {
  832. QDialog::accept();
  833. }
  834. }
  835. /*
  836. * Shared Slots
  837. */
  838. void NewFlightDialog::onGoodInputReceived(QLineEdit *line_edit)
  839. {
  840. DEB << line_edit->objectName() << " - Good input received - " << line_edit->text();
  841. line_edit->setStyleSheet("");
  842. if (mandatoryLineEdits.contains(line_edit))
  843. mandatoryLineEditsGood.setBit(mandatoryLineEdits.indexOf(line_edit), true);
  844. if (mandatoryLineEditsGood.count(true) == 7)
  845. onMandatoryLineEditsFilled();
  846. DEB << "Mandatory good: " << mandatoryLineEditsGood.count(true)
  847. << " (out of 7) " << mandatoryLineEditsGood;
  848. }
  849. void NewFlightDialog::onBadInputReceived(QLineEdit *line_edit)
  850. {
  851. DEB << line_edit->objectName() << " - Bad input received - " << line_edit->text();
  852. line_edit->setStyleSheet("border: 1px solid red");
  853. DEB << "Mandatory Good: " << mandatoryLineEditsGood.count(true) << " out of "
  854. << mandatoryLineEditsGood.size() << ". Array: " << mandatoryLineEditsGood;
  855. }
  856. // capitalize input for dept, dest and registration input
  857. void NewFlightDialog::onToUpperTriggered_textChanged(const QString &text)
  858. {
  859. auto sender_object = sender();
  860. auto line_edit = this->findChild<QLineEdit*>(sender_object->objectName());
  861. DEB << "Text changed - " << line_edit->objectName() << line_edit->text();
  862. {
  863. const QSignalBlocker blocker(line_edit);
  864. line_edit->setText(text.toUpper());
  865. }
  866. }
  867. // update is disabled if the user chose to manually edit extra times
  868. void NewFlightDialog::onMandatoryLineEditsFilled()
  869. {
  870. if (!(mandatoryLineEditsGood.count(true) == 7)) {
  871. DEB << "erroneously called.";
  872. return;
  873. };
  874. if (updateEnabled)
  875. fillDeductibleData();
  876. DEB << mandatoryLineEditsGood;
  877. }
  878. // make sure that when using keyboard to scroll through completer sugggestions, line edit is up to date
  879. void NewFlightDialog::onCompleter_highlighted(const QString &completion)
  880. {
  881. auto sender_object = sender();
  882. auto line_edit = this->findChild<QLineEdit*>(sender_object->objectName());
  883. DEB << "Completer highlighted - " << line_edit->objectName() << completion;
  884. line_edit->setText(completion);
  885. }
  886. void NewFlightDialog::onCompleter_activated(const QString &)
  887. {
  888. auto sender_object = sender();
  889. auto line_edit = this->findChild<QLineEdit*>(sender_object->objectName());
  890. DEB << "Line edit " << line_edit->objectName() << "completer activated.";
  891. emit line_edit->editingFinished();
  892. }
  893. /*
  894. * Date of Flight
  895. */
  896. void NewFlightDialog::on_doftLineEdit_editingFinished()
  897. {
  898. auto line_edit = ui->doftLineEdit;
  899. auto text = ui->doftLineEdit->text();
  900. auto label = ui->doftDisplayLabel;
  901. DEB << line_edit->objectName() << "Editing finished - " << text;
  902. auto date = QDate::fromString(text, Qt::ISODate);
  903. if (date.isValid()) {
  904. label->setText(date.toString(Qt::TextDate));
  905. onGoodInputReceived(line_edit);
  906. return;
  907. }
  908. //try to correct input if only numbers are entered, eg 20200101
  909. if(text.length() == 8) {
  910. DEB << "Trying to fix input...";
  911. text.insert(4,'-');
  912. text.insert(7,'-');
  913. date = QDate::fromString(text, Qt::ISODate);
  914. if (date.isValid()) {
  915. line_edit->setText(date.toString(Qt::ISODate));
  916. label->setText(date.toString(Qt::TextDate));
  917. onGoodInputReceived(line_edit);
  918. return;
  919. }
  920. }
  921. label->setText("Invalid Date.");
  922. onBadInputReceived(line_edit);
  923. }
  924. void NewFlightDialog::onCalendarWidget_clicked(const QDate &date)
  925. {
  926. const auto& le = ui->doftLineEdit;
  927. le->blockSignals(false);
  928. ui->calendarWidget->hide();
  929. ui->placeLabel1->resize(ui->placeLabel2->size());
  930. le->setText(date.toString(Qt::ISODate));
  931. le->setFocus();
  932. }
  933. void NewFlightDialog::onCalendarWidget_selected(const QDate &date)
  934. {
  935. ui->calendarWidget->hide();
  936. ui->placeLabel1->resize(ui->placeLabel2->size());
  937. ui->doftDisplayLabel->setText(date.toString(Qt::TextDate));
  938. const auto& le = ui->doftLineEdit;
  939. le->setText(date.toString(Qt::ISODate));
  940. le->setFocus();
  941. le->blockSignals(false);
  942. }
  943. void NewFlightDialog::onDoftLineEdit_entered()
  944. {
  945. const auto& cw = ui->calendarWidget;
  946. const auto& le = ui->doftLineEdit;
  947. const auto& anchor = ui->placeLabel1;
  948. if(cw->isVisible()){
  949. le->blockSignals(false);
  950. cw->hide();
  951. anchor->resize(ui->placeLabel2->size());
  952. le->setFocus();
  953. } else {
  954. le->blockSignals(true);
  955. // Determine size based on layout coordinates
  956. int c1 = anchor->pos().rx();
  957. int c2 = le->pos().rx();
  958. int z = le->size().rwidth();
  959. int x = (c2 - c1)+ z;
  960. c1 = anchor->pos().ry();
  961. c2 = ui->acftLineEdit->pos().ry();
  962. z = ui->acftLineEdit->size().height();
  963. int y = (c2 - c1) + z;
  964. //Re-size calendar and parent label accordingly
  965. anchor->resize(x, y);
  966. cw->setParent(ui->placeLabel1);
  967. cw->setGeometry(QRect(0, 0, x, y));
  968. cw->show();
  969. cw->setFocus();
  970. }
  971. }
  972. void NewFlightDialog::on_calendarCheckBox_stateChanged(int arg1)
  973. {
  974. ASettings::write(ASettings::NewFlight::CalendarCheckBox, ui->calendarCheckBox->isChecked());
  975. DEB << "Calendar check box state changed.";
  976. switch (arg1) {
  977. case 0: // unchecked
  978. setPopUpCalendarEnabled(false);
  979. break;
  980. case 2: // checked
  981. setPopUpCalendarEnabled(true);
  982. break;
  983. default:
  984. break;
  985. }
  986. }
  987. /*
  988. * Location Line Edits
  989. */
  990. void NewFlightDialog::on_deptLocLineEdit_editingFinished()
  991. {
  992. onLocationEditingFinished(ui->deptLocLineEdit, ui->deptNameLabel);
  993. }
  994. void NewFlightDialog::on_destLocLineEdit_editingFinished()
  995. {
  996. onLocationEditingFinished(ui->destLocLineEdit, ui->destNameLabel);
  997. }
  998. void NewFlightDialog::onLocationEditingFinished(QLineEdit *line_edit, QLabel *name_label)
  999. {
  1000. const auto &text = line_edit->text();
  1001. //DEB << line_edit->objectName() << " Editing finished. " << text);
  1002. int airport_id = 0;
  1003. // try to map iata or icao code to airport id;
  1004. if (text.length() == 3) {
  1005. airport_id = airportIataIdMap.value(text);
  1006. } else {
  1007. airport_id = airportIcaoIdMap.value(text);
  1008. }
  1009. // check result
  1010. if (airport_id == 0) {
  1011. // to do: prompt user how to handle unknown airport
  1012. name_label->setText("Unknown airport identifier");
  1013. onBadInputReceived(line_edit);
  1014. return;
  1015. }
  1016. line_edit->setText(airportIcaoIdMap.key(airport_id));
  1017. name_label->setText(airportNameIdMap.key(airport_id));
  1018. onGoodInputReceived(line_edit);
  1019. }
  1020. /*
  1021. * Time Line Edits
  1022. */
  1023. void NewFlightDialog::onTimeLineEdit_editingFinished()
  1024. {
  1025. auto sender_object = sender();
  1026. auto line_edit = this->findChild<QLineEdit*>(sender_object->objectName());
  1027. DEB << line_edit->objectName() << "Editing Finished -" << line_edit->text();
  1028. line_edit->setText(ATime::formatTimeInput(line_edit->text()));
  1029. const auto time = ATime::fromString(line_edit->text());
  1030. if(time.isValid()){
  1031. if(primaryTimeLineEdits.contains(line_edit)) {
  1032. onGoodInputReceived(line_edit);
  1033. } else { // is extra time line edit
  1034. if (!isLessOrEqualThanBlockTime(line_edit->text())) {
  1035. line_edit->setText(EMPTY_STRING);
  1036. line_edit->setFocus();
  1037. return;
  1038. }
  1039. }
  1040. } else {
  1041. if (!line_edit->text().isEmpty())
  1042. onBadInputReceived(line_edit);
  1043. }
  1044. }
  1045. /*
  1046. * Aircraft Line Edit
  1047. */
  1048. void NewFlightDialog::on_acftLineEdit_editingFinished()
  1049. {
  1050. auto line_edit = ui->acftLineEdit;
  1051. //DEB << line_edit->objectName() << "Editing Finished!" << line_edit->text());
  1052. if (tailsIdMap.value(line_edit->text()) != 0) {
  1053. DEB << "Mapped: " << line_edit->text() << tailsIdMap.value(line_edit->text());
  1054. auto acft = aDB->getTailEntry(tailsIdMap.value(line_edit->text()));
  1055. ui->acftTypeLabel->setText(acft.type());
  1056. onGoodInputReceived(line_edit);
  1057. return;
  1058. }
  1059. // try to fix input
  1060. if (!line_edit->completer()->currentCompletion().isEmpty()
  1061. && !line_edit->text().isEmpty()) {
  1062. DEB << "Trying to fix input...";
  1063. line_edit->setText(line_edit->completer()->currentCompletion());
  1064. emit line_edit->editingFinished();
  1065. return;
  1066. }
  1067. // to do: promp user to add new
  1068. onBadInputReceived(line_edit);
  1069. ui->acftTypeLabel->setText("Unknown Registration.");
  1070. addNewTail(line_edit);
  1071. }
  1072. /*
  1073. * Pilot Line Edits
  1074. */
  1075. void NewFlightDialog::onPilotNameLineEdit_editingFinished()
  1076. {
  1077. auto sender_object = sender();
  1078. auto line_edit = this->findChild<QLineEdit*>(sender_object->objectName());
  1079. //DEB << line_edit->objectName() << "Editing Finished -" << line_edit->text());
  1080. if(line_edit->text().contains(SELF_RX)) {
  1081. DEB << "self recognized.";
  1082. line_edit->setText(pilotsIdMap.key(1));
  1083. auto pilot = aDB->getPilotEntry(1);
  1084. ui->picCompanyLabel->setText(pilot.getData().value(Opl::Db::TAILS_COMPANY).toString());
  1085. onGoodInputReceived(line_edit);
  1086. return;
  1087. }
  1088. if(pilotsIdMap.value(line_edit->text()) != 0) {
  1089. DEB << "Mapped: " << line_edit->text() << pilotsIdMap.value(line_edit->text());
  1090. auto pilot = aDB->getPilotEntry(pilotsIdMap.value(line_edit->text()));
  1091. ui->picCompanyLabel->setText(pilot.getData().value(Opl::Db::TAILS_COMPANY).toString());
  1092. onGoodInputReceived(line_edit);
  1093. return;
  1094. }
  1095. if (line_edit->text().isEmpty()) {
  1096. return;
  1097. }
  1098. if (!line_edit->completer()->currentCompletion().isEmpty()) {
  1099. DEB << "Trying to fix input...";
  1100. line_edit->setText(line_edit->completer()->currentCompletion());
  1101. emit line_edit->editingFinished();
  1102. return;
  1103. }
  1104. // to do: prompt user to add new
  1105. onBadInputReceived(line_edit);
  1106. addNewPilot(line_edit);
  1107. }
  1108. ///////////////////////////////////////////////////////////////////////////////////////////////////
  1109. /// Auto Logging Tab Slots ///
  1110. ///////////////////////////////////////////////////////////////////////////////////////////////////
  1111. void NewFlightDialog::on_setAsDefaultButton_clicked()
  1112. {
  1113. writeSettings();
  1114. }
  1115. void NewFlightDialog::on_restoreDefaultButton_clicked()
  1116. {
  1117. readSettings();
  1118. }
  1119. void NewFlightDialog::on_PilotFlyingCheckBox_stateChanged(int)
  1120. {
  1121. DEB << "PF checkbox state changed.";
  1122. if(ui->PilotFlyingCheckBox->isChecked()){
  1123. ui->TakeoffSpinBox->setValue(1);
  1124. ui->TakeoffCheckBox->setCheckState(Qt::Checked);
  1125. ui->LandingSpinBox->setValue(1);
  1126. ui->LandingCheckBox->setCheckState(Qt::Checked);
  1127. }else if(!ui->PilotFlyingCheckBox->isChecked()){
  1128. ui->TakeoffSpinBox->setValue(0);
  1129. ui->TakeoffCheckBox->setCheckState(Qt::Unchecked);
  1130. ui->LandingSpinBox->setValue(0);
  1131. ui->LandingCheckBox->setCheckState(Qt::Unchecked);
  1132. }
  1133. }
  1134. void NewFlightDialog::on_IfrCheckBox_stateChanged(int)
  1135. {
  1136. if (mandatoryLineEditsGood.count(true) == 7 && updateEnabled)
  1137. onMandatoryLineEditsFilled();
  1138. }
  1139. void NewFlightDialog::on_manualEditingCheckBox_stateChanged(int arg1)
  1140. {
  1141. if (!(mandatoryLineEditsGood.count(true) == 7) && ui->manualEditingCheckBox->isChecked()) {
  1142. QMessageBox message_box(this);
  1143. message_box.setText("Before editing times manually, please fill out the required fields in the flight data tab,"
  1144. " so that total time can be calculated.");
  1145. message_box.exec();
  1146. ui->manualEditingCheckBox->setChecked(false);
  1147. return;
  1148. }
  1149. QList<QLineEdit*> LE = {ui->tSPSETimeLineEdit, ui->tSPMETimeLineEdit, ui->tMPTimeLineEdit, ui->tIFRTimeLineEdit,
  1150. ui->tNIGHTTimeLineEdit,ui->tPICTimeLineEdit, ui->tPICUSTimeLineEdit, ui->tSICTimeLineEdit,
  1151. ui->tDUALTimeLineEdit, ui->tFITimeLineEdit};
  1152. switch (arg1) {
  1153. case 0:
  1154. for(const auto& le : LE){
  1155. le->setFocusPolicy(Qt::NoFocus);
  1156. le->setStyleSheet("");
  1157. }
  1158. updateEnabled = true;
  1159. if (mandatoryLineEditsGood.count(true) == 7 && updateEnabled)
  1160. onMandatoryLineEditsFilled();
  1161. break;
  1162. case 2:
  1163. for(const auto& le : LE){
  1164. le->setFocusPolicy(Qt::StrongFocus);
  1165. }
  1166. updateEnabled = false;
  1167. break;
  1168. default:
  1169. break;
  1170. }
  1171. }
  1172. void NewFlightDialog::on_ApproachComboBox_currentTextChanged(const QString &arg1)
  1173. {
  1174. if(arg1 == QStringLiteral("ILS CAT III")){ //for a CAT III approach an Autoland is mandatory, so we can preselect it.
  1175. ui->AutolandCheckBox->setCheckState(Qt::Checked);
  1176. ui->AutolandSpinBox->setValue(1);
  1177. }else{
  1178. ui->AutolandCheckBox->setCheckState(Qt::Unchecked);
  1179. ui->AutolandSpinBox->setValue(0);
  1180. }
  1181. if (arg1 != QStringLiteral("VISUAL"))
  1182. ui->IfrCheckBox->setChecked(true);
  1183. if (arg1 == QStringLiteral("OTHER")) {
  1184. QMessageBox message_box(this);
  1185. message_box.setText(QStringLiteral("You can specify the approach type in the Remarks field."));
  1186. message_box.exec();
  1187. }
  1188. }
  1189. void NewFlightDialog::on_FunctionComboBox_currentIndexChanged(int)
  1190. {
  1191. if (updateEnabled)
  1192. fillDeductibleData();
  1193. }