فهرست منبع

Rework of input verification started

Aim of this refactor is to simplify and centralise input verification. For this purpose, a new class DatabaseCache is created. This class caches relevant data from the database which is needed for input completion and verification. It automatically updates on database changes. 

The abstract UserInput class has the methods verify and fixup, which are implemented in subclasses for the specific type of user input.

The ValidatorProvider class provides QValidator instances for use of any line edits that may need it.
Felix Turowsky 2 سال پیش
والد
کامیت
61ca357d32

+ 13 - 0
CMakeLists.txt

@@ -74,6 +74,17 @@ set(PROJECT_SOURCES
     src/gui/widgets/settingswidget.cpp
     src/gui/widgets/settingswidget.h
     src/gui/widgets/settingswidget.ui
+    # Verification
+    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
 
 
     # Classes
@@ -115,6 +126,8 @@ set(PROJECT_SOURCES
     src/database/dbcompletiondata.cpp
     src/database/dbsummary.h
     src/database/dbsummary.cpp
+    src/database/databasecache.h
+    src/database/databasecache.cpp
 
     # Ressources
     assets/icons.qrc

+ 2 - 0
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()
@@ -55,6 +56,7 @@ MainWindow::MainWindow(QWidget *parent)
 
     // retreive completion lists and maps
     completionData.init();
+    OPL::DBCache.init();
 
     // Construct Widgets
     homeWidget = new HomeWidget(this);

+ 1 - 1
src/database/database.h

@@ -336,7 +336,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.

+ 191 - 0
src/database/databasecache.cpp

@@ -0,0 +1,191 @@
+#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();
+
+    // Listen to database for updates, reload cache if needed
+    QObject::connect(DB,   &OPL::Database::dataBaseUpdated,
+                     this, &DatabaseCache::update);
+}
+
+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 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 Airports:
+        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);
+    airportList = fetchList(Airports);
+}
+
+void DatabaseCache::updateSimulators()
+{
+    TODO << "not yet implemented";
+}
+
+void DatabaseCache::updatePilots()
+{
+    pilotNamesMap  = fetchMap(PilotNames);
+    pilotNamesList = fetchList(PilotNames);
+    companiesList  = fetchList(Companies);
+}
+
+void DatabaseCache::update(const 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;
+    default:
+        break;
+    }
+}
+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;
+}
+
+
+
+} // namespace OPL

+ 74 - 0
src/database/databasecache.h

