Browse Source

Merge pull request #95 from fiffty-50/develop-refactor-newflightdialog

Refactor of NewFlightDialog
Felix Turowsky 2 years ago
parent
commit
e18ed2f70f
40 changed files with 1114 additions and 422 deletions
  1. 18 4
      CMakeLists.txt
  2. 23 1
      deprecated/dbcompletiondata.cpp
  3. 9 1
      deprecated/dbcompletiondata.h
  4. 56 73
      mainwindow.cpp
  5. 5 7
      mainwindow.h
  6. 2 0
      src/classes/settings.cpp
  7. 1 3
      src/database/database.h
  8. 227 0
      src/database/databasecache.cpp
  9. 87 0
      src/database/databasecache.h
  10. 3 3
      src/functions/calc.cpp
  11. 6 9
      src/functions/calc.h
  12. 2 0
      src/functions/time.h
  13. 71 149
      src/gui/dialogues/newflightdialog.cpp
  14. 6 54
      src/gui/dialogues/newflightdialog.h
  15. 2 9
      src/gui/dialogues/newpilotdialog.cpp
  16. 18 13
      src/gui/dialogues/newsimdialog.cpp
  17. 3 0
      src/gui/dialogues/newsimdialog.ui
  18. 3 5
      src/gui/dialogues/newtaildialog.cpp
  19. 24 0
      src/gui/verification/airportinput.cpp
  20. 36 0
      src/gui/verification/airportinput.h
  21. 110 0
      src/gui/verification/completerprovider.cpp
  22. 54 0
      src/gui/verification/completerprovider.h
  23. 18 0
      src/gui/verification/pilotinput.cpp
  24. 28 0
      src/gui/verification/pilotinput.h
  25. 15 0
      src/gui/verification/tailinput.cpp
  26. 16 0
      src/gui/verification/tailinput.h
  27. 34 0
      src/gui/verification/timeinput.cpp
  28. 30 0
      src/gui/verification/timeinput.h
  29. 1 0
      src/gui/verification/userinput.cpp
  30. 30 0
      src/gui/verification/userinput.h
  31. 49 0
      src/gui/verification/validationstate.h
  32. 24 0
      src/gui/widgets/debugwidget.cpp
  33. 4 0
      src/gui/widgets/debugwidget.h
  34. 6 14
      src/gui/widgets/logbookwidget.cpp
  35. 11 4
      src/gui/widgets/logbookwidget.h
  36. 22 24
      src/gui/widgets/pilotswidget.cpp
  37. 2 1
      src/gui/widgets/pilotswidget.h
  38. 42 38
      src/gui/widgets/tailswidget.cpp
  39. 9 10
      src/gui/widgets/tailswidget.h
  40. 7 0
      src/opl.h

+ 18 - 4
CMakeLists.txt

@@ -50,8 +50,8 @@ set(PROJECT_SOURCES
     src/gui/dialogues/newsimdialog.cpp
     src/gui/dialogues/newsimdialog.ui
     # Widgets
-    src/gui/widgets/aircraftwidget.h
-    src/gui/widgets/aircraftwidget.cpp
+    src/gui/widgets/tailswidget.h
+    src/gui/widgets/tailswidget.cpp
     src/gui/widgets/aircraftwidget.ui
     src/gui/widgets/airportwidget.h
     src/gui/widgets/airportwidget.cpp
@@ -74,6 +74,20 @@ set(PROJECT_SOURCES
     src/gui/widgets/settingswidget.cpp
     src/gui/widgets/settingswidget.h
     src/gui/widgets/settingswidget.ui
+    # Verification
+    src/gui/verification/validationstate.h
+    src/gui/verification/userinput.h
+    src/gui/verification/userinput.cpp
+    src/gui/verification/timeinput.h
+    src/gui/verification/timeinput.cpp
+    src/gui/verification/airportinput.h
+    src/gui/verification/airportinput.cpp
+    src/gui/verification/pilotinput.h
+    src/gui/verification/pilotinput.cpp
+    src/gui/verification/completerprovider.h
+    src/gui/verification/completerprovider.cpp
+    src/gui/verification/tailinput.h
+    src/gui/verification/tailinput.cpp
 
 
     # Classes
@@ -111,10 +125,10 @@ set(PROJECT_SOURCES
     src/database/database.cpp
     src/database/row.h
     src/database/row.cpp
-    src/database/dbcompletiondata.h
-    src/database/dbcompletiondata.cpp
     src/database/dbsummary.h
     src/database/dbsummary.cpp
+    src/database/databasecache.h
+    src/database/databasecache.cpp
 
     # Ressources
     assets/icons.qrc

+ 23 - 1
src/database/dbcompletiondata.cpp → deprecated/dbcompletiondata.cpp

@@ -21,8 +21,10 @@
 
 namespace OPL {
 
+QT_DEPRECATED
 void DbCompletionData::init()
 {
+    LOG << "Initialising Completion Data...";
     // retreive user modifiable data
     pilotsIdMap = getIdMap(CompleterTarget::PilotNames);
     tailsIdMap  = getIdMap(CompleterTarget::Registrations);
@@ -45,8 +47,13 @@ void DbCompletionData::init()
     airportIataIdMap = getIdMap(CompleterTarget::AirportIdentifierIATA);
     airportNameIdMap = getIdMap(CompleterTarget::AirportNames);
     airportList      = getCompletionList(CompleterTarget::AirportIdentifier);
+
+    // retreive default data
+    airportsMapICAO = getIdMap(CompleterTarget::AirportIdentifierICAO);
+    airportsMapIATA = getIdMap(CompleterTarget::AirportIdentifierIATA);
 }
 
+QT_DEPRECATED
 void DbCompletionData::update()
 {
         updatePilots();
@@ -54,7 +61,7 @@ void DbCompletionData::update()
         updateAirports();
 };
 
-
+QT_DEPRECATED
 void DbCompletionData::updateTails()
 {
     DEB << "Updating Tails...";
@@ -62,6 +69,7 @@ void DbCompletionData::updateTails()
     tailsList   = getCompletionList(CompleterTarget::Registrations);
 }
 
+QT_DEPRECATED
 void DbCompletionData::updatePilots()
 {
     DEB << "Updating Pilots...";
@@ -69,6 +77,7 @@ void DbCompletionData::updatePilots()
     pilotList    = getCompletionList(CompleterTarget::PilotNames);
 }
 
+QT_DEPRECATED
 void DbCompletionData::updateAirports()
 {
     DEB << "Updating Airports...";
@@ -78,6 +87,7 @@ void DbCompletionData::updateAirports()
     airportList      = getCompletionList(CompleterTarget::AirportIdentifier);
 }
 
+QT_DEPRECATED
 const QStringList DbCompletionData::getCompletionList(CompleterTarget target)
 {
     QString statement;
@@ -121,6 +131,7 @@ const QStringList DbCompletionData::getCompletionList(CompleterTarget target)
     return completer_list;
 }
 
+QT_DEPRECATED
 const QHash<int, QString> DbCompletionData::getIdMap(CompleterTarget target)
 {
     QString statement;
@@ -161,4 +172,15 @@ const QHash<int, QString> DbCompletionData::getIdMap(CompleterTarget target)
     return id_map;
 }
 
+QT_DEPRECATED
+const QHash<int, QString> &DbCompletionData::getAirportsMapICAO() const
+{
+    return airportsMapICAO;
+}
+
+QT_DEPRECATED
+const QHash<int, QString> &DbCompletionData::getAirportsMapIATA() const
+{
+    return airportsMapIATA;
+}
 } // namespace OPL

+ 9 - 1
src/database/dbcompletiondata.h → deprecated/dbcompletiondata.h

@@ -17,6 +17,7 @@
  */
 #ifndef DBCOMPLETIONDATA_H
 #define DBCOMPLETIONDATA_H
+#include "src/opl.h"
 
 namespace OPL {
 
@@ -82,8 +83,15 @@ public:
      * its row id. Used in the Dialogs to map user input to unique database entries.
      */
     static const QHash<int, QString> getIdMap(CompleterTarget target);
-};
 
+    const QHash<int, QString> &getAirportsMapICAO() const;
+    const QHash<int, QString> &getAirportsMapIATA() const;
+
+private:
+    QHash<int, QString> airportsMapICAO;
+    QHash<int, QString> airportsMapIATA;
+
+};
 } // namespace OPL
 
 #endif // DBCOMPLETIONDATA_H

+ 56 - 73
mainwindow.cpp

@@ -24,6 +24,7 @@
 #include "src/gui/dialogues/newflightdialog.h"
 #include "src/gui/dialogues/newsimdialog.h"
 #include "src/gui/dialogues/newflightdialog.h"
+#include "src/database/databasecache.h"
 // Quick and dirty Debug area
 #include "src/testing/randomgenerator.h"
 void MainWindow::doDebugStuff()
@@ -32,7 +33,7 @@ void MainWindow::doDebugStuff()
     OPL::FlightEntry entry = generator.randomFlight();
     DB->commit(entry);
 
-    auto nfd = NewFlightDialog(completionData, DB->getLastEntry(OPL::DbTable::Flights));
+    auto nfd = NewFlightDialog(DB->getLastEntry(OPL::DbTable::Flights));
     nfd.exec();
 }
 
@@ -41,51 +42,10 @@ MainWindow::MainWindow(QWidget *parent)
     , ui(new Ui::MainWindow)
 {
     ui->setupUi(this);
-    setupToolbar();
-    setActionIcons(OPL::Style::getStyleType());
-
-    // connect to the Database
-    if (OPL::Paths::databaseFileInfo().size() == 0)
-        onDatabaseInvalid();
-
-    if(!DB->connect()){
-        WARN(tr("Error establishing database connection. The following error has ocurred:<br><br>%1")
-             .arg(DB->lastError.text()));
-    }
-
-    // retreive completion lists and maps
-    completionData.init();
-
-    // Construct Widgets
-    homeWidget = new HomeWidget(this);
-    ui->stackedWidget->addWidget(homeWidget);
-    logbookWidget = new LogbookWidget(completionData, this);
-    ui->stackedWidget->addWidget(logbookWidget);
-    aircraftWidget = new AircraftWidget(this);
-    ui->stackedWidget->addWidget(aircraftWidget);
-    pilotsWidget = new PilotsWidget(this);
-    ui->stackedWidget->addWidget(pilotsWidget);
+    init();
 
-    airportWidget = new AirportWidget(this);
-    ui->stackedWidget->addWidget(airportWidget);
-    settingsWidget = new SettingsWidget(this);
-    ui->stackedWidget->addWidget(settingsWidget);
-    debugWidget = new DebugWidget(this);
-    ui->stackedWidget->addWidget(debugWidget);
-
-    connectWidgets();
-
-    // Startup Screen (Home Screen)
+    // set Startup Screen (Home Screen)
     ui->stackedWidget->setCurrentWidget(homeWidget);
-
-    // check database version (Debug)
-    //if (DB->dbRevision() < DB->getMinimumDatabaseRevision()) {
-    //    QString message = tr("Your database is out of date."
-    //                         "Minimum required revision: %1<br>"
-    //                         "You have revision: %2<br>")
-    //                         .arg(DB->getMinimumDatabaseRevision(), DB->dbRevision());
-    //    WARN(message);
-    //}
 }
 
 MainWindow::~MainWindow()
@@ -93,6 +53,15 @@ MainWindow::~MainWindow()
     delete ui;
 }
 
+void MainWindow::init()
+{
+    connectDatabase();
+    initialiseWidgets();
+    setupToolbar();
+    connectWidgets();
+    setActionIcons(OPL::Style::getStyleType());
+}
+
 void MainWindow::setupToolbar()
 {
     // Create and set up the toolbar
@@ -112,6 +81,45 @@ void MainWindow::setupToolbar()
     addToolBar(Qt::ToolBarArea::LeftToolBarArea, toolBar);
 }
 