@@ -0,0 +1,74 @@
+#ifndef DATABASECACHE_H
+#define DATABASECACHE_H
+#include "QtWidgets/qcompleter.h"
+#include "src/opl.h"
+#include <QtCore>
+
+namespace OPL{
+
+using IdMap = QHash<int, QString>;
+#define DBCache DatabaseCache::getInstance()
+
+/*!
+ * \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& getInstance() {
+        static DatabaseCache instance;
+        return instance;
+    }
+
+    DatabaseCache(DatabaseCache const&) = delete;
+    void operator=(DatabaseCache const&) = delete;
+
+    enum CompleterTarget {PilotNames, Tails, AircraftTypes, Airports, AirportsICAO, AirportsIATA, Companies};
+
+    void init();
+
+    const IdMap &getAirportsMapICAO() const;
+    const IdMap &getAirportsMapIATA() const;
+    const IdMap &getPilotNamesMap() const;
+
+    const QStringList &getPilotNamesList() const;
+    const QStringList &getTailsList() const;
+    const QStringList &getAirportList() const;
+    const QStringList &getCompaniesList() const;
+
+private:
+    Q_OBJECT
+    DatabaseCache() {};
+
+    // Id Maps
+    IdMap airportsMapICAO;
+    IdMap airportsMapIATA;
+    IdMap pilotNamesMap;
+    IdMap tailsMap;
+    // Lists
+    QStringList pilotNamesList;
+    QStringList tailsList;
+    QStringList airportList;
+    QStringList companiesList;
+
+    const IdMap fetchMap(CompleterTarget target);
+    const QStringList fetchList(CompleterTarget target);
+
+
+    void updateTails();
+    void updateAirports();
+    void updateSimulators();
+    void updatePilots();
+
+public slots:
+    void update(const OPL::DbTable table);
+
+};
+
+}// namespace OPL
+#endif // DATABASECACHE_H

+ 23 - 1
src/database/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

@@ -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

+ 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(':');

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

@@ -0,0 +1,24 @@
+#include "airportinput.h"
+#include "src/database/databasecache.h"
+bool AirportInput::isValid() const
+{
+    return OPL::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 = OPL::DBCache.getAirportsMapIATA().key(input.toUpper());
+        if (id != 0)
+            fixed = OPL::DBCache.getAirportsMapICAO().value(id);
+    } else {
+        //input could be lower case
+        int id = OPL::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

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

@@ -0,0 +1,102 @@
+#include "completerprovider.h"
+#include "src/database/databasecache.h"
+
+//namespace OPL {
+
+CompleterProvider::CompleterProvider()
+{
+    pilotCompleter = new QCompleter(OPL::DBCache.getPilotNamesList());
+    tailsCompleter = new QCompleter(OPL::DBCache.getTailsList());
+    airportCompleter = new QCompleter(OPL::DBCache.getAirportList());
+
+    QList<QCompleter*> completers = {
+        pilotCompleter,
+        tailsCompleter,
+        airportCompleter,
+    };
+    for (const auto completer : completers) {
+        completer->setCaseSensitivity(Qt::CaseInsensitive);
+        completer->setCompletionMode(QCompleter::PopupCompletion);
+        completer->setFilterMode(Qt::MatchContains);
+    }
+}
+
+CompleterProvider::~CompleterProvider()
+{
+    delete pilotCompleter;
+    delete tailsCompleter;
+    delete airportCompleter;
+}
+
+QCompleter *CompleterProvider::getCompleter(CompleterTarget target) const
+{
+    switch (target) {
+    case Airports:
+        return airportCompleter;
+        break;
+    case Pilots:
+        return pilotCompleter;
+        break;
+    case Tails:
+        return tailsCompleter;
+        break;
+    default:
+        break;
+    }
+}
+
+/*
+QCompleter *ValidatorProvider::newCompleter(CompleterTarget target, QObject *parent)
+{
+    QCompleter* completer = nullptr;
+    switch(target) {
+    case Airports:
+        completer = new QCompleter(OPL::DBCACHE.getAirportList(), parent);
+        break;
+    case Pilots:
+        completer = new QCompleter(OPL::DBCACHE.getPilotNamesList(), parent);
+        break;
+    case Tails: {
+        completer = new QCompleter(OPL::DBCACHE.getTailsList(), parent);
+        break;
+    }
+    default:
+        return nullptr;
+    }
+
+    completer->setCaseSensitivity(Qt::CaseInsensitive);
+    completer->setCompletionMode(QCompleter::PopupCompletion);
+    completer->setFilterMode(Qt::MatchContains);
+
+    return completer;
+}
+*/
+void CompleterProvider::updateModel(CompleterTarget target)
+{
+    const QStringList *newData = nullptr;
+    QStringListModel* model = nullptr;
+
+    switch(target) {
+    case Airports:
+        newData = &OPL::DBCache.getAirportList();
+        model = qobject_cast<QStringListModel*>(airportCompleter->model());
+        break;
+    case Pilots:
+        newData = &OPL::DBCache.getPilotNamesList();
+        model = qobject_cast<QStringListModel*>(pilotCompleter->model());
+        break;
+    case Tails: {
+        newData = &OPL::DBCache.getTailsList();
+        model = qobject_cast<QStringListModel*>(tailsCompleter->model());
+        break;
+    }
+    default:
+        break;
+    }
+
+    if(newData == nullptr) return;
+
+    model->setStringList(*newData);
+}
+
+//} // namespace OPL

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

@@ -0,0 +1,53 @@
+#ifndef VALIDATORFACTORY_H
+#define VALIDATORFACTORY_H
+#include <QCompleter>
+
+//#define OPL::CompleterProvider 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,
+ * it is important to call updateModel.
+ */
+class CompleterProvider
+{
+    CompleterProvider();
+
+    QCompleter* pilotCompleter;
+    QCompleter* airportCompleter;
+    QCompleter* tailsCompleter;
+public:
+    static CompleterProvider& getInstance() {
+        static CompleterProvider instance;
+        return instance;
+    }
+
+    CompleterProvider(CompleterProvider const&) = delete;
+    void operator=(CompleterProvider const&) = delete;
+    ~CompleterProvider();
+
+    enum CompleterTarget {Pilots, Tails, Airports};
+
+    /*!
+     * \brief Create a new QCompleter for the given CompleterTarget
+     */
+    //static QCompleter *newCompleter(CompleterTarget target, QObject* parent = nullptr);
+
+    /*!
+     * \brief updates the completion model for a given QCompleter
+     */
+    void updateModel(CompleterTarget target);
+
+    /*!
+     * \brief return a pointer to the static completer instance
+     */
+    QCompleter *getCompleter(CompleterTarget target) const;
+};
+
+#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 OPL::DBCache.getPilotNamesMap().key(input) != 0;
+}
+
+QString PilotInput::fixup() const
+{
+    if (input.contains(self, Qt::CaseInsensitive))
+        return OPL::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

+ 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

+ 16 - 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/database/dbcompletiondata.h"
+#include "src/gui/verification/airportinput.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"
@@ -273,3 +277,15 @@ 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());
+    }
+}
+

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

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

+ 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