+void MainWindow::initialiseWidgets()
+{
+    // Construct Widgets
+    homeWidget = new HomeWidget(this);
+    ui->stackedWidget->addWidget(homeWidget);
+
+    logbookWidget = new LogbookWidget(this);
+    ui->stackedWidget->addWidget(logbookWidget);
+
+    aircraftWidget = new TailsWidget(this);
+    ui->stackedWidget->addWidget(aircraftWidget);
+
+    pilotsWidget = new PilotsWidget(this);
+    ui->stackedWidget->addWidget(pilotsWidget);
+
+    airportWidget = new AirportWidget(this);
+    ui->stackedWidget->addWidget(airportWidget);
+
+    settingsWidget = new SettingsWidget(this);
+    ui->stackedWidget->addWidget(settingsWidget);
+
+    debugWidget = new DebugWidget(this);
+    ui->stackedWidget->addWidget(debugWidget);
+}
+
+void MainWindow::connectDatabase()
+{
+    // connect to the Database
+    if (OPL::Paths::databaseFileInfo().size() == 0)
+        onDatabaseInvalid();
+
+    if(!DB->connect()){
+        WARN(tr("Error establishing database connection. The following error has ocurred:<br><br>%1")
+             .arg(DB->lastError.text()));
+    }
+    DBCache->init();
+    // Load Cache
+}
+
 void MainWindow::setActionIcons(OPL::Style::StyleType style)
 {
     switch (style){
@@ -167,9 +175,9 @@ void MainWindow::connectWidgets()
                      logbookWidget,  &LogbookWidget::onLogbookWidget_viewSelectionChanged);
 
     QObject::connect(DB,             &OPL::Database::dataBaseUpdated,
-                     aircraftWidget, &AircraftWidget::onAircraftWidget_dataBaseUpdated);
+                     aircraftWidget, &TailsWidget::onAircraftWidget_dataBaseUpdated);
     QObject::connect(settingsWidget, &SettingsWidget::settingChanged,
-                     aircraftWidget, &AircraftWidget::onAircraftWidget_settingChanged);
+                     aircraftWidget, &TailsWidget::onAircraftWidget_settingChanged);
 
     QObject::connect(DB,             &OPL::Database::dataBaseUpdated,
                      pilotsWidget,   &PilotsWidget::onPilotsWidget_databaseUpdated);
@@ -183,31 +191,7 @@ void MainWindow::connectWidgets()
     QObject::connect(DB,              &OPL::Database::connectionReset,
                      pilotsWidget,    &PilotsWidget::repopulateModel);
     QObject::connect(DB,              &OPL::Database::connectionReset,
-                     aircraftWidget,  &AircraftWidget::repopulateModel);
-
-    // Catch database updates to lazily update CompletionData
-    QObject::connect(DB,              &OPL::Database::dataBaseUpdated,
-                     this,            &MainWindow::onDatabaseUpdated);
-}
-
-void MainWindow::onDatabaseUpdated(const OPL::DbTable table)
-{
-    switch (table) {
-    case OPL::DbTable::Pilots:
-        DEB << "Pilots table updated...";
-        completionData.updatePilots();
-        break;
-    case OPL::DbTable::Tails:
-        DEB << "Tails table updated...";
-        completionData.updateTails();
-        break;
-    case OPL::DbTable::Airports:
-        DEB << "Airports table updated...";
-        completionData.updateAirports();
-        break;
-    default:
-        break;
-    }
+                     aircraftWidget,  &TailsWidget::repopulateModel);
 }
 
 void MainWindow::onDatabaseInvalid()
@@ -262,8 +246,7 @@ void MainWindow::on_actionHome_triggered()
 
 void MainWindow::on_actionNewFlight_triggered()
 {
-    completionData.update();
-    auto* nf = new NewFlightDialog(completionData, this);
+    auto* nf = new NewFlightDialog(this);
     nf->exec();
 }
 

+ 5 - 7
mainwindow.h

@@ -32,13 +32,12 @@
 #include "src/gui/widgets/homewidget.h"
 #include "src/gui/widgets/settingswidget.h"
 #include "src/gui/widgets/logbookwidget.h"
-#include "src/gui/widgets/aircraftwidget.h"
+#include "src/gui/widgets/tailswidget.h"
 #include "src/gui/widgets/airportwidget.h"
 #include "src/gui/widgets/airportwidget.h"
 #include "src/gui/widgets/pilotswidget.h"
 #include "src/gui/widgets/debugwidget.h"
 #include "src/classes/style.h"
-#include "src/database/dbcompletiondata.h"
 
 enum Style {Light, Dark};
 QT_BEGIN_NAMESPACE
@@ -117,8 +116,6 @@ private slots:
 
     void on_actionNewSim_triggered();
 
-    void onDatabaseUpdated(const OPL::DbTable table);
-
 private:
     Ui::MainWindow *ui;
 
@@ -126,7 +123,7 @@ private:
 
     LogbookWidget* logbookWidget;
 
-    AircraftWidget* aircraftWidget;
+    TailsWidget* aircraftWidget;
 
     PilotsWidget* pilotsWidget;
 
@@ -136,11 +133,12 @@ private:
 
     DebugWidget* debugWidget;
 
-    // Completion Data for QCompleters and Mapping
-    OPL::DbCompletionData completionData;
     bool airportDbIsDirty = false;
 
+    void init();
     void setupToolbar();
+    void initialiseWidgets();
+    void connectDatabase();
     void setActionIcons(OPL::Style::StyleType style = OPL::Style::StyleType::Light);
 
     void nope();

+ 2 - 0
src/classes/settings.cpp

@@ -83,6 +83,8 @@ void Settings::resetToDefaults()
     write(UserData::ShowMedCurrency, false);
     write(UserData::ShowCustom1Currency, false);
     write(UserData::ShowCustom2Currency, false);
+    write(UserData::PilotSortColumn, 0);
+    write(UserData::TailSortColumn, 0);
 
     write(FlightLogging::PilotFlying, true);
     write(FlightLogging::NightAngle, -6);

+ 1 - 3
src/database/database.h

@@ -39,8 +39,6 @@
 
 namespace OPL {
 
-//using RowData_T = QHash<QString, QVariant>;
-
 /*!
  * \brief Convenience macro that returns instance of DataBase.
  * Instead of this:
@@ -336,7 +334,7 @@ signals:
      * trigger an update to the models of the views displaying database contents in
      * the user interface so that a user is always presented with up-to-date information.
      */
-    void dataBaseUpdated(const DbTable table);
+    void dataBaseUpdated(const OPL::DbTable table);
     /*!
      * \brief connectionReset is emitted whenever the database connection is reset, for
      * example when creating or restoring a backup.

+ 227 - 0
src/database/databasecache.cpp

@@ -0,0 +1,227 @@
+#include "databasecache.h"
+#include "src/database/database.h"
+#include "src/opl.h"
+#include <QSqlQuery>
+
+namespace OPL{
+
+void DatabaseCache::init()
+{
+    LOG << "Initialising database cache...";
+
+    updateTails();
+    updatePilots();
+    updateAirports();
+    updateSimulators();
+    updateAircraft();
+
+    // Listen to database for updates, reload cache if needed
+    QObject::connect(DB,   		   &OPL::Database::dataBaseUpdated,
+                     this,         &OPL::DatabaseCache::onDatabaseUpdated);
+
+}
+
+const IdMap DatabaseCache::fetchMap(CompleterTarget target)
+{
+    QString statement;
+
+    switch (target) {
+    case AirportsICAO:
+        statement.append(QStringLiteral("SELECT ROWID, icao FROM airports"));
+        break;
+    case AirportsIATA:
+        statement.append(QStringLiteral("SELECT ROWID, iata FROM airports WHERE iata NOT NULL"));
+        break;
+    case AirportNames:
+        statement.append(QStringLiteral("SELECT ROWID, name FROM airports"));
+        break;
+    case PilotNames:
+        statement.append(QStringLiteral("SELECT ROWID, lastname||', '||firstname FROM pilots"));
+        break;
+    case Tails:
+        statement.append(QStringLiteral("SELECT ROWID, registration FROM tails"));
+        break;
+    case AircraftTypes:
+        statement.append(QStringLiteral("SELECT ROWID, make||' '||model FROM aircraft WHERE model IS NOT NULL AND variant IS NULL "
+                         "UNION "
+                         "SELECT ROWID, make||' '||model||'-'||variant FROM aircraft WHERE variant IS NOT NULL"));
+        break;
+    default:
+        return {};
+    }
+
+    QSqlQuery query;
+    query.setForwardOnly(true);
+    query.prepare(statement);
+    query.exec();
+
+    IdMap id_map;
+    while (query.next())
+        id_map.insert(query.value(0).toInt(), query.value(1).toString());
+    return id_map;
+}
+
+const QStringList DatabaseCache::fetchList(CompleterTarget target)
+{
+    QString statement;
+
+    switch (target) {
+    case PilotNames:
+        statement.append(QStringLiteral("SELECT lastname||', '||firstname FROM pilots"));
+        break;
+    case AircraftTypes:
+        statement.append(QStringLiteral("SELECT make||' '||model FROM aircraft WHERE model IS NOT NULL AND variant IS NULL "
+                         "UNION "
+                         "SELECT make||' '||model||'-'||variant FROM aircraft WHERE variant IS NOT NULL"));
+        break;
+    case AirportsAny:
+        statement.append(QStringLiteral("SELECT icao FROM airports UNION SELECT iata FROM airports"));
+        break;
+    case Tails:
+        statement.append(QStringLiteral("SELECT registration FROM tails"));
+        break;
+    case Companies:
+        statement.append(QStringLiteral("SELECT company FROM pilots"));
+        break;
+    default:
+        DEB << "Not a valid completer target for this function.";
+        return QStringList();
+    }
+
+    QSqlQuery query;
+    query.prepare(statement);
+    query.setForwardOnly(true);
+    query.exec();
+
+    QStringList completer_list;
+    while (query.next())
+        completer_list.append(query.value(0).toString());
+
+    completer_list.sort();
+    completer_list.removeAll(QString());
+    completer_list.removeDuplicates();
+
+    return completer_list;
+}
+
+void DatabaseCache::updateTails()
+{
+    tailsMap = fetchMap(Tails);
+    tailsList = fetchList(Tails);
+    for (auto &reg : tailsList) {
+        if(reg.contains(QLatin1Char('-'))) { // check to avoid duplication if reg has no '-'
+            QString copy = reg;
+            reg.remove(QLatin1Char('-'));
+            reg = copy + " (" + reg + QLatin1Char(')');
+        }
+    }
+}
+
+void DatabaseCache::updateAirports()
+{
+    airportsMapIATA  = fetchMap(AirportsIATA);
+    airportsMapICAO  = fetchMap(AirportsICAO);
+    airportsMapNames = fetchMap(AirportNames);
+    airportList      = fetchList(AirportsAny);
+}
+
+void DatabaseCache::updateSimulators()
+{
+    TODO << "not yet implemented";
+}
+
+void DatabaseCache::updatePilots()
+{
+    pilotNamesMap  = fetchMap(PilotNames);
+    pilotNamesList = fetchList(PilotNames);
+    companiesList  = fetchList(Companies);
+}
+
+void DatabaseCache::updateAircraft()
+{
+    aircraftList = fetchList(AircraftTypes);
+    aircraftMap = fetchMap(AircraftTypes);
+}
+
+void DatabaseCache::onDatabaseUpdated(const OPL::DbTable table)
+{
+    LOG << "Updating Database Cache...";
+    switch (table) {
+    case DbTable::Pilots:
+        updatePilots();
+        break;
+    case DbTable::Tails:
+        updateTails();
+        break;
+    case DbTable::Simulators:
+        updateSimulators();
+        break;
+    case DbTable::Airports:
+        updateAirports();
+        break;
+    case DbTable::Aircraft:
+        updateAircraft();
+        break;
+    default:
+        break;
+    }
+    emit databaseCacheUpdated(table);
+}
+const IdMap &DatabaseCache::getAirportsMapICAO() const
+{
+    return airportsMapICAO;
+}
+
+const IdMap &DatabaseCache::getAirportsMapIATA() const
+{
+    return airportsMapIATA;
+}
+
+const IdMap &DatabaseCache::getPilotNamesMap() const
+{
+    return pilotNamesMap;
+}
+
+const QStringList &DatabaseCache::getPilotNamesList() const
+{
+    return pilotNamesList;
+}
+
+const QStringList &DatabaseCache::getTailsList() const
+{
+    return tailsList;
+}
+
+const QStringList &DatabaseCache::getAirportList() const
+{
+    return airportList;
+}
+
+const QStringList &DatabaseCache::getCompaniesList() const
+{
+    return companiesList;
+}
+
+const QStringList &DatabaseCache::getAircraftList() const
+{
+    return aircraftList;
+}
+
+const IdMap &DatabaseCache::getAircraftMap() const
+{
+    return aircraftMap;
+}
+
+const IdMap &DatabaseCache::getAirportsMapNames() const
+{
+    return airportsMapNames;
+}
+
+const IdMap &DatabaseCache::getTailsMap() const
+{
+    return tailsMap;
+}
+
+
+
+} // namespace OPL

+ 87 - 0
src/database/databasecache.h

@@ -0,0 +1,87 @@
+#ifndef DATABASECACHE_H
+#define DATABASECACHE_H
+#include "src/opl.h"
+#include <QtCore>
+
+namespace OPL{
+
+using IdMap = QHash<int, QString>;
+#define DBCache OPL::DatabaseCache::instance()
+
+/*!
+ * \brief Caches certain often accessed database content in memory
+ * \details Access to the database is rather slow and memory these days is cheap enough to cache some contents for ease
+ * and speed of access. This class provides lists of database entries that are used as inputs for QCompleter instances
+ * as well as maps which contain database entries and their associated row ids, which are used for user input verification.
+ *
+ * The Cache can be accessed by using the DBCACHE macro and needs to be updated whenever the database contents are modified.
+ */
+class DatabaseCache : public QObject
+{
+public:
+    static DatabaseCache* instance() {
+        static DatabaseCache instance;
+        return &instance;
+    }
+
+    DatabaseCache(DatabaseCache const&) = delete;
+    void operator=(DatabaseCache const&) = delete;
+
+    enum CompleterTarget {PilotNames, Tails, AircraftTypes, AirportsAny, AirportsICAO, AirportNames, AirportsIATA, Companies};
+
+    void init();
+
+    const IdMap &getAirportsMapICAO() const;
+    const IdMap &getAirportsMapIATA() const;
+    const IdMap &getPilotNamesMap() const;
+    const IdMap &getTailsMap() const;
+
+    const QStringList &getPilotNamesList() const;
+    const QStringList &getTailsList() const;
+    const QStringList &getAirportList() const;
+    const QStringList &getCompaniesList() const;
+
+
+    const QStringList &getAircraftList() const;
+
+    const IdMap &getAircraftMap() const;
+
+    const IdMap &getAirportsMapNames() const;
+
+private:
+    Q_OBJECT
+    DatabaseCache() {};
+
+    // Id Maps
+    IdMap airportsMapICAO;
+    IdMap airportsMapIATA;
+    IdMap airportsMapNames;
+    IdMap pilotNamesMap;
+    IdMap tailsMap;
+    IdMap aircraftMap;
+    // Lists
+    QStringList pilotNamesList;
+    QStringList tailsList;
+    QStringList aircraftList;
+    QStringList airportList;
+    QStringList companiesList;
+
+    const IdMap fetchMap(CompleterTarget target);
+    const QStringList fetchList(CompleterTarget target);
+
+
+    void updateTails();
+    void updateAirports();
+    void updateSimulators();
+    void updatePilots();
+    void updateAircraft();
+
+public slots:
+    void onDatabaseUpdated(const OPL::DbTable table);
+signals:
+    void databaseCacheUpdated(const OPL::DbTable table);
+
+};
+
+}// namespace OPL
+#endif // DATABASECACHE_H

+ 3 - 3
src/functions/calc.cpp

@@ -164,7 +164,7 @@ QVector<QVector<double>> OPL::Calc::intermediatePointsOnGreatCircle(double lat1,
 }
 
 
-double OPL::Calc::solarElevation(QDateTime utc_time_point, double lat, double lon)
+double OPL::Calc::solarElevation(const QDateTime &utc_time_point, double lat, double lon)
 {
     double Alt = 11; // Assuming 11 kilometers as an average cruising height for a commercial passenger airplane.
     // convert current DateTime Object to a J2000 value used in the Calculation
@@ -223,7 +223,7 @@ double OPL::Calc::solarElevation(QDateTime utc_time_point, double lat, double lo
 }
 
 
-int OPL::Calc::calculateNightTime(const QString &dept, const QString &dest, QDateTime departureTime, int tblk, int night_angle)
+int OPL::Calc::calculateNightTime(const QString &dept, const QString &dest, const QDateTime &departureTime, int tblk, int night_angle)
 {
 
     const QString statement = QLatin1String("SELECT lat, long FROM airports WHERE icao = '")
@@ -269,7 +269,7 @@ int OPL::Calc::calculateNightTime(const QString &dept, const QString &dest, QDat
     return night_time;
 }
 
-bool OPL::Calc::isNight(const QString &icao, QDateTime event_time, int night_angle)
+bool OPL::Calc::isNight(const QString &icao, const QDateTime &event_time, int night_angle)
 {
     const QString statement = QLatin1String("SELECT lat, long FROM airports WHERE icao = '")
             + icao

+ 6 - 9
src/functions/calc.h

@@ -40,8 +40,7 @@ namespace OPL::Calc {
  */
 inline double radToDeg(double rad)
 {
-    double deg = rad * (180 / M_PI);
-    return deg;
+    return rad * (180 / M_PI);
 }
 
 /*!
@@ -51,8 +50,7 @@ inline double radToDeg(double rad)
  */
 inline double degToRad(double deg)
 {
-    double rad = deg * (M_PI / 180);
-    return rad;
+    return deg * (M_PI / 180);
 }
 
 /*!
@@ -62,8 +60,7 @@ inline double degToRad(double deg)
  */
 inline double radToNauticalMiles(double rad)
 {
-    double nm = rad * 3440.06479482;
-    return nm;
+    return rad * 3440.06479482;
 }
 
 /*!
@@ -116,7 +113,7 @@ QVector<QVector<double>> intermediatePointsOnGreatCircle(double lat1,
  * \param lon - Location Longitude in degrees -180:180 W(-) E(+)
  * \return elevation - double of solar elevation in degrees.
  */
-double solarElevation(QDateTime utc_time_point, double lat, double lon);
+double solarElevation(const QDateTime& utc_time_point, double lat, double lon);
 
 /*!
  * \brief Calculates which portion of a flight was conducted in night conditions.
@@ -128,9 +125,9 @@ double solarElevation(QDateTime utc_time_point, double lat, double lon);
  * Default -6 (end of civil evening twilight)
  * \return Total number of minutes under night flying conditions
  */
-int calculateNightTime(const QString &dept, const QString &dest, QDateTime departureTime, int tblk, int nightAngle);
+int calculateNightTime(const QString &dept, const QString &dest, const QDateTime& departureTime, int tblk, int nightAngle);
 
-bool isNight(const QString &icao, QDateTime event_time, int night_angle);
+bool isNight(const QString &icao, const QDateTime &event_time, int night_angle);
 
 QString formatTimeInput(QString user_input);
 

+ 2 - 0
src/functions/time.h

@@ -183,8 +183,10 @@ inline int blockMinutes(const QTime& tofb, const QTime& tonb)
  * \param userinput from a QLineEdit
  * \return formatted QString "hh:mm" or Empty String
  */
+QT_DEPRECATED
 inline const QString formatTimeInput(QString user_input)
 {
+    LOG << "DEPRECATED";
     QTime temp_time; //empty time object is invalid by default
 
     bool contains_seperator = user_input.contains(':');

+ 71 - 149
src/gui/dialogues/newflightdialog.cpp

@@ -16,6 +16,12 @@
  *along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 #include "newflightdialog.h"
+#include "src/database/databasecache.h"
+#include "src/gui/verification/airportinput.h"
+#include "src/gui/verification/completerprovider.h"
+#include "src/gui/verification/pilotinput.h"
+#include "src/gui/verification/tailinput.h"
+#include "src/gui/verification/timeinput.h"
 #include "ui_newflightdialog.h"
 #include "src/opl.h"
 #include "src/functions/datetime.h"
@@ -27,16 +33,12 @@
 #include <QCompleter>
 #include <QKeyEvent>
 
-const auto CAT_3 = QLatin1String(OPL::GLOBALS->getApproachTypes()[3].toLatin1());
 
-NewFlightDialog::NewFlightDialog(OPL::DbCompletionData &completion_data,
-                                       QWidget *parent)
+NewFlightDialog::NewFlightDialog(QWidget *parent)
     : QDialog(parent),
-      ui(new Ui::NewFlightDialog),
-      completionData(completion_data)
+      ui(new Ui::NewFlightDialog)
 {
     init();
-    //flightEntry = AFlightEntry();
     // Set up UI (New Flight)
     LOG << Settings::read(Settings::FlightLogging::Function);
     if(Settings::read(Settings::FlightLogging::Function).toInt() == static_cast<int>(OPL::PilotFunction::PIC)){
@@ -53,10 +55,9 @@ NewFlightDialog::NewFlightDialog(OPL::DbCompletionData &completion_data,
     emit ui->doftLineEdit->editingFinished();
 }
 
-NewFlightDialog::NewFlightDialog(OPL::DbCompletionData &completion_data, int row_id, QWidget *parent)
+NewFlightDialog::NewFlightDialog(int row_id, QWidget *parent)
     : QDialog(parent),
-      ui(new Ui::NewFlightDialog),
-      completionData(completion_data)
+      ui(new Ui::NewFlightDialog)
 {
     init();
     flightEntry = DB->getFlightEntry(row_id);
@@ -110,27 +111,14 @@ void NewFlightDialog::setupRawInputValidation()
     for (const auto& line_edit : *locationLineEdits) {
         auto validator = new QRegularExpressionValidator(QRegularExpression("[a-zA-Z0-9]{1,4}"), line_edit);
         line_edit->setValidator(validator);
-
-        auto completer = new QCompleter(completionData.airportList, line_edit);
-        completer->setCaseSensitivity(Qt::CaseInsensitive);
-        completer->setCompletionMode(QCompleter::PopupCompletion);
-        completer->setFilterMode(Qt::MatchContains);
-        line_edit->setCompleter(completer);
+        line_edit->setCompleter(QCompleterProvider.getCompleter(CompleterProvider::Airports));
     }
     // Name Line Edits
     for (const auto& line_edit : *pilotNameLineEdits) {
-        auto completer = new QCompleter(completionData.pilotList, line_edit);
-        completer->setCaseSensitivity(Qt::CaseInsensitive);
-        completer->setCompletionMode(QCompleter::PopupCompletion);
-        completer->setFilterMode(Qt::MatchContains);
-        line_edit->setCompleter(completer);
+        line_edit->setCompleter(QCompleterProvider.getCompleter(CompleterProvider::Pilots));
     }
     // Acft Line Edit
-    auto completer = new QCompleter(completionData.tailsList, ui->acftLineEdit);
-    completer->setCaseSensitivity(Qt::CaseInsensitive);
-    completer->setCompletionMode(QCompleter::PopupCompletion);
-    completer->setFilterMode(Qt::MatchContains);
-    ui->acftLineEdit->setCompleter(completer);
+    ui->acftLineEdit->setCompleter(QCompleterProvider.getCompleter(CompleterProvider::Tails));
 }
 
 void NewFlightDialog::setupSignalsAndSlots()
@@ -166,7 +154,6 @@ bool NewFlightDialog::eventFilter(QObject *object, QEvent *event)
         if (mandatoryLineEdits->contains(line_edit) && event->type() == QEvent::FocusIn) {
             // set verification bit to false when entering a mandatory line edit
             validationState.invalidate(mandatoryLineEdits->indexOf(line_edit));
-            DEB << "Invalidating: " << line_edit->objectName();
             return false;
         }
     }
@@ -204,10 +191,10 @@ void NewFlightDialog::fillWithEntryData()
     // Times
     ui->tofbTimeLineEdit->setText(OPL::Time::toString(flight_data.value(OPL::Db::FLIGHTS_TOFB).toInt()));
     ui->tonbTimeLineEdit->setText(OPL::Time::toString(flight_data.value(OPL::Db::FLIGHTS_TONB).toInt()));
-    ui->acftLineEdit->setText(completionData.tailsIdMap.value(flight_data.value(OPL::Db::FLIGHTS_ACFT).toInt()));
-    ui->picNameLineEdit->setText(completionData.pilotsIdMap.value(flight_data.value(OPL::Db::FLIGHTS_PIC).toInt()));
-    ui->sicNameLineEdit->setText(completionData.pilotsIdMap.value(flight_data.value(OPL::Db::FLIGHTS_SECONDPILOT).toInt()));
-    ui->thirdPilotNameLineEdit->setText(completionData.pilotsIdMap.value(flight_data.value(OPL::Db::FLIGHTS_THIRDPILOT).toInt()));
+    ui->acftLineEdit->setText(DBCache->getTailsMap().value(flight_data.value(OPL::Db::FLIGHTS_ACFT).toInt()));
+    ui->picNameLineEdit->setText(DBCache->getPilotNamesMap().value(flight_data.value(OPL::Db::FLIGHTS_PIC).toInt()));
+    ui->sicNameLineEdit->setText(DBCache->getPilotNamesMap().value(flight_data.value(OPL::Db::FLIGHTS_SECONDPILOT).toInt()));
+    ui->thirdPilotNameLineEdit->setText(DBCache->getPilotNamesMap().value(flight_data.value(OPL::Db::FLIGHTS_THIRDPILOT).toInt()));
 
     //Function
     const QHash<int, QString> functions = {
@@ -248,6 +235,25 @@ void NewFlightDialog::fillWithEntryData()
         emit line_edit->editingFinished();
 }
 
+bool NewFlightDialog::verifyUserInput(QLineEdit *line_edit, const UserInput &input)
+{
+    DEB << "Verifying user input: " << line_edit->text();
+
+    if(!input.isValid()) {
+        QString fixed = input.fixup();
+        if(fixed == QString()) {
+            onBadInputReceived(line_edit);
+            return false;
+        } else {
+            line_edit->setText(fixed);
+            onGoodInputReceived(line_edit);
+            return true;
+        }
+    }
+    onGoodInputReceived(line_edit);
+    return true;
+}
+
 void NewFlightDialog::onGoodInputReceived(QLineEdit *line_edit)
 {
     DEB << line_edit->objectName() << " - Good input received - " << line_edit->text();
@@ -303,13 +309,9 @@ bool NewFlightDialog::addNewTail(QLineEdit& parent_line_edit)
             DEB << "New Tail Entry added:";
             DEB << DB->getTailEntry(DB->getLastEntry(OPL::DbTable::Tails));
 
-            // update completion Data and Completer
-            completionData.updateTails();
-            auto new_model = new QStringListModel(completionData.tailsList, parent_line_edit.completer());
-            parent_line_edit.completer()->setModel(new_model); //setModel deletes old model if it has the completer as parent
-
-            // update Line Edit
-            parent_line_edit.setText(completionData.tailsIdMap.value(DB->getLastEntry(OPL::DbTable::Tails)));
+            // update Line Edit with newly added tail
+            parent_line_edit.setText(DBCache->getTailsMap().value(DB->getLastEntry(OPL::DbTable::Tails)));
+            emit parent_line_edit.editingFinished();
             return true;
         } else {
             return false;
@@ -341,13 +343,10 @@ bool NewFlightDialog::addNewPilot(QLineEdit& parent_line_edit)
         if (ret == QDialog::Accepted) {
             DEB << "New Pilot Entry added:";
             DEB << DB->getPilotEntry(DB->getLastEntry(OPL::DbTable::Pilots));
-            // update completion Data and Completer
-            completionData.updatePilots();
-            auto new_model = new QStringListModel(completionData.pilotList, parent_line_edit.completer());
-            parent_line_edit.completer()->setModel(new_model); //setModel deletes old model if it has the completer as parent
 
-            // update Line Edit
-            parent_line_edit.setText(completionData.pilotsIdMap.value(DB->getLastEntry(OPL::DbTable::Pilots)));
+            // update Line Edit with newly added pilot
+            parent_line_edit.setText(DBCache->getPilotNamesMap().value(DB->getLastEntry(OPL::DbTable::Pilots)));
+            emit parent_line_edit.editingFinished();
             return true;
         } else {
             return false;
@@ -382,7 +381,7 @@ OPL::RowData_T NewFlightDialog::prepareFlightEntryData()
     // Night
     new_data.insert(OPL::Db::FLIGHTS_TNIGHT, night_time_data.nightMinutes);
     // Aircraft
-    int acft_id = completionData.tailsIdMap.key(ui->acftLineEdit->text());
+    int acft_id = DBCache->getTailsMap().key(ui->acftLineEdit->text());
     new_data.insert(OPL::Db::FLIGHTS_ACFT, acft_id);
     const OPL::TailEntry acft_data = DB->getTailEntry(acft_id);
     bool multi_pilot = acft_data.getData().value(OPL::Db::TAILS_MULTIPILOT).toBool();
@@ -402,9 +401,9 @@ OPL::RowData_T NewFlightDialog::prepareFlightEntryData()
         new_data.insert(OPL::Db::FLIGHTS_TSPME, block_minutes);
     }
     // Pilots
-    new_data.insert(OPL::Db::FLIGHTS_PIC, completionData.pilotsIdMap.key(ui->picNameLineEdit->text()));
-    new_data.insert(OPL::Db::FLIGHTS_SECONDPILOT, completionData.pilotsIdMap.key(ui->sicNameLineEdit->text()));
-    new_data.insert(OPL::Db::FLIGHTS_THIRDPILOT, completionData.pilotsIdMap.key(ui->thirdPilotNameLineEdit->text()));
+    new_data.insert(OPL::Db::FLIGHTS_PIC, DBCache->getPilotNamesMap().key(ui->picNameLineEdit->text()));
+    new_data.insert(OPL::Db::FLIGHTS_SECONDPILOT, DBCache->getPilotNamesMap().key(ui->sicNameLineEdit->text()));
+    new_data.insert(OPL::Db::FLIGHTS_THIRDPILOT, DBCache->getPilotNamesMap().key(ui->thirdPilotNameLineEdit->text()));
     // IFR time
     if (ui->ifrCheckBox->isChecked()) {
         new_data.insert(OPL::Db::FLIGHTS_TIFR, block_minutes);
@@ -514,124 +513,49 @@ void NewFlightDialog::toUpper(const QString &text)
 void NewFlightDialog::onTimeLineEdit_editingFinished()
 {
     auto line_edit = this->findChild<QLineEdit*>(sender()->objectName());
-    DEB << line_edit->objectName() << "Editing finished -" << line_edit->text();
-
-    const QString time_string = OPL::Time::formatTimeInput(line_edit->text());
-    const QTime time = OPL::Time::fromString(time_string);
-
-    if (time.isValid()) {
-        line_edit->setText(time_string);
-        onGoodInputReceived(line_edit);
-    } else {
-        onBadInputReceived(line_edit);
-        line_edit->setText(QString());
-    }
-
+    verifyUserInput(line_edit, TimeInput(line_edit->text()));
 }
 
 void NewFlightDialog::onPilotNameLineEdit_editingFinshed()
 {
     auto line_edit = this->findChild<QLineEdit*>(sender()->objectName());
-    DEB << line_edit->objectName() << "Editing Finished -" << line_edit->text();
-
-    int pilot_id = 0;
-
-    // Check for self and try mapping to rowid
-    if(line_edit->text().contains(self, Qt::CaseInsensitive)) {
-        DEB << "self recognized.";
-        line_edit->setText(completionData.pilotsIdMap.value(1));
-        pilot_id = 1;
-    } else
-        pilot_id = completionData.pilotsIdMap.key(line_edit->text());
-
-
-    if(pilot_id != 0) {
-        DEB << "Mapped: " << line_edit->text() << pilot_id;
-        onGoodInputReceived(line_edit);
-        return;
-    }
-
-    if (line_edit->text().isEmpty()) {
-        if (line_edit->objectName() == QLatin1String("picNameLineEdit"))
-            validationState.invalidate(mandatoryLineEdits->indexOf(line_edit));
-        return;
-    }
-
-    if (!line_edit->completer()->currentCompletion().isEmpty()) {
-        DEB << "Trying to fix input...";
-        line_edit->setText(line_edit->completer()->currentCompletion());
-        emit line_edit->editingFinished();
-        return;
+    if(!verifyUserInput(line_edit, PilotInput(line_edit->text()))) {
+        if(!addNewPilot(*line_edit))
+            onBadInputReceived(line_edit);
     }
-
-    // Fall through to adding new pilot to database
-    if(!addNewPilot(*line_edit))
-        onBadInputReceived(line_edit);
 }
 
 void NewFlightDialog::onLocationLineEdit_editingFinished()
 {
-    const QString line_edit_name = sender()->objectName();
+    const QString& line_edit_name = sender()->objectName();
     const auto line_edit = this->findChild<QLineEdit*>(line_edit_name);
-    DEB << line_edit->objectName() << "Editing Finished -" << line_edit->text();
     QLabel* name_label;
-    if (line_edit_name.contains(QLatin1String("dept"))) {
+    if (line_edit_name.contains(QLatin1String("dept")))
         name_label = ui->deptNameLabel;
-    } else {
+    else
         name_label = ui->destNameLabel;
-    }
 
-    const auto &text = line_edit->text();
-    int airport_id = 0;
 
-    // try to map iata or icao code to airport id;
-    if (text.length() == 3) {
-        airport_id = completionData.airportIataIdMap.key(text);
-    } else {
-        airport_id = completionData.airportIcaoIdMap.key(text);
-    }
-    // check result
-    if (airport_id == 0) {
-        // to do: prompt user how to handle unknown airport
-        name_label->setText(tr("Unknown airport identifier"));
-        onBadInputReceived(line_edit);
-        return;
-    }
-    line_edit->setText(completionData.airportIcaoIdMap.value(airport_id));
-    name_label->setText(completionData.airportNameIdMap.value(airport_id));
-    onGoodInputReceived(line_edit);
+    if(verifyUserInput(line_edit, AirportInput(line_edit->text())) ) {
+        // Match ICAO code with Airport Name and display on label
+        name_label->setText(DBCache->getAirportsMapNames().value(
+                                DBCache->getAirportsMapICAO().key(
+                                    line_edit->text())));
+    } else
+        name_label->setText("Unknown Airport");
 }
 
 void NewFlightDialog::on_acftLineEdit_editingFinished()
 {
     const auto line_edit = ui->acftLineEdit;
-    int acft_id = completionData.tailsIdMap.key(line_edit->text());
-    DEB << line_edit->text() << " has acft_id: " << acft_id;
-    DEB << completionData.tailsIdMap;
-
-    if (acft_id != 0) { // Success
-        onGoodInputReceived(line_edit);
-        return;
-    }
-    // check for whitespaces
-    acft_id = completionData.tailsIdMap.key(line_edit->text().split(" ").first());
-    if (acft_id != 0) {
-        line_edit->setText(completionData.tailsIdMap.value(acft_id));
-        onGoodInputReceived(line_edit);
-        return;
-    }
-
 
-    // try to use a completion
-    if (!line_edit->completer()->currentCompletion().isEmpty() && !line_edit->text().isEmpty()) {
-        line_edit->setText(line_edit->completer()->currentCompletion().split(QLatin1Char(' ')).first());
-        emit line_edit->editingFinished();
-        return;
-    }
-
-    if (!(line_edit->text() == QString()))
+    if(!verifyUserInput(line_edit, TailInput(line_edit->text())))
         if(!addNewTail(*line_edit))
             onBadInputReceived(line_edit);
+
+    const auto space = QLatin1Char(' ');
+    if(line_edit->text().contains(space))
+        line_edit->setText(line_edit->text().split(space)[0]); // strip out autocomplete suggestion
 }
 
 void NewFlightDialog::on_doftLineEdit_editingFinished()
@@ -680,18 +604,17 @@ void NewFlightDialog::on_approachComboBox_currentTextChanged(const QString &arg1
  */
 void NewFlightDialog::on_functionComboBox_currentIndexChanged(int index)
 {
-    DEB << "Current Index: " << index;
     if (index == static_cast<int>(OPL::PilotFunction::PIC)) {
         ui->picNameLineEdit->setText(self);
         emit ui->picNameLineEdit->editingFinished();
-        if (completionData.pilotsIdMap.key(ui->picNameLineEdit->text())
-         == completionData.pilotsIdMap.key(ui->sicNameLineEdit->text()))
+        if (DBCache->getPilotNamesMap().key(ui->picNameLineEdit->text())
+         == DBCache->getPilotNamesMap().key(ui->sicNameLineEdit->text()))
                 ui->sicNameLineEdit->setText(QString());
     } else if (index == static_cast<int>(OPL::PilotFunction::SIC)) {
         ui->sicNameLineEdit->setText(self);
         emit ui->sicNameLineEdit->editingFinished();
-        if (completionData.pilotsIdMap.key(ui->picNameLineEdit->text())
-         == completionData.pilotsIdMap.key(ui->sicNameLineEdit->text()))
+        if (DBCache->getPilotNamesMap().key(ui->picNameLineEdit->text())
+         == DBCache->getPilotNamesMap().key(ui->sicNameLineEdit->text()))
             ui->picNameLineEdit->setText(QString());
     }
 }
@@ -707,7 +630,7 @@ void NewFlightDialog::on_functionComboBox_currentIndexChanged(int index)
  */
 bool NewFlightDialog::checkPilotFunctionsValid()
 {
-    int pic_id = completionData.pilotsIdMap.key(ui->picNameLineEdit->text());
+    int pic_id = DBCache->getPilotNamesMap().key(ui->picNameLineEdit->text());
     int function_index = ui->functionComboBox->currentIndex();
 
     if (pic_id == 1) {
@@ -768,11 +691,10 @@ void NewFlightDialog::on_buttonBox_accepted()
         return;
 
     // If input verification passed, collect input and submit to database
-    auto newData = prepareFlightEntryData();
+    const auto newData = prepareFlightEntryData();
     DEB << "Old Data: ";
     DEB << flightEntry;
 
-    //DEB << "Setting Data for flightEntry...";
     flightEntry.setData(newData);
     DEB << "Committing: ";
     DEB << flightEntry;

+ 6 - 54
src/gui/dialogues/newflightdialog.h

@@ -24,57 +24,10 @@
 #include <QList>
 #include <QBitArray>
 
-#include "src/functions/time.h"
-#include "src/database/database.h"
-#include "src/database/dbcompletiondata.h"
+#include "src/gui/verification/userinput.h"
 #include "src/opl.h"
 #include "src/database/row.h"
-
-
-
-/*!
- * \brief The ValidationState class encapsulates a QBitArray that has a bit set (or unset) depending on wether the
- * input for the associated index has been verified. The indexes correspond to the mandatory items enumerated in the
- * ValidationItem enum.
- */
-class ValidationState {
-public:
-    ValidationState() = default;
-
-    /*!
-     * \brief The ValidationItem enum contains the items that are mandatory for logging a flight:
-     * Date of Flight, Departure, Destination, Time Off Blocks, Time On Blocks, Pilot in Command, Aircraft Registration
-     */
-    enum ValidationItem {doft = 0, dept = 1, dest = 2, tofb = 3, tonb = 4, pic = 5, acft = 6};
-
-    void validate(ValidationItem item)             { validationArray[item] = true;};
-    void validate(int index)                       { validationArray[index] = true;};
-    void invalidate(ValidationItem item)           { validationArray[item] = false;}
-    void invalidate(int index)                     { validationArray[index] = false;}
-    inline bool allValid() const                   { return validationArray.count(true) == 7;};
-    inline bool timesValid() const                 { return validationArray[ValidationItem::tofb] && validationArray[ValidationItem::tonb];}
-    inline bool locationsValid() const             { return validationArray[ValidationItem::dept] && validationArray[ValidationItem::dest];}
-    inline bool nightDataValid() const             { return timesValid() && locationsValid() && validationArray[ValidationItem::doft];}
-    inline bool acftValid() const                  { return validationArray[ValidationItem::acft];}
-    inline bool validAt(int index) const           { return validationArray[index];}
-    inline bool validAt(ValidationItem item) const { return validationArray[item];}
-
-    // Debug
-    void printValidationStatus() const {
-        QString deb_string("\033[mValidation State:\tdoft\tdept\tdest\ttofb\ttonb\tpic\tacft\n");
-        deb_string += "\t\t\t\t";
-        for (int i = 0; i < 7; i++) { //\033[32m
-            if (validationArray[i])
-                deb_string += "\t\033[32m" + QString::number(validationArray[i]);
-            else
-                deb_string += "\t\033[31m" + QString::number(validationArray[i]);
-        }
-        deb_string += QLatin1String("\u001b[38;5;75m"); // return to default DEB
-        qDebug().noquote() << deb_string;
-    }
-private:
-    QBitArray validationArray = QBitArray(7);
-};
+#include "src/gui/verification/validationstate.h"
 
 namespace Ui {
 class NewFlightDialog;
@@ -112,20 +65,17 @@ public:
 
     /*!
      * \brief NewFlightDialog - Creates a NewFlightDialog that can be used to add a new flight entry to the logbook
-     * \param completion_data - contains QStringLists for the QCompleter to autocomplete Airport Codes, Pilot Names and aircraft registrationsn
      */
-    explicit NewFlightDialog(OPL::DbCompletionData& completion_data, QWidget *parent = nullptr);
+    explicit NewFlightDialog(QWidget *parent = nullptr);
     /*!
      * \brief NewFlightDialog - Creates a NewFlightDialog that can be used to edit an existing entry in the logbook
-     * \param completion_data - contains QStringLists for the QCompleter to autocomplete Airport Codes, Pilot Names and aircraft registrationsn
      * \param row_id - The database ROW ID of the entry to be edited
      */
-    explicit NewFlightDialog(OPL::DbCompletionData& completion_data, int row_id, QWidget* parent = nullptr);
+    explicit NewFlightDialog(int row_id, QWidget* parent = nullptr);
     ~NewFlightDialog();
 
 private:
     Ui::NewFlightDialog *ui;
-    OPL::DbCompletionData completionData;
     ValidationState validationState;
 
     /*!
@@ -159,6 +109,7 @@ private:
     void setupSignalsAndSlots();
     void readSettings();
     void fillWithEntryData();
+    bool verifyUserInput(QLineEdit *line_edit, const UserInput &input);
 
     /*!
      * \brief onGoodInputReceived - Sets a verification bit for the line edit that has been edited.
@@ -184,6 +135,7 @@ private:
     bool checkPilotFunctionsValid();
     OPL::RowData_T prepareFlightEntryData();
 
+    const static inline auto CAT_3 = QLatin1String(OPL::GLOBALS->getApproachTypes()[3].toLatin1());
 
 private slots:
     void toUpper(const QString& text);

+ 2 - 9
src/gui/dialogues/newpilotdialog.cpp

@@ -16,13 +16,12 @@
  *along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 #include "newpilotdialog.h"
+#include "src/gui/verification/completerprovider.h"
 #include "ui_newpilot.h"
 #include "src/opl.h"
 
 #include "src/database/database.h"
-#include "src/database/dbcompletiondata.h"
 #include "src/database/row.h"
-#include "src/functions/log.h"
 
 /*!
  * \brief NewPilotDialog::NewPilotDialog - creates a new pilot dialog which can be used to add a new entry to the database
@@ -32,8 +31,6 @@ NewPilotDialog::NewPilotDialog(QWidget *parent) :
     ui(new Ui::NewPilot)
 {
     setup();
-
-    //pilotEntry = APilotEntry();
     ui->lastnameLineEdit->setFocus();
 }
 
@@ -61,11 +58,7 @@ NewPilotDialog::~NewPilotDialog()
 void NewPilotDialog::setup()
 {
     ui->setupUi(this);
-
-    auto completer = new QCompleter(OPL::DbCompletionData::getCompletionList(OPL::CompleterTarget::Companies), ui->companyLineEdit);
-    completer->setCompletionMode(QCompleter::InlineCompletion);
-    completer->setCaseSensitivity(Qt::CaseSensitive);
-    ui->companyLineEdit->setCompleter(completer);
+    ui->companyLineEdit->setCompleter(QCompleterProvider.getCompleter(CompleterProvider::Companies));
 }
 
 void NewPilotDialog::on_buttonBox_accepted()

+ 18 - 13
src/gui/dialogues/newsimdialog.cpp

@@ -1,10 +1,11 @@
 #include "newsimdialog.h"
+#include "src/database/databasecache.h"
+#include "src/gui/verification/timeinput.h"
 #include "ui_newsimdialog.h"
 #include "src/opl.h"
 #include "src/functions/time.h"
 #include "src/functions/datetime.h"
 #include "src/database/database.h"
-#include "src/database/dbcompletiondata.h"
 #include <QCompleter>
 /*!
  * \brief create a NewSimDialog to add a new Simulator Entry to the database
@@ -40,7 +41,7 @@ void NewSimDialog::init()
 {
     OPL::GLOBALS->loadSimulatorTypes(ui->deviceTypeComboBox);
 
-    const QStringList aircraft_list = OPL::DbCompletionData::getCompletionList(OPL::CompleterTarget::AircraftTypes);
+    const QStringList aircraft_list = DBCache->getAircraftList();
     auto completer = new QCompleter(aircraft_list, ui->aircraftTypeLineEdit);
     completer->setCaseSensitivity(Qt::CaseInsensitive);
     completer->setCompletionMode(QCompleter::PopupCompletion);
@@ -86,15 +87,18 @@ void NewSimDialog::on_dateLineEdit_editingFinished()
 
 void NewSimDialog::on_totalTimeLineEdit_editingFinished()
 {
-    const QString time_string = OPL::Time::formatTimeInput(ui->totalTimeLineEdit->text());
-    const QTime time = OPL::Time::fromString(time_string);
-
-    if (time.isValid()) {
-        ui->totalTimeLineEdit->setText(time_string);
-        ui->totalTimeLineEdit->setStyleSheet(QString());
-    } else {
-        ui->totalTimeLineEdit->setText(QString());
-        ui->totalTimeLineEdit->setStyleSheet(OPL::Styles::RED_BORDER);
+    const auto input = TimeInput(ui->totalTimeLineEdit->text());
+    if(input.isValid())
+        return;
+    else {
+        QString fixed = input.fixup();
+        if(fixed == QString()) {
+            ui->totalTimeLineEdit->setText(QString());
+            ui->totalTimeLineEdit->setStyleSheet(OPL::Styles::RED_BORDER);
+        } else {
+            ui->totalTimeLineEdit->setText(fixed);
+            ui->totalTimeLineEdit->setStyleSheet(QString());
+        }
     }
 }
 
@@ -129,8 +133,9 @@ bool NewSimDialog::verifyInput(QString& error_msg)
         return false;
     }
     // Time
-    const QString time_string = OPL::Time::formatTimeInput(ui->totalTimeLineEdit->text());
-    const QTime time = OPL::Time::fromString(time_string);
+    if(!TimeInput(ui->totalTimeLineEdit->text()).isValid())
+        return false;
+    const QTime time = OPL::Time::fromString(ui->totalTimeLineEdit->text());
 
     if (!time.isValid()) {
         ui->totalTimeLineEdit->setStyleSheet(OPL::Styles::RED_BORDER);

+ 3 - 0
src/gui/dialogues/newsimdialog.ui

@@ -70,6 +70,9 @@
        <height>0</height>
       </size>
      </property>
+     <property name="placeholderText">
+      <string>00:00</string>
+     </property>
     </widget>
    </item>
    <item row="1" column="0">

+ 3 - 5
src/gui/dialogues/newtaildialog.cpp

@@ -16,9 +16,9 @@
  *along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 #include "newtaildialog.h"
+#include "src/database/databasecache.h"
 #include "ui_newtail.h"
 #include "src/opl.h"
-#include "src/database/dbcompletiondata.h"
 
 NewTailDialog::NewTailDialog(const QString &new_registration, QWidget *parent) :
     QDialog(parent),
@@ -65,8 +65,8 @@ NewTailDialog::~NewTailDialog()
  */
 void NewTailDialog::setupCompleter()
 {
-    idMap = OPL::DbCompletionData::getIdMap(OPL::CompleterTarget::AircraftTypes);
-    aircraftList = OPL::DbCompletionData::getCompletionList(OPL::CompleterTarget::AircraftTypes);
+    idMap = DBCache->getAircraftMap();
+    aircraftList = DBCache->getAircraftList();
 
     QCompleter *completer = new QCompleter(aircraftList, ui->searchLineEdit);
     completer->setCaseSensitivity(Qt::CaseInsensitive);
@@ -78,8 +78,6 @@ void NewTailDialog::setupCompleter()
                      this, &NewTailDialog::onSearchCompleterActivated);
     QObject::connect(completer, static_cast<void(QCompleter::*)(const QString &)>(&QCompleter::highlighted),
                      this, &NewTailDialog::onSearchCompleterActivated);
-
-
 }
 
 void NewTailDialog::setupValidators()

+ 24 - 0
src/gui/verification/airportinput.cpp

@@ -0,0 +1,24 @@
+#include "airportinput.h"
+#include "src/database/databasecache.h"
+bool AirportInput::isValid() const
+{
+    return DBCache->getAirportsMapICAO().key(input) != 0;
+}
+
+QString AirportInput::fixup() const
+{
+    QString fixed;
+
+    if(input.length() == 3) {
+        //input could be IATA code, try to look up Airport ID and match to ICAO code
+        int id = DBCache->getAirportsMapIATA().key(input.toUpper());
+        if (id != 0)
+            fixed = DBCache->getAirportsMapICAO().value(id);
+    } else {
+        //input could be lower case
+        int id = DBCache->getAirportsMapICAO().key(input.toUpper());
+        if (id != 0)
+            fixed = input.toUpper();
+    }
+    return fixed;
+}

+ 36 - 0
src/gui/verification/airportinput.h

@@ -0,0 +1,36 @@
+#ifndef AIRPORTINPUT_H
+#define AIRPORTINPUT_H
+
+#include "userinput.h"
+
+/*!
+ * \brief Checks if a user input is a valid airport code
+ * \details The user can input either ICAO or IATA Airport Codes, but both will be
+ * matched against ICAO codes, which are more widespread and used exclusively in the database
+ * because there are airports which don't have an IATA code, mainly smaller non-commercial ones.
+ *
+ * The AirportInput class compares an input string against a Hash Map of Airport Codes and their respective
+ * row id's, which are cached from the database.
+ */
+class AirportInput : public UserInput
+{
+public:
+    AirportInput() = delete;
+    AirportInput(const QString &input) : UserInput(input) {}
+
+    /*!
+     * \brief An input is considered valid if it can be matched to an ICAO code present in the database
+     */
+    bool isValid() const override;
+
+    /*!
+     * \brief fixup tries to convert IATA to ICAO code and converts lower to upper case
+     * \details The input is converted to upper case and then compared against ICAO and IATA
+     * codes present in the database. If a match is found, the valid ICAO code from the database
+     * cache is returned, otherwise an empty QString.
+     */
+    QString fixup() const override;
+
+};
+
+#endif // AIRPORTINPUT_H

+ 110 - 0
src/gui/verification/completerprovider.cpp

@@ -0,0 +1,110 @@
+#include "completerprovider.h"
+#include "src/database/databasecache.h"
+
+//namespace OPL {
+
+CompleterProvider::CompleterProvider()
+{
+    pilotCompleter    = new QCompleter(DBCache->getPilotNamesList());
+    tailsCompleter    = new QCompleter(DBCache->getTailsList());
+    airportCompleter  = new QCompleter(DBCache->getAirportList());
+    companyCompleter  = new QCompleter(DBCache->getCompaniesList());
+    aircraftCompleter = new QCompleter(DBCache->getAircraftList());
+
+    QList<QCompleter*> completers = {
+        pilotCompleter,
+        tailsCompleter,
+        airportCompleter,
+        companyCompleter,
+        aircraftCompleter,
+    };
+    for (const auto completer : completers) {
+        completer->setCaseSensitivity(Qt::CaseInsensitive);
+        completer->setCompletionMode(QCompleter::PopupCompletion);
+        completer->setFilterMode(Qt::MatchContains);
+    }
+
+    // Listen for changes in Database Cache
+    QObject::connect(DBCache,    	&OPL::DatabaseCache::databaseCacheUpdated,
+                     this,			&CompleterProvider::onDatabaseCacheUpdated);
+}
+
+CompleterProvider::~CompleterProvider()
+{
+    pilotCompleter->deleteLater();
+    tailsCompleter->deleteLater();
+    airportCompleter->deleteLater();
+}
+
+
+QCompleter *CompleterProvider::getCompleter(CompleterTarget target) const
+{
+    switch (target) {
+    case Airports:
+        return airportCompleter;
+        break;
+    case Pilots:
+        return pilotCompleter;
+        break;
+    case Tails:
+        return tailsCompleter;
+    case Aircraft:
+        return aircraftCompleter;
+    case Companies:
+        return companyCompleter;
+        break;
+    default:
+        return nullptr;
+        break;
+    }
+}
+
+void CompleterProvider::onDatabaseCacheUpdated(const OPL::DbTable table)
+{
+    switch (table) {
+    case OPL::DbTable::Pilots:
+        DEB << "Pilots completer model updated...";
+        updateModel(CompleterTarget::Pilots);
+        break;
+    case OPL::DbTable::Tails:
+        DEB << "Tails completer model updated...";
+        updateModel(CompleterTarget::Tails);
+        break;
+    case OPL::DbTable::Airports:
+        DEB << "Airports completer model updated...";
+        updateModel(CompleterTarget::Airports);
+        break;
+    default:
+        break;
+    }
+}
+
+void CompleterProvider::updateModel(CompleterTarget target)
+{
+    const QStringList *newData = nullptr;
+    QStringListModel* model = nullptr;
+
+    switch(target) {
+    case Airports:
+        newData = &DBCache->getAirportList();
+        model = qobject_cast<QStringListModel*>(airportCompleter->model());
+        break;
+    case Pilots:
+        newData = &DBCache->getPilotNamesList();
+        model = qobject_cast<QStringListModel*>(pilotCompleter->model());
+        break;
+    case Tails: {
+        newData = &DBCache->getTailsList();
+        model = qobject_cast<QStringListModel*>(tailsCompleter->model());
+        break;
+    }
+    default:
+        break;
+    }
+
+    if(newData == nullptr) return;
+
+    model->setStringList(*newData);
+}
+
+//} // namespace OPL

+ 54 - 0
src/gui/verification/completerprovider.h

@@ -0,0 +1,54 @@
+#ifndef VALIDATORFACTORY_H
+#define VALIDATORFACTORY_H
+#include "src/opl.h"
+#include <QCompleter>
+
+#define QCompleterProvider CompleterProvider::getInstance()
+/*!
+ * \brief The CompleterProvider class provides static QCompleter instances
+ * that are set up with application-wide behaviour standards.
+ * \details In order to facilitate correct and user-friendly input
+ * throughout the application, a combination of different methods are
+ * used. The QCompleter is used on QLineEdit input fields and provides
+ * pattern-completion. The instances created by this factory are
+ * set up with application-wide behaviour standards in mind to create
+ * a consistent user experience. The QCompleters' models are based on
+ * input from the database, so whenever the database content is modified,
+ * the completion model is updated via the databaseCacheUpdated Signal.
+ */
+class CompleterProvider : public QObject
+{
+    Q_OBJECT
+    CompleterProvider();
+
+    QCompleter* pilotCompleter;
+    QCompleter* airportCompleter;
+    QCompleter* tailsCompleter;
+    QCompleter* companyCompleter;
+    QCompleter* aircraftCompleter;
+public:
+    static CompleterProvider& getInstance() {
+        static CompleterProvider instance;
+        return instance;
+    }
+
+    CompleterProvider(CompleterProvider const&) = delete;
+    void operator=(CompleterProvider const&) = delete;
+    ~CompleterProvider();
+
+    enum CompleterTarget { Pilots, Tails, Aircraft, Airports, Companies };
+
+    /*!
+     * \brief updates the completion model for a given QCompleter
+     */
+    void updateModel(CompleterTarget target);
+
+    /*!
+     * \brief return a pointer to the completer instance
+     */
+    QCompleter *getCompleter(CompleterTarget target) const;
+public slots:
+    void onDatabaseCacheUpdated(const OPL::DbTable table);
+};
+
+#endif // VALIDATORFACTORY_H

+ 18 - 0
src/gui/verification/pilotinput.cpp

@@ -0,0 +1,18 @@
+#include "pilotinput.h"
+#include "src/database/databasecache.h"
+#include "src/gui/verification/completerprovider.h"
+
+bool PilotInput::isValid() const
+{
+    return DBCache->getPilotNamesMap().key(input) != 0;
+}
+
+QString PilotInput::fixup() const
+{
+    if (input.contains(self, Qt::CaseInsensitive))
+        return DBCache->getPilotNamesMap().value(1);
+
+    QCompleter* completer = CompleterProvider::getInstance().getCompleter(CompleterProvider::Pilots);
+    completer->setCompletionPrefix(input);
+    return completer->currentCompletion();
+}

+ 28 - 0
src/gui/verification/pilotinput.h

@@ -0,0 +1,28 @@
+#ifndef PILOTINPUT_H
+#define PILOTINPUT_H
+
+#include "userinput.h"
+
+class PilotInput : public UserInput
+{
+public:
+    PilotInput() = delete;
+    PilotInput(const QString& userInput) : UserInput(userInput) {}
+
+    /*!
+     * \brief Checks if a user-given Pilot Name is present in the database
+     * \details An input String is compared against a Hash Map of values present
+     * in the database, if an exact match is found, the input is considered valid.
+     */
+    bool isValid() const override;
+    /*!
+     * \brief Tries to match a user given input to values present in the database.
+     * \details The user input is compared to a list of values present in the database
+     * and the closest match returned, or an empty string if none found.
+     */
+    QString fixup() const override;
+private:
+    const static inline QLatin1String self{"self"};
+};
+
+#endif // PILOTINPUT_H

+ 15 - 0
src/gui/verification/tailinput.cpp

@@ -0,0 +1,15 @@
+#include "tailinput.h"
+#include "src/database/databasecache.h"
+#include "src/gui/verification/completerprovider.h"
+
+bool TailInput::isValid() const
+{
+    return DBCache->getTailsMap().key(input) != 0;
+}
+
+QString TailInput::fixup() const
+{
+    QCompleter *completer = QCompleterProvider.getCompleter(CompleterProvider::Tails);
+    completer->setCompletionPrefix(input);
+    return completer->currentCompletion();
+}

+ 16 - 0
src/gui/verification/tailinput.h

@@ -0,0 +1,16 @@
+#ifndef TAILINPUT_H
+#define TAILINPUT_H
+
+#include "userinput.h"
+
+class TailInput : public UserInput
+{
+public:
+    TailInput() = delete;
+    TailInput(const QString &input) : UserInput(input) {}
+public:
+    bool isValid() const override;
+    QString fixup() const override;
+};
+
+#endif // TAILINPUT_H

+ 34 - 0
src/gui/verification/timeinput.cpp

@@ -0,0 +1,34 @@
+#include "timeinput.h"
+#include "src/opl.h"
+#include <QTime>
+
+bool TimeInput::isValid() const
+{
+     return QTime::fromString(input, OPL::Format::TIME_FORMAT).isValid();
+}
+
+/*!
+ * \brief Tries to format user input to hh:mm.
+ * \details If the user input is not a valid time, this function tries to fix it. Accepted Inputs are
+ * hh:mm, h:mm, hhmm or hmm. Returns "hh:mm" or an empty string if the resulting time is invalid.
+ */
+QString TimeInput::fixup() const
+{
+    QString fixed = input;
+
+    if (input.contains(':')) { // contains seperator
+        if(input.length() == 4)
+            fixed.prepend('0');
+    } else { // does not contain seperator
+        if(input.length() == 4) {
+            fixed.insert(2, ':');
+        }
+        if(input.length() == 3) {
+            fixed.prepend('0');
+            fixed.insert(2, ':');
+        }
+    }
+
+    QTime time = QTime::fromString(fixed, OPL::Format::TIME_FORMAT);
+    return time.toString(OPL::Format::TIME_FORMAT);
+}

+ 30 - 0
src/gui/verification/timeinput.h

@@ -0,0 +1,30 @@
+#ifndef TIMEINPUT_H
+#define TIMEINPUT_H
+
+#include "userinput.h"
+
+class TimeInput : public UserInput
+{
+public:
+    TimeInput() = delete;
+    TimeInput(const QString &input) : UserInput(input) {}
+
+    /*!
+     * \brief Checks if a user entered String is a valid time input
+     * \details A user input is considered a valid time, if it complies with the ISO-8601 standard,
+     * and a valid QTime object can be derived from it.
+     */
+    bool isValid() const override;
+
+    /*!
+     * \brief Tries to convert a user input String into a valid time string
+     * \details The user can input time in various formats, for example "hmm",
+     * "hhmm" or "hh:mm". The fixup function tries to interpret the input string
+     * and converts it into a QTime Object. If the resulting Time Object is valid,
+     * the ISO-8601 representation "hh:mm" is returned, otherwise an empty QString.
+     */
+    QString fixup() const override;
+private:
+};
+
+#endif // TIMEINPUT_H

+ 1 - 0
src/gui/verification/userinput.cpp

@@ -0,0 +1 @@
+#include "userinput.h"

+ 30 - 0
src/gui/verification/userinput.h

@@ -0,0 +1,30 @@
+#ifndef USERINPUT_H
+#define USERINPUT_H
+#include <QString>
+
+/*!
+ * \brief The UserInput class verifies and tries to correct user input.
+ * \details Basic User Input Verification is accomplished via QCompleters, QValidators and input masks. However, in many
+ * cases more complex logic is required to make sure user input is not just syntactically acceptable but also correct before
+ * submitting it to the database.
+ */
+class UserInput
+{
+public:
+    UserInput(const QString& input)
+        : input(input) {};
+    /*!
+     * \brief isValid determines if the user input is valid.
+     * \return
+     */
+    virtual bool isValid() const = 0;
+
+    /*!
+     * \brief try to correct the user input and return a corrected QString. If the user input cannot be corrected, return an empty string.
+     */
+    virtual QString fixup() const = 0;
+protected:
+    const QString& input;
+};
+
+#endif // USERINPUT_H

+ 49 - 0
src/gui/verification/validationstate.h

@@ -0,0 +1,49 @@
+#ifndef VALIDATIONSTATE_H
+#define VALIDATIONSTATE_H
+#include <QtCore>
+
+/*!
+ * \brief Holds information about whether mandatory components of a new flight entry have been validated.
+ * \details The ValidationState class encapsulates a QBitArray that has a bit set (or unset) depending on wether the
+ * input for the associated index has been verified. The indexes correspond to the mandatory items enumerated in the
+ * ValidationItem enum. It is used by the NewFlightDialog to keep track of validation states.
+ */
+class ValidationState {
+public:
+    ValidationState() = default;
+
+    /*!
+     * \brief The ValidationItem enum contains the items that are mandatory for logging a flight:
+     * Date of Flight, Departure, Destination, Time Off Blocks, Time On Blocks, Pilot in Command, Aircraft Registration
+     */
+    enum ValidationItem {doft = 0, dept = 1, dest = 2, tofb = 3, tonb = 4, pic = 5, acft = 6};
+
+    void validate(ValidationItem item)             { validationArray[item] = true;};
+    void validate(int index)                       { validationArray[index] = true;};
+    void invalidate(ValidationItem item)           { validationArray[item] = false;}
+    void invalidate(int index)                     { validationArray[index] = false;}
+    inline bool allValid() const                   { return validationArray.count(true) == 7;};
+    inline bool timesValid() const                 { return validationArray[ValidationItem::tofb] && validationArray[ValidationItem::tonb];}
+    inline bool locationsValid() const             { return validationArray[ValidationItem::dept] && validationArray[ValidationItem::dest];}
+    inline bool nightDataValid() const             { return timesValid() && locationsValid() && validationArray[ValidationItem::doft];}
+    inline bool acftValid() const                  { return validationArray[ValidationItem::acft];}
+    inline bool validAt(int index) const           { return validationArray[index];}
+    inline bool validAt(ValidationItem item) const { return validationArray[item];}
+
+    // Debug
+    void printValidationStatus() const {
+        QString deb_string("\033[mValidation State:\tdoft\tdept\tdest\ttofb\ttonb\tpic\tacft\n");
+        deb_string += "\t\t\t\t";
+        for (int i = 0; i < 7; i++) { //\033[32m
+            if (validationArray[i])
+                deb_string += "\t\033[32m" + QString::number(validationArray[i]);
+            else
+                deb_string += "\t\033[31m" + QString::number(validationArray[i]);
+        }
+        deb_string += QLatin1String("\u001b[38;5;75m"); // return to default DEB
+        qDebug().noquote() << deb_string;
+    }
+private:
+    QBitArray validationArray = QBitArray(7);
+};
+#endif // VALIDATIONSTATE_H

+ 24 - 0
src/gui/widgets/debugwidget.cpp

@@ -16,6 +16,10 @@
  *along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 #include "debugwidget.h"
+#include "src/gui/verification/airportinput.h"
+#include "src/gui/verification/completerprovider.h"
+#include "src/gui/verification/pilotinput.h"
+#include "src/gui/verification/timeinput.h"
 #include "src/testing/importCrewlounge/processaircraft.h"
 #include "src/testing/importCrewlounge/processflights.h"
 #include "src/testing/importCrewlounge/processpilots.h"
@@ -56,6 +60,8 @@ DebugWidget::DebugWidget(QWidget *parent) :
             ui->tableComboBox->addItem(table);
         }
     }
+    ui->debugLineEdit->setCompleter(QCompleterProvider.getCompleter(CompleterProvider::Airports));
+    ui->debug2LineEdit->setCompleter(QCompleterProvider.getCompleter(CompleterProvider::Airports));
 }
 
 DebugWidget::~DebugWidget()
@@ -273,3 +279,21 @@ void DebugWidget::changeEvent(QEvent *event)
  * #endif
  */
 
+
+void DebugWidget::on_debugLineEdit_editingFinished()
+{
+    PilotInput user_input = PilotInput(ui->debugLineEdit->text());
+    if(user_input.isValid())
+        DEB << "Good Input";
+    else {
+        DEB << "Fixing...";
+        ui->debugLineEdit->setText(user_input.fixup());
+    }
+}
+
+
+void DebugWidget::on_debug2LineEdit_editingFinished()
+{
+
+}
+

+ 4 - 0
src/gui/widgets/debugwidget.h

@@ -53,6 +53,10 @@ private slots:
 
     void on_debugPushButton_clicked();
 
+    void on_debugLineEdit_editingFinished();
+
+    void on_debug2LineEdit_editingFinished();
+
 private:
     Ui::DebugWidget *ui;
 

+ 6 - 14
src/gui/widgets/logbookwidget.cpp

@@ -23,20 +23,13 @@
 #include "src/classes/settings.h"
 #include "src/gui/dialogues/newflightdialog.h"
 #include "src/gui/dialogues/newsimdialog.h"
+#include "src/functions/time.h"
 
-const QHash<int, QString> FILTER_MAP = {
-    {0, QStringLiteral("Date LIKE \"%")},
-    {1, QStringLiteral("Dept LIKE \"%")},
-    {2, QStringLiteral("Dest LIKE \"%")},
-    {3, QStringLiteral("Registration LIKE \"%")},
-    {4, QStringLiteral("\"Name PIC\" LIKE \"%")}
-};
-const auto NON_WORD_CHAR = QRegularExpression("\\W");
 
-LogbookWidget::LogbookWidget(OPL::DbCompletionData& completion_data, QWidget *parent) :
+
+LogbookWidget::LogbookWidget(QWidget *parent) :
     QWidget(parent),
-    ui(new Ui::LogbookWidget),
-    completionData(completion_data)
+    ui(new Ui::LogbookWidget)
 {
     ui->setupUi(this);
 
@@ -297,9 +290,8 @@ void LogbookWidget::on_viewsComboBox_currentIndexChanged(int index)
 
 void LogbookWidget::on_actionEdit_Flight_triggered()
 {
-    completionData.update();
     if(selectedEntries.length() == 1){
-        NewFlightDialog nff(completionData,selectedEntries.first(), this);
+        NewFlightDialog nff(selectedEntries.first(), this);
         ui->stackedWidget->addWidget(&nff);
         ui->stackedWidget->setCurrentWidget(&nff);
         nff.setWindowFlag(Qt::Widget);
@@ -316,7 +308,7 @@ void LogbookWidget::on_actionEdit_Flight_triggered()
 void LogbookWidget::on_actionEdit_Sim_triggered()
 {
     if (selectedEntries.length() == 1) {
-        NewSimDialog nsd((selectedEntries.first() * -1), this);
+        NewSimDialog nsd((selectedEntries.first() * -1), this); // simulator entries have inverse row ID's in the model
         ui->stackedWidget->addWidget(&nsd);
         ui->stackedWidget->setCurrentWidget(&nsd);
         nsd.setWindowFlag(Qt::Widget);

+ 11 - 4
src/gui/widgets/logbookwidget.h

@@ -25,8 +25,8 @@
 #include <QDebug>
 #include <QMenu>
 #include <QTableView>
+#include "src/database/row.h"
 #include "src/gui/widgets/settingswidget.h"
-#include "src/database/dbcompletiondata.h"
 #include "src/opl.h"
 
 namespace Ui {
@@ -49,7 +49,7 @@ class LogbookWidget : public QWidget
     Q_OBJECT
 
 public:
-    explicit LogbookWidget(OPL::DbCompletionData &completion_data, QWidget *parent = nullptr);
+    explicit LogbookWidget(QWidget *parent = nullptr);
     ~LogbookWidget();
 
 private slots:
@@ -89,8 +89,6 @@ private:
 
     const QString getFlightSummary(const OPL::FlightEntry &flight) const;
 
-    OPL::DbCompletionData completionData;
-
     /*!
      * \brief isFlight Determines whether an entry shown in a view is a Flight or a Simulator.
      * \param model_row_id the row id in the QSqlTableModel used for displaying
@@ -100,6 +98,15 @@ private:
      */
     inline bool isFlight(int model_row_id) { return model_row_id > 0; }
 
+    const static inline QHash<int, QString> FILTER_MAP = {
+        {0, QStringLiteral("Date LIKE \"%")},
+        {1, QStringLiteral("Dept LIKE \"%")},
+        {2, QStringLiteral("Dest LIKE \"%")},
+        {3, QStringLiteral("Registration LIKE \"%")},
+        {4, QStringLiteral("\"Name PIC\" LIKE \"%")}
+    };
+    const static inline QRegularExpression NON_WORD_CHAR = QRegularExpression("\\W");
+
 protected:
     /*!
      * \brief Handles change events, like updating the UI to new localisation

+ 22 - 24
src/gui/widgets/pilotswidget.cpp

@@ -76,6 +76,15 @@ void PilotsWidget::connectSignalsAndSlots()
                      this,                              &PilotsWidget::tableView_headerClicked);
 }
 
+void PilotsWidget::setUiEnabled(bool enabled)
+{
+   ui->tableView->setEnabled(enabled);
+   ui->newPilotButton->setEnabled(enabled);
+   ui->deletePilotButton->setEnabled(enabled);
+   ui->pilotSearchLineEdit->setEnabled(enabled);
+   ui->pilotsSearchComboBox->setEnabled(enabled);
+}
+
 void PilotsWidget::changeEvent(QEvent *event)
 {
     if (event != nullptr)
@@ -94,11 +103,6 @@ void PilotsWidget::onPilotsWidget_databaseUpdated()
     refreshView();
 }
 
-void PilotsWidget::onNewPilotDialog_editingFinished()
-{
-    refreshView();
-}
-
 void PilotsWidget::on_pilotSearchLineEdit_textChanged(const QString &arg1)
 {
     model->setFilter(QLatin1Char('\"') + ui->pilotsSearchComboBox->currentText()
@@ -112,7 +116,7 @@ void PilotsWidget::tableView_selectionChanged()
         delete this->findChild<NewPilotDialog*>();
     }
 
-    auto *selection = ui->tableView->selectionModel();
+    auto selection = ui->tableView->selectionModel();
     selectedPilots.clear();
 
     for (const auto& row : selection->selectedRows()) {
@@ -120,17 +124,15 @@ void PilotsWidget::tableView_selectionChanged()
         DEB << "Selected Tails(s) with ID: " << selectedPilots;
     }
     if(selectedPilots.length() == 1) {
-
-        NewPilotDialog* np = new NewPilotDialog(selectedPilots.first(), this);
-        QObject::connect(np,   &QDialog::accepted,
-                         this, &PilotsWidget::onNewPilotDialog_editingFinished);
-        QObject::connect(np,   &QDialog::rejected,
-                         this, &PilotsWidget::onNewPilotDialog_editingFinished);
-        np->setWindowFlag(Qt::Widget);
-        np->setAttribute(Qt::WA_DeleteOnClose);
-        ui->stackedWidget->addWidget(np);
-        ui->stackedWidget->setCurrentWidget(np);
-        np->exec();
+        NewPilotDialog np = NewPilotDialog(selectedPilots.first(), this);
+        np.setWindowFlag(Qt::Widget);
+        ui->stackedWidget->addWidget(&np);
+        ui->stackedWidget->setCurrentWidget(&np);
+
+        setUiEnabled(false);
+        np.exec();
+        refreshView();
+        setUiEnabled(true);
     }
 }
 
@@ -142,13 +144,9 @@ void PilotsWidget::tableView_headerClicked(int column)
 
 void PilotsWidget::on_newPilotButton_clicked()
 {
-    NewPilotDialog* np = new NewPilotDialog(this);
-    QObject::connect(np,   &QDialog::accepted,
-                     this, &PilotsWidget::onNewPilotDialog_editingFinished);
-    QObject::connect(np,   &QDialog::rejected,
-                     this, &PilotsWidget::onNewPilotDialog_editingFinished);
-    np->setAttribute(Qt::WA_DeleteOnClose);
-    np->exec();
+    NewPilotDialog np = NewPilotDialog(this);
+    np.exec();
+    refreshView();
 }
 
 void PilotsWidget::on_deletePilotButton_clicked()

+ 2 - 1
src/gui/widgets/pilotswidget.h

@@ -63,7 +63,6 @@ private slots:
     void on_newPilotButton_clicked();
     void on_deletePilotButton_clicked();
     void onDeleteUnsuccessful();
-    void onNewPilotDialog_editingFinished();
     void on_pilotSearchLineEdit_textChanged(const QString &arg1);
 
 public slots:
@@ -102,6 +101,8 @@ private:
 
     void connectSignalsAndSlots();
 
+    void setUiEnabled(bool enabled);
+
     inline void refreshView(){model->select();}
 
 protected:

+ 42 - 38
src/gui/widgets/aircraftwidget.cpp → src/gui/widgets/tailswidget.cpp

@@ -15,14 +15,16 @@
  *You should have received a copy of the GNU General Public License
  *along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
-#include "aircraftwidget.h"
+#include "tailswidget.h"
 #include "ui_aircraftwidget.h"
 #include "src/opl.h"
 #include "src/classes/settings.h"
 #include "src/database/database.h"
 #include "src/database/row.h"
+#include "src/gui/dialogues/newtaildialog.h"
 
-AircraftWidget::AircraftWidget(QWidget *parent) :
+
+TailsWidget::TailsWidget(QWidget *parent) :
     QWidget(parent),
     ui(new Ui::AircraftWidget)
 {
@@ -34,12 +36,12 @@ AircraftWidget::AircraftWidget(QWidget *parent) :
     setupModelAndView();
 }
 
-AircraftWidget::~AircraftWidget()
+TailsWidget::~TailsWidget()
 {
     delete ui;
 }
 
-void AircraftWidget::setupModelAndView()
+void TailsWidget::setupModelAndView()
 {
     model = new QSqlTableModel(this);
     model->setTable(QStringLiteral("viewTails"));
@@ -67,19 +69,19 @@ void AircraftWidget::setupModelAndView()
     connectSignalsAndSlots();
 }
 
-void AircraftWidget::connectSignalsAndSlots()
+void TailsWidget::connectSignalsAndSlots()
 {
     QObject::connect(ui->tableView->selectionModel(),   &QItemSelectionModel::selectionChanged,
-                     this,                              &AircraftWidget::tableView_selectionChanged);
+                     this,                              &TailsWidget::tableView_selectionChanged);
     QObject::connect(ui->tableView->horizontalHeader(), &QHeaderView::sectionClicked,
-                     this,                              &AircraftWidget::tableView_headerClicked);
+                     this,                              &TailsWidget::tableView_headerClicked);
 }
 
 /*
  * Slots
  */
 
-void AircraftWidget::onAircraftWidget_settingChanged(SettingsWidget::SettingSignal signal)
+void TailsWidget::onAircraftWidget_settingChanged(SettingsWidget::SettingSignal signal)
 {
     if (signal != SettingsWidget::AircraftWidget)
         return;
@@ -87,37 +89,31 @@ void AircraftWidget::onAircraftWidget_settingChanged(SettingsWidget::SettingSign
     setupModelAndView();
 }
 
-void AircraftWidget::onAircraftWidget_dataBaseUpdated()
+void TailsWidget::onAircraftWidget_dataBaseUpdated()
 {
     refreshView();
 }
 
-void AircraftWidget::changeEvent(QEvent *event)
+void TailsWidget::changeEvent(QEvent *event)
 {
     if (event != nullptr)
         if(event->type() == QEvent::LanguageChange)
             ui->retranslateUi(this);
 }
 
-void AircraftWidget::onNewTailDialog_editingFinished()
-{
-    refreshView();
-}
-
-void AircraftWidget::on_newAircraftButton_clicked()
+void TailsWidget::on_newAircraftButton_clicked()
 {
     NewTailDialog nt(QString(), this);
-    QObject::connect(&nt,  &QDialog::accepted,
-                     this, &AircraftWidget::onNewTailDialog_editingFinished);
-    QObject::connect(&nt,  &QDialog::rejected,
-                     this, &AircraftWidget::onNewTailDialog_editingFinished);
+    setUiEnabled(false);
     nt.exec();
+    refreshView();
+    setUiEnabled(true);
 }
 
 /*!
  * \brief Displays a dialog in which the Tail can be edited
  */
-void AircraftWidget::tableView_selectionChanged()
+void TailsWidget::tableView_selectionChanged()
 {
     if (this->findChild<NewTailDialog*>() != nullptr)
         delete this->findChild<NewTailDialog*>();
@@ -130,23 +126,22 @@ void AircraftWidget::tableView_selectionChanged()
     }
 
     if(selectedTails.length() == 1) {
-        auto* nt = new NewTailDialog(selectedTails.first(), this);
-        QObject::connect(nt,   &QDialog::accepted,
-                         this, &AircraftWidget::onNewTailDialog_editingFinished);
-        QObject::connect(nt,   &QDialog::rejected,
-                         this, &AircraftWidget::onNewTailDialog_editingFinished);
-        ui->stackedWidget->addWidget(nt);
-        ui->stackedWidget->setCurrentWidget(nt);
-        nt->setWindowFlag(Qt::Widget);
-        nt->setAttribute(Qt::WA_DeleteOnClose);
-        nt->exec();
+        NewTailDialog nt(selectedTails.first(), this);
+        nt.setWindowFlag(Qt::Widget);
+        ui->stackedWidget->addWidget(&nt);
+        ui->stackedWidget->setCurrentWidget(&nt);
+
+        setUiEnabled(false);
+        nt.exec();
+        refreshView();
+        setUiEnabled(true);
     }
 }
 
 /*!
  * \brief Acts as a filter on the display model
  */
-void AircraftWidget::on_aircraftSearchLineEdit_textChanged(const QString &arg1)
+void TailsWidget::on_aircraftSearchLineEdit_textChanged(const QString &arg1)
 {
     if(ui->aircraftSearchComboBox->currentIndex() == 0){
         ui->aircraftSearchLineEdit->setText(arg1.toUpper());
@@ -156,13 +151,13 @@ void AircraftWidget::on_aircraftSearchLineEdit_textChanged(const QString &arg1)
                      + arg1 + QLatin1String("%\""));
 }
 
-void AircraftWidget::tableView_headerClicked(int column)
+void TailsWidget::tableView_headerClicked(int column)
 {
     sortColumn = column;
     Settings::write(Settings::UserData::TailSortColumn, column);
 }
 
-void AircraftWidget::on_deleteAircraftButton_clicked()
+void TailsWidget::on_deleteAircraftButton_clicked()
 {
     if (selectedTails.length() == 0) {
         INFO(tr("No Aircraft selected."));
@@ -209,7 +204,7 @@ void AircraftWidget::on_deleteAircraftButton_clicked()
  *
  * This function is used to inform the user and give hints on how to solve the problem.
  */
-void AircraftWidget::onDeleteUnsuccessful()
+void TailsWidget::onDeleteUnsuccessful()
 {
     QList<int> foreign_key_constraints = DB->getForeignKeyConstraints(selectedTails.first(),
                                                                        OPL::DbTable::Tails);
@@ -246,7 +241,16 @@ void AircraftWidget::onDeleteUnsuccessful()
     }
 }
 
-void AircraftWidget::repopulateModel()
+void TailsWidget::setUiEnabled(bool enabled)
+{
+    ui->newAircraftButton->setEnabled(enabled);
+    ui->deleteAircraftButton->setEnabled(enabled);
+    ui->tableView->setEnabled(enabled);
+    ui->aircraftSearchComboBox->setEnabled(enabled);
+    ui->aircraftSearchComboBox->setEnabled(enabled);
+}
+
+void TailsWidget::repopulateModel()
 {
     // unset the current model and delete it to avoid leak
     view->setModel(nullptr);
@@ -257,7 +261,7 @@ void AircraftWidget::repopulateModel()
     connectSignalsAndSlots();
 }
 
-const QString AircraftWidget::getAircraftTypeString(const OPL::Row &row) const
+const QString TailsWidget::getAircraftTypeString(const OPL::Row &row) const
 {
     QString type_string;
     if (!row.getData().value(OPL::Db::TAILS_MAKE).toString().isEmpty())
@@ -270,7 +274,7 @@ const QString AircraftWidget::getAircraftTypeString(const OPL::Row &row) const
     return type_string;
 }
 
-const QString AircraftWidget::getFlightSummary(const OPL::FlightEntry &flight) const
+const QString TailsWidget::getFlightSummary(const OPL::FlightEntry &flight) const
 {
     if(!flight.isValid())
         return QString();

+ 9 - 10
src/gui/widgets/aircraftwidget.h → src/gui/widgets/tailswidget.h

@@ -15,16 +15,15 @@
  *You should have received a copy of the GNU General Public License
  *along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
-#ifndef AIRCRAFTWIDGET_H
-#define AIRCRAFTWIDGET_H
+#ifndef TAILSWIDGET_H
+#define TAILSWIDGET_H
 
 #include <QWidget>
 #include <QItemSelection>
 #include <QSqlTableModel>
 #include <QTableView>
+#include "src/database/row.h"
 #include "src/gui/widgets/settingswidget.h"
-#include "src/gui/dialogues/newtaildialog.h"
-
 
 namespace Ui {
 class AircraftWidget;
@@ -50,13 +49,13 @@ class AircraftWidget;
  * templates of aircraft types. For example, 'D-ABCD' and 'N-XYZ' are different tails (Registrations), but they might be the same type of aircraft,
  * for example 'Boeing 737-800'.
  */
-class AircraftWidget : public QWidget
+class TailsWidget : public QWidget
 {
     Q_OBJECT
 
 public:
-    explicit AircraftWidget(QWidget *parent = nullptr);
-    ~AircraftWidget();
+    explicit TailsWidget(QWidget *parent = nullptr);
+    ~TailsWidget();
 
 private slots:
     void tableView_selectionChanged();
@@ -67,8 +66,6 @@ private slots:
 
     void on_newAircraftButton_clicked();
 
-    void onNewTailDialog_editingFinished();
-
     void on_aircraftSearchLineEdit_textChanged(const QString &arg1);
 
 public slots:
@@ -112,6 +109,8 @@ private:
 
     void onDeleteUnsuccessful();
 
+    void setUiEnabled(bool enabled);
+
     inline void refreshView(){model->select();}
 
 protected:
@@ -121,4 +120,4 @@ protected:
     void changeEvent(QEvent* event) override;
 };
 
-#endif // AIRCRAFTWIDGET_H
+#endif // TAILSWIDGET_H

+ 7 - 0
src/opl.h

@@ -280,6 +280,7 @@ private:
             QStringLiteral("OTHER")
     };
 };
+
 //Make available as a global static
 Q_GLOBAL_STATIC(OplGlobals, GLOBALS)
 
@@ -437,6 +438,12 @@ namespace Styles {
 const inline auto  RED_BORDER = QStringLiteral("border: 1px solid red");
 } // namespace Styles
 
+namespace Format {
+
+const inline auto TIME_FORMAT = QStringLiteral("hh:mm");
+
+} // namespace Format
+
 } // namespace opl
 
 #endif // OPLCONSTANTS_H