Browse Source

Fixed SQL file

Database creation was broken by additional whitespace in sql file.
Felix 7 months ago
parent
commit
71584d22a3

+ 289 - 289
CMakeLists.txt

@@ -1,289 +1,289 @@
-cmake_minimum_required(VERSION 3.0)
-
-project(openPilotLog LANGUAGES CXX)
-
-set(CMAKE_INCLUDE_CURRENT_DIR ON)
-
-set(CMAKE_AUTOUIC ON)
-set(CMAKE_AUTOMOC ON)
-set(CMAKE_AUTORCC ON)
-
-set(CMAKE_CXX_STANDARD 20)
-set(CMAKE_CXX_STANDARD_REQUIRED ON)
-
-set(QT_MIN_VERSION "5.5.1")
-
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_USE_QSTRINGBUILDER")
-MESSAGE ("Enabling QStringBuilder")
-
-find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED)
-# find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Sql Network LinguistTools REQUIRED)
-# find_package(OpenSSL REQUIRED) # Pending testing
-find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Sql Network REQUIRED)
-
-set(PROJECT_SOURCES
-
-    main.cpp
-    src/opl.h
-    src/opl.cpp
-
-    ##  GUI
-    mainwindow.h
-    mainwindow.cpp
-    mainwindow.ui
-    # Dialogs
-    src/gui/dialogues/firstrundialog.h
-    src/gui/dialogues/firstrundialog.cpp
-    src/gui/dialogues/firstrundialog.ui
-    src/gui/dialogues/newairportdialog.h
-    src/gui/dialogues/newairportdialog.cpp
-    src/gui/dialogues/newairportdialog.ui
-    src/gui/dialogues/newflightdialog.h
-    src/gui/dialogues/newflightdialog.cpp
-    src/gui/dialogues/newflightdialog.ui
-    src/gui/dialogues/newpilotdialog.h
-    src/gui/dialogues/newpilotdialog.cpp
-    src/gui/dialogues/newpilot.ui
-    src/gui/dialogues/newtaildialog.h
-    src/gui/dialogues/newtaildialog.cpp
-    src/gui/dialogues/newtail.ui
-    src/gui/dialogues/newsimdialog.h
-    src/gui/dialogues/newsimdialog.cpp
-    src/gui/dialogues/newsimdialog.ui
-    src/gui/dialogues/exporttocsvdialog.h
-    src/gui/dialogues/exporttocsvdialog.cpp
-    src/gui/dialogues/exporttocsvdialog.ui
-
-    src/gui/dialogues/entryeditdialog.h
-    src/gui/dialogues/entryeditdialog.cpp
-    src/gui/dialogues/flightentryeditdialog.h
-    src/gui/dialogues/flightentryeditdialog.cpp
-
-    # Widgets
-    src/gui/widgets/homewidget.h
-    src/gui/widgets/homewidget.cpp
-    src/gui/widgets/homewidget.ui
-    src/gui/widgets/totalswidget.h
-    src/gui/widgets/totalswidget.cpp
-    src/gui/widgets/totalswidget.ui
-    src/gui/widgets/backupwidget.h
-    src/gui/widgets/backupwidget.cpp
-    src/gui/widgets/backupwidget.ui
-    src/gui/widgets/debugwidget.h
-    src/gui/widgets/debugwidget.cpp
-    src/gui/widgets/debugwidget.ui
-
-    src/gui/widgets/settingswidget.h
-    src/gui/widgets/settingswidget.cpp
-    src/gui/widgets/settingswidget.ui
-
-    src/gui/widgets/tableeditwidget.h
-    src/gui/widgets/tableeditwidget.cpp
-    src/gui/widgets/pilottableeditwidget.h
-    src/gui/widgets/pilottableeditwidget.cpp
-    src/gui/widgets/tailtableeditwidget.h
-    src/gui/widgets/tailtableeditwidget.cpp
-    src/gui/widgets/airporttableeditwidget.h
-    src/gui/widgets/airporttableeditwidget.cpp
-    src/gui/widgets/logbooktableeditwidget.h
-    src/gui/widgets/logbooktableeditwidget.cpp
-
-    # 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
-    src/gui/widgets/currencywidget.h
-    src/gui/widgets/currencywidget.cpp
-
-    # Classes
-    src/classes/style.h
-    src/classes/style.cpp
-    src/classes/paths.h
-    src/classes/paths.cpp
-    src/classes/downloadhelper.h
-    src/classes/downloadhelper.cpp
-    src/classes/runguard.h
-    src/classes/runguard.cpp
-    src/classes/settings.h
-    src/classes/settings.cpp
-    src/classes/translator.h
-    src/classes/translator.cpp
-    src/classes/jsonhelper.h
-    src/classes/jsonhelper.cpp
-    src/classes/md5sum.h
-    src/classes/md5sum.cpp
-    src/classes/time.h
-    src/classes/time.cpp
-    src/classes/easaftl.h
-    src/classes/easaftl.cpp
-    src/classes/date.h
-    src/classes/date.cpp
-    src/classes/styleddatedelegate.h
-    src/classes/styleddatedelegate.cpp
-    src/classes/styledtimedelegate.h
-    src/classes/styledtimedelegate.cpp
-    src/classes/styledpilotdelegate.h
-    src/classes/styledpilotdelegate.cpp
-    src/classes/styledregistrationdelegate.h
-    src/classes/styledregistrationdelegate.cpp
-    src/classes/styledtypedelegate.h
-    src/classes/styledtypedelegate.cpp
-
-    # Database Entries
-    src/database/flightentry.h
-    src/database/flightentry.cpp
-    src/database/aircraftentry.h
-    src/database/aircraftentry.cpp
-    src/database/tailentry.h
-    src/database/tailentry.cpp
-    src/database/airportentry.h
-    src/database/airportentry.cpp
-    src/database/pilotentry.h
-    src/database/pilotentry.cpp
-    src/database/simulatorentry.h
-    src/database/simulatorentry.cpp
-    src/database/currencyentry.h
-    src/database/currencyentry.cpp
-    src/database/previousexperienceentry.h
-    src/database/previousexperienceentry.cpp
-
-    # Namespaces
-    src/functions/calc.h
-    src/functions/calc.cpp
-    src/functions/log.h
-    src/functions/log.cpp
-    src/functions/readcsv.h
-    src/functions/statistics.h
-    src/functions/statistics.cpp
-    src/functions/datetime.h
-    src/functions/datetime.cpp
-
-
-    # Database
-    src/database/database.h
-    src/database/database.cpp
-    src/database/row.h
-    src/database/row.cpp
-    src/database/dbsummary.h
-    src/database/dbsummary.cpp
-    src/database/databasecache.h
-    src/database/databasecache.cpp
-
-    src/database/views/logbookviewinfo.h
-
-    # Ressources
-    assets/icons.qrc
-    assets/database/templates.qrc
-    assets/themes/stylesheets/breeze/breeze.qrc
-    assets/themes/stylesheets/qdarkstyle/qdarkstyle.qrc
-
-    # network
-    src/network/flightawareflightdata.h
-    src/network/flightawarequery.h
-    src/network/flightawarequery.cpp
-    src/network/flightawarejsonparser.h
-    src/network/flightawarejsonparser.cpp
-
-
-    # Testing / Debug
-    src/testing/importCrewlounge/importcrewlounge.h
-    src/testing/importCrewlounge/importcrewlounge.cpp
-    src/testing/importCrewlounge/processpilots.h
-    src/testing/importCrewlounge/processpilots.cpp
-    src/testing/importCrewlounge/processaircraft.h
-    src/testing/importCrewlounge/processaircraft.cpp
-    src/testing/importCrewlounge/processflights.h
-    src/testing/importCrewlounge/processflights.cpp
-    src/testing/atimer.h
-    src/testing/atimer.cpp
-    src/testing/randomgenerator.h
-    src/testing/randomgenerator.cpp
-)
-
-# This is currently a bit buggy, see
-# https://bugreports.qt.io/browse/QTBUG-41736
-# https://bugreports.qt.io/browse/QTBUG-76410
-# Before working on the translations themselves, check for updates and consider not updating for
-# every build due to danger of loss of translations...
-#if (Qt5Widgets_FOUND)
-#    if (Qt5Widgets_VERSION VERSION_LESS 5.15.0)
-#        message("Translations are available for Qt5 version >= 5.15")
-#    else()
-#        message("Qt > 5.15 detected. Enabling translations.")
-#        set(TS_FILES
-#            l10n/openpilotlog_en.ts
-#            l10n/openpilotlog_de.ts
-#            l10n/openpilotlog_es.ts
-#        )
-#        set_source_files_properties(${TS_FILES} PROPERTIES OUTPUT_LOCATION "l10n")
-#        qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
-#    endif(Qt5Widgets_VERSION VERSION_LESS 5.15.0)
-#endif(Qt5Widgets_FOUND)
-
-# Disable Debug output for Release Build
-if(CMAKE_BUILD_TYPE STREQUAL "Debug")
-    message("Build type: Debug")
-elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
-    message("Build type: Release")
-    add_definitions(-DQT_NO_DEBUG_OUTPUT)
-endif()
-
-# set the win32 entry point to avoid terminal popping up
-if(WIN32)
-    MESSAGE("Building for WIN32")
-    if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
-        qt_add_executable(openPilotLog WIN32
-            ${PROJECT_SOURCES}
-            ${QM_FILES}
-            ${app_icon_macos}
-        )
-    else()
-        add_executable(openPilotLog
-            ${PROJECT_SOURCES}
-            ${QM_FILES}
-            ${app_icon_macos}
-        )
-    endif()
-else()
-if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
-        qt_add_executable(openPilotLog
-            ${PROJECT_SOURCES}
-            ${QM_FILES}
-            ${app_icon_macos}
-     )
-    else()
-     add_executable(openPilotLog
-         ${PROJECT_SOURCES}
-         ${QM_FILES}
-         ${app_icon_macos}
-     )
-    endif()
-endif()
-
-
-set_target_properties(openPilotLog PROPERTIES
-    MACOSX_BUNDLE TRUE
-)
-# The MACOSX_BUNDLE_ICON_FILE variable is added to the Info.plist
-# generated by CMake. This variable contains the .icns file name,
-# without the path.
-set(MACOSX_BUNDLE_ICON_FILE icon_ios.icns)
-
-# And the following tells CMake where to find and install the file itself.
-set(app_icon_macos "${CMAKE_CURRENT_SOURCE_DIR}/assets/opl-icons/app/icon_ios.icns")
-set_source_files_properties(${app_icon_macos} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
-
-# target_link_libraries(openPilotLog PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Sql Qt${QT_VERSION_MAJOR}::Network OpenSSL::SSL)
-target_link_libraries(openPilotLog PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Sql Qt${QT_VERSION_MAJOR}::Network)
-
-install(TARGETS openPilotLog DESTINATION bin)
+cmake_minimum_required(VERSION 3.0)
+
+project(openPilotLog LANGUAGES CXX)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_AUTOUIC ON)
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(QT_MIN_VERSION "5.5.1")
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_USE_QSTRINGBUILDER")
+MESSAGE ("Enabling QStringBuilder")
+
+find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets Sql Network REQUIRED)
+# find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Sql Network LinguistTools REQUIRED)
+# find_package(OpenSSL REQUIRED) # Pending testing
+find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Sql Network REQUIRED)
+
+set(PROJECT_SOURCES
+
+    main.cpp
+    src/opl.h
+    src/opl.cpp
+
+    ##  GUI
+    mainwindow.h
+    mainwindow.cpp
+    mainwindow.ui
+    # Dialogs
+    src/gui/dialogues/firstrundialog.h
+    src/gui/dialogues/firstrundialog.cpp
+    src/gui/dialogues/firstrundialog.ui
+    src/gui/dialogues/newairportdialog.h
+    src/gui/dialogues/newairportdialog.cpp
+    src/gui/dialogues/newairportdialog.ui
+    src/gui/dialogues/newflightdialog.h
+    src/gui/dialogues/newflightdialog.cpp
+    src/gui/dialogues/newflightdialog.ui
+    src/gui/dialogues/newpilotdialog.h
+    src/gui/dialogues/newpilotdialog.cpp
+    src/gui/dialogues/newpilot.ui
+    src/gui/dialogues/newtaildialog.h
+    src/gui/dialogues/newtaildialog.cpp
+    src/gui/dialogues/newtail.ui
+    src/gui/dialogues/newsimdialog.h
+    src/gui/dialogues/newsimdialog.cpp
+    src/gui/dialogues/newsimdialog.ui
+    src/gui/dialogues/exporttocsvdialog.h
+    src/gui/dialogues/exporttocsvdialog.cpp
+    src/gui/dialogues/exporttocsvdialog.ui
+
+    src/gui/dialogues/entryeditdialog.h
+    src/gui/dialogues/entryeditdialog.cpp
+    src/gui/dialogues/flightentryeditdialog.h
+    src/gui/dialogues/flightentryeditdialog.cpp
+
+    # Widgets
+    src/gui/widgets/homewidget.h
+    src/gui/widgets/homewidget.cpp
+    src/gui/widgets/homewidget.ui
+    src/gui/widgets/totalswidget.h
+    src/gui/widgets/totalswidget.cpp
+    src/gui/widgets/totalswidget.ui
+    src/gui/widgets/backupwidget.h
+    src/gui/widgets/backupwidget.cpp
+    src/gui/widgets/backupwidget.ui
+    src/gui/widgets/debugwidget.h
+    src/gui/widgets/debugwidget.cpp
+    src/gui/widgets/debugwidget.ui
+
+    src/gui/widgets/settingswidget.h
+    src/gui/widgets/settingswidget.cpp
+    src/gui/widgets/settingswidget.ui
+
+    src/gui/widgets/tableeditwidget.h
+    src/gui/widgets/tableeditwidget.cpp
+    src/gui/widgets/pilottableeditwidget.h
+    src/gui/widgets/pilottableeditwidget.cpp
+    src/gui/widgets/tailtableeditwidget.h
+    src/gui/widgets/tailtableeditwidget.cpp
+    src/gui/widgets/airporttableeditwidget.h
+    src/gui/widgets/airporttableeditwidget.cpp
+    src/gui/widgets/logbooktableeditwidget.h
+    src/gui/widgets/logbooktableeditwidget.cpp
+
+    # 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
+    src/gui/widgets/currencywidget.h
+    src/gui/widgets/currencywidget.cpp
+
+    # Classes
+    src/classes/style.h
+    src/classes/style.cpp
+    src/classes/paths.h
+    src/classes/paths.cpp
+    src/classes/downloadhelper.h
+    src/classes/downloadhelper.cpp
+    src/classes/runguard.h
+    src/classes/runguard.cpp
+    src/classes/settings.h
+    src/classes/settings.cpp
+    src/classes/translator.h
+    src/classes/translator.cpp
+    src/classes/jsonhelper.h
+    src/classes/jsonhelper.cpp
+    src/classes/md5sum.h
+    src/classes/md5sum.cpp
+    src/classes/time.h
+    src/classes/time.cpp
+    src/classes/easaftl.h
+    src/classes/easaftl.cpp
+    src/classes/date.h
+    src/classes/date.cpp
+    src/classes/styleddatedelegate.h
+    src/classes/styleddatedelegate.cpp
+    src/classes/styledtimedelegate.h
+    src/classes/styledtimedelegate.cpp
+    src/classes/styledpilotdelegate.h
+    src/classes/styledpilotdelegate.cpp
+    src/classes/styledregistrationdelegate.h
+    src/classes/styledregistrationdelegate.cpp
+    src/classes/styledtypedelegate.h
+    src/classes/styledtypedelegate.cpp
+
+    # Database Entries
+    src/database/flightentry.h
+    src/database/flightentry.cpp
+    src/database/aircraftentry.h
+    src/database/aircraftentry.cpp
+    src/database/tailentry.h
+    src/database/tailentry.cpp
+    src/database/airportentry.h
+    src/database/airportentry.cpp
+    src/database/pilotentry.h
+    src/database/pilotentry.cpp
+    src/database/simulatorentry.h
+    src/database/simulatorentry.cpp
+    src/database/currencyentry.h
+    src/database/currencyentry.cpp
+    src/database/previousexperienceentry.h
+    src/database/previousexperienceentry.cpp
+
+    # Namespaces
+    src/functions/calc.h
+    src/functions/calc.cpp
+    src/functions/log.h
+    src/functions/log.cpp
+    src/functions/readcsv.h
+    src/functions/statistics.h
+    src/functions/statistics.cpp
+    src/functions/datetime.h
+    src/functions/datetime.cpp
+
+
+    # Database
+    src/database/database.h
+    src/database/database.cpp
+    src/database/row.h
+    src/database/row.cpp
+    src/database/dbsummary.h
+    src/database/dbsummary.cpp
+    src/database/databasecache.h
+    src/database/databasecache.cpp
+
+    src/database/views/logbookviewinfo.h
+
+    # Ressources
+    assets/icons.qrc
+    assets/database/templates.qrc
+    assets/themes/stylesheets/breeze/breeze.qrc
+    assets/themes/stylesheets/qdarkstyle/qdarkstyle.qrc
+
+    # network
+    src/network/flightawareflightdata.h
+    src/network/flightawarequery.h
+    src/network/flightawarequery.cpp
+    src/network/flightawarejsonparser.h
+    src/network/flightawarejsonparser.cpp
+
+
+    # Testing / Debug
+    src/testing/importCrewlounge/importcrewlounge.h
+    src/testing/importCrewlounge/importcrewlounge.cpp
+    src/testing/importCrewlounge/processpilots.h
+    src/testing/importCrewlounge/processpilots.cpp
+    src/testing/importCrewlounge/processaircraft.h
+    src/testing/importCrewlounge/processaircraft.cpp
+    src/testing/importCrewlounge/processflights.h
+    src/testing/importCrewlounge/processflights.cpp
+    src/testing/atimer.h
+    src/testing/atimer.cpp
+    src/testing/randomgenerator.h
+    src/testing/randomgenerator.cpp
+)
+
+# This is currently a bit buggy, see
+# https://bugreports.qt.io/browse/QTBUG-41736
+# https://bugreports.qt.io/browse/QTBUG-76410
+# Before working on the translations themselves, check for updates and consider not updating for
+# every build due to danger of loss of translations...
+#if (Qt5Widgets_FOUND)
+#    if (Qt5Widgets_VERSION VERSION_LESS 5.15.0)
+#        message("Translations are available for Qt5 version >= 5.15")
+#    else()
+#        message("Qt > 5.15 detected. Enabling translations.")
+#        set(TS_FILES
+#            l10n/openpilotlog_en.ts
+#            l10n/openpilotlog_de.ts
+#            l10n/openpilotlog_es.ts
+#        )
+#        set_source_files_properties(${TS_FILES} PROPERTIES OUTPUT_LOCATION "l10n")
+#        qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
+#    endif(Qt5Widgets_VERSION VERSION_LESS 5.15.0)
+#endif(Qt5Widgets_FOUND)
+
+# Disable Debug output for Release Build
+if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+    message("Build type: Debug")
+elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
+    message("Build type: Release")
+    add_definitions(-DQT_NO_DEBUG_OUTPUT)
+endif()
+
+# set the win32 entry point to avoid terminal popping up
+if(WIN32)
+    MESSAGE("Building for WIN32")
+    if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
+        qt_add_executable(openPilotLog WIN32
+            ${PROJECT_SOURCES}
+            ${QM_FILES}
+            ${app_icon_macos}
+        )
+    else()
+        add_executable(openPilotLog
+            ${PROJECT_SOURCES}
+            ${QM_FILES}
+            ${app_icon_macos}
+        )
+    endif()
+else()
+if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
+        qt_add_executable(openPilotLog
+            ${PROJECT_SOURCES}
+            ${QM_FILES}
+            ${app_icon_macos}
+     )
+    else()
+     add_executable(openPilotLog
+         ${PROJECT_SOURCES}
+         ${QM_FILES}
+         ${app_icon_macos}
+     )
+    endif()
+endif()
+
+
+set_target_properties(openPilotLog PROPERTIES
+    MACOSX_BUNDLE TRUE
+)
+# The MACOSX_BUNDLE_ICON_FILE variable is added to the Info.plist
+# generated by CMake. This variable contains the .icns file name,
+# without the path.
+set(MACOSX_BUNDLE_ICON_FILE icon_ios.icns)
+
+# And the following tells CMake where to find and install the file itself.
+set(app_icon_macos "${CMAKE_CURRENT_SOURCE_DIR}/assets/opl-icons/app/icon_ios.icns")
+set_source_files_properties(${app_icon_macos} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
+
+# target_link_libraries(openPilotLog PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Sql Qt${QT_VERSION_MAJOR}::Network OpenSSL::SSL)
+target_link_libraries(openPilotLog PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Sql Qt${QT_VERSION_MAJOR}::Network)
+
+install(TARGETS openPilotLog DESTINATION bin)

+ 342 - 357
assets/database/database_schema.sql

@@ -1,357 +1,342 @@
-DROP TABLE IF EXISTS 'pilots';
-CREATE TABLE IF NOT EXISTS 'pilots' (
-	'pilot_id'	INTEGER NOT NULL,
-	'lastname'	TEXT NOT NULL,
-	'firstname'	TEXT,
-        'alias'		TEXT,
-	'company'	TEXT,
-	'employeeid'	TEXT,
-        'phone'		TEXT,
-        'email'		TEXT,
-	PRIMARY KEY('pilot_id' AUTOINCREMENT)
-);
-DROP TABLE IF EXISTS 'tails';
-CREATE TABLE IF NOT EXISTS 'tails' (
-	'tail_id'	INTEGER NOT NULL,
-	'registration'	TEXT NOT NULL,
-	'company'	TEXT,
-        'make'		TEXT,
-        'model'		TEXT,
-	'variant'	TEXT,
-	'multipilot'	INTEGER,
-	'multiengine'	INTEGER,
-	'engineType'	INTEGER,
-	'weightClass'	INTEGER,
-        'typeString'	TEXT,
-	PRIMARY KEY('tail_id' AUTOINCREMENT)
-);
-DROP TABLE IF EXISTS 'flights';
-CREATE TABLE IF NOT EXISTS 'flights' (
-	'flight_id'	INTEGER NOT NULL,
-        'doft'		NUMERIC NOT NULL,
-        'dept'		TEXT NOT NULL,
-        'dest'		TEXT NOT NULL,
-        'tofb'		INTEGER NOT NULL,
-        'tonb'		INTEGER NOT NULL,
-        'pic'		INTEGER NOT NULL,
-        'acft'		INTEGER NOT NULL,
-        'tblk'		INTEGER NOT NULL,
-        'tSPSE'		INTEGER,
-        'tSPME'		INTEGER,
-        'tMP'		INTEGER,
-	'tNIGHT'	INTEGER,
-        'tIFR'		INTEGER,
-        'tPIC'		INTEGER,
-	'tPICUS'	INTEGER,
-        'tSIC'		INTEGER,
-        'tDUAL'		INTEGER,
-        'tFI'		INTEGER,
-        'tSIM'		INTEGER,
-	'pilotFlying'	INTEGER,
-        'toDay'		INTEGER,
-	'toNight'	INTEGER,
-	'ldgDay'	INTEGER,
-	'ldgNight'	INTEGER,
-	'autoland'	INTEGER,
-	'secondPilot'	INTEGER,
-	'thirdPilot'	INTEGER,
-	'approachType'	TEXT,
-	'flightNumber'	TEXT,
-	'remarks'	TEXT,
-	FOREIGN KEY('pic') REFERENCES 'pilots'('pilot_id') ON DELETE RESTRICT,
-	FOREIGN KEY('acft') REFERENCES 'tails'('tail_id') ON DELETE RESTRICT,
-	PRIMARY KEY('flight_id' AUTOINCREMENT)
-);
-DROP TABLE IF EXISTS 'aircraft';
-CREATE TABLE IF NOT EXISTS 'aircraft' (
-	'aircraft_id'	INTEGER NOT NULL,
-        'make'		TEXT,
-        'model'		TEXT,
-	'variant'	TEXT,
-        'name'		TEXT,
-        'iata'		TEXT,
-        'icao'		TEXT,
-	'multipilot'	INTEGER,
-	'multiengine'	INTEGER,
-	'engineType'	INTEGER,
-	'weightClass'	INTEGER,
-	PRIMARY KEY('aircraft_id' AUTOINCREMENT)
-);
-DROP TABLE IF EXISTS 'airports';
-CREATE TABLE IF NOT EXISTS 'airports' (
-	'airport_id'	INTEGER NOT NULL,
-        'icao'		TEXT NOT NULL,
-        'iata'		TEXT,
-        'name'		TEXT,
-        'lat'		REAL,
-        'long'		REAL,
-	'country'	TEXT,
-	'tzolson'	TEXT,
-	PRIMARY KEY('airport_id' AUTOINCREMENT)
-);
-DROP TABLE IF EXISTS 'currencies';
-CREATE TABLE IF NOT EXISTS "currencies" (
-        "currency_id"	INTEGER NOT NULL,
-        "currencyName"	TEXT,
-        "expiryDate"	NUMERIC,
-        PRIMARY KEY('currency_id' AUTOINCREMENT)
-);
-DROP TABLE IF EXISTS 'changelog';
-CREATE TABLE IF NOT EXISTS 'changelog' (
-	'revision'	INTEGER NOT NULL,
-	'comment'	TEXT,
-        'date'		NUMERIC,
-	PRIMARY KEY('revision' AUTOINCREMENT)
-);
-DROP TABLE IF EXISTS 'simulators';
-CREATE TABLE IF NOT EXISTS 'simulators' (
-	'session_id'	INTEGER NOT NULL,
-        'date'		NUMERIC NOT NULL,
-	'totalTime'	INTEGER NOT NULL,
-	'deviceType'	TEXT NOT NULL,
-	'aircraftType'	TEXT,
-	'registration'	TEXT,
-	'remarks'	TEXT,
-	PRIMARY KEY('session_id' AUTOINCREMENT)
-);
-DROP TABLE IF EXISTS 'previousExperience';
-CREATE TABLE 'previousExperience' (
-        'tblk'		INTEGER,
-        'tSPSE'		INTEGER,
-        'tSPME'		INTEGER,
-        'tMP'		INTEGER,
-        'tNIGHT'	INTEGER,
-        'tIFR'		INTEGER,
-        'tPIC'		INTEGER,
-        'tPICUS'	INTEGER,
-        'tSIC'		INTEGER,
-        'tDUAL'		INTEGER,
-        'tFI'		INTEGER,
-        'tSIM'		INTEGER,
-        'toDay'		INTEGER,
-        'toNight'	INTEGER,
-        'ldgDay'	INTEGER,
-        'ldgNight'	INTEGER,
-        'autoland'	INTEGER
-);
-DROP VIEW IF EXISTS 'viewDefault';
-CREATE VIEW viewDefault AS
-SELECT 	flight_id,
-        doft,
-        dept,
-        tofb,
-        dest,
-        tonb,
-        tblk,
-        pilots.pilot_id,
-        tails.tail_id,
-        tails.registration,
-        flightNumber,
-        remarks
-FROM flights
-INNER JOIN pilots on flights.pic = pilots.pilot_id
-INNER JOIN tails on flights.acft = tails.tail_id
-ORDER BY doft DESC
-
-DROP VIEW IF EXISTS 'viewDefaultSim';
-CREATE VIEW viewDefaultSim AS
-SELECT 	flights.flight_id,
-        flights.doft,
-        flights.dept,
-        flights.tofb,
-        flights.dest,
-        flights.tonb,
-        flights.tblk,
-        pilots.pilot_id ,
-        tails.tail_id,
-        tails.registration,
-        null AS 'deviceType',
-        null AS 'SimTime',
-        flights.remarks
-FROM flights
-INNER JOIN pilots on flights.pic = pilots.pilot_id
-INNER JOIN tails on flights.acft = tails.tail_id
-UNION
-        SELECT (simulators.session_id * -1),
-        simulators.date,
-        null, null, null, null, null, null,
-        simulators.aircraftType,
-        simulators.registration,
-        simulators.deviceType,
-        simulators.totalTime,
-        remarks
-FROM simulators
-ORDER BY date DESC
-
-DROP VIEW IF EXISTS 'viewEasa';
-CREATE VIEW viewEasa AS
-SELECT
-        flight_id,
-        doft,
-        dept,
-        tofb,
-        dest,
-        tonb,
-        tail_id,
-        registration,
-        tSPSE,
-        tSPME,
-        tMP,
-        tblk,
-        pilot_id,
-        ldgDay,
-        ldgNight,
-        tNight,
-        tIFR,
-        tPIC,
-        tSIC,
-        tDUAL,
-        tFI,
-        remarks
-FROM flights
-INNER JOIN pilots on flights.pic = pilots.pilot_id
-INNER JOIN tails on flights.acft = tails.tail_id  ORDER BY doft DESC
-
-DROP VIEW IF EXISTS 'viewEasaSim';
-CREATE VIEW viewEasaSim AS
-SELECT  flight_id,
-        flights.doft as 'Date',
-        flights.dept,
-        flights.tofb,
-        flights.dest,
-        flights.tonb,
-        tails.tail_id AS 'Type',
-        tails.registration AS 'Registration',
-        flights.tSPSE,
-        flights.tSPME,
-        flights.tMP,
-        flights.tblk,
-        pilots.pilot_id AS 'PIC',
-        flights.ldgDay,
-        flights.ldgNight,
-        flights.tNight,
-        flights.tIFR,
-        flights.tPIC,
-        flights.tSIC,
-        flights.tDual,
-        flights.tFI,
-        null AS 'deviceType',
-        null AS 'simTime',
-        flights.remarks
-FROM flights
-INNER JOIN pilots on flights.pic = pilots.pilot_id
-INNER JOIN tails on flights.acft = tails.tail_id
-UNION
-SELECT (session_id * -1),
-        simulators.date,
-        null, null, null, null,
-        simulators.aircraftType,
-        simulators.registration,
-        null, null, null, null,
-        null, null, null, null,
-        null, null, null, null,
-        null,
-        simulators.deviceType,
-        simulators.totalTime,
-        simulators.remarks
-FROM simulators
-ORDER BY date DESC
-
-DROP VIEW IF EXISTS 'viewSimulators';
-CREATE VIEW viewSimulators AS
-SELECT (session_id * -1),
-        date,
-        registration,
-        aircraftType,
-        deviceType,
-        totalTime,
-        remarks
-FROM simulators
-ORDER BY date DESC
-
-DROP VIEW IF EXISTS 'viewTails';
-CREATE VIEW viewTails AS  
-SELECT  tail_id AS 'ID',  
-        registration AS 'Registration',
-        make||' '||model AS 'Type',
-        company AS 'Company'
-FROM tails WHERE model IS NOT NULL AND variant IS NULL  
-UNION  
-SELECT  tail_id AS 'ID',  
-        registration AS 'Registration',
-        make||' '||model||'-'||variant AS 'Type',
-        company AS 'Company'
-FROM tails WHERE variant IS NOT NULL;
-
-DROP VIEW IF EXISTS 'viewPilots';
-CREATE VIEW viewPilots AS  
-SELECT  pilot_id AS 'ID',  
-        lastname AS 'Last Name',
-        firstname AS 'First Name',
-        company AS 'Company'
-FROM pilots;
-
-DROP VIEW IF EXISTS 'viewTotals';
-CREATE VIEW viewTotals AS  
-SELECT  printf('%02d',CAST(SUM(tblk) AS INT)/60)||':'||printf('%02d',CAST(SUM(tblk) AS INT)%60) AS 'TOTAL',  
-        printf('%02d',CAST(SUM(tSPSE) AS INT)/60)||':'||printf('%02d',CAST(SUM(tSPSE) AS INT)%60) AS 'SP SE',
-        printf('%02d',CAST(SUM(tSPME) AS INT)/60)||':'||printf('%02d',CAST(SUM(tSPME) AS INT)%60) AS 'SP ME',
-        printf('%02d',CAST(SUM(tNIGHT) AS INT)/60)||':'||printf('%02d',CAST(SUM(tNIGHT) AS INT)%60) AS 'NIGHT',
-        printf('%02d',CAST(SUM(tIFR) AS INT)/60)||':'||printf('%02d',CAST(SUM(tIFR) AS INT)%60) AS 'IFR',
-        printf('%02d',CAST(SUM(tPIC) AS INT)/60)||':'||printf('%02d',CAST(SUM(tPIC) AS INT)%60) AS 'PIC',
-        printf('%02d',CAST(SUM(tPICUS) AS INT)/60)||':'||printf('%02d',CAST(SUM(tPICUS) AS INT)%60) AS 'PICUS',
-        printf('%02d',CAST(SUM(tSIC) AS INT)/60)||':'||printf('%02d',CAST(SUM(tSIC) AS INT)%60) AS 'SIC',
-        printf('%02d',CAST(SUM(tDual) AS INT)/60)||':'||printf('%02d',CAST(SUM(tDual) AS INT)%60) AS 'DUAL',
-        printf('%02d',CAST(SUM(tFI) AS INT)/60)||':'||printf('%02d',CAST(SUM(tFI) AS INT)%60) AS 'INSTRUCTOR',
-        printf('%02d',CAST(SUM(tSIM) AS INT)/60)||':'||printf('%02d',CAST(SUM(tSIM) AS INT)%60) AS 'SIMULATOR',
-        printf('%02d',CAST(SUM(tMP) AS INT)/60)||':'||printf('%02d',CAST(SUM(tMP) AS INT)%60) AS 'MultPilot',
-        CAST(SUM(toDay) AS INT) AS 'TO Day',
-        CAST(SUM(toNight) AS INT) AS 'TO Night',
-        CAST(SUM(ldgDay) AS INT) AS 'LDG Day',
-        CAST(SUM(ldgNight) AS INT) AS 'LDG Night'
-FROM flights;
-
-DROP VIEW IF EXISTS 'viewExport';
-CREATE VIEW viewExport AS
-SELECT  flight_id,
-        doft as 'Date',
-        dept AS 'Dept',
-        printf('%02d',(tofb/60))||':'||printf('%02d',(tofb%60)) AS 'Time Out',
-        dest AS 'Dest',
-        printf('%02d',(tonb/60))||':'||printf('%02d',(tonb%60)) AS 'Time In ',
-        CASE  WHEN variant IS NOT NULL THEN make||' '||model||'-'||variant  ELSE make||' '||model  END  AS 'Type',
-        registration AS 'Registration',
-        (SELECT printf('%02d',(tSPSE/60))||':'||printf('%02d',(tSPSE%60)) WHERE tSPSE IS NOT NULL) AS 'SP SE',
-        (SELECT printf('%02d',(tSPME/60))||':'||printf('%02d',(tSPME%60)) WHERE tSPME IS NOT NULL) AS 'SP ME',
-        (SELECT printf('%02d',(tMP/60))||':'||printf('%02d',(tMP%60)) WHERE tMP IS NOT NULL) AS 'MP',
-        printf('%02d',(tblk/60))||':'||printf('%02d',(tblk%60)) AS 'Total',
-        CASE  WHEN pilot_id = 1 THEN alias  ELSE lastname||', '||substr(firstname, 1, 1)||'.'  END  AS 'Name PIC',
-        toDay AS 'Take-Off Day',
-        ldgDay AS 'Landings Day',
-        toNight AS 'Take-Off Night',
-        ldgNight AS 'Landings Night',
-        (SELECT printf('%02d',(tNight/60))||':'||printf('%02d',(tNight%60)) WHERE tNight IS NOT NULL)  AS 'Night',
-        (SELECT printf('%02d',(tIFR/60))||':'||printf('%02d',(tIFR%60)) WHERE tIFR IS NOT NULL)  AS 'IFR',
-        (SELECT printf('%02d',(tPIC/60))||':'||printf('%02d',(tPIC%60)) WHERE tPIC IS NOT NULL)  AS 'PIC',
-        (SELECT printf('%02d',(tSIC/60))||':'||printf('%02d',(tSIC%60)) WHERE tSIC IS NOT NULL)  AS 'SIC',
-        (SELECT printf('%02d',(tDual/60))||':'||printf('%02d',(tDual%60)) WHERE tDual IS NOT NULL)  AS 'Dual',
-        (SELECT printf('%02d',(tFI/60))||':'||printf('%02d',(tFI%60)) WHERE tFI IS NOT NULL)  AS 'FI',
-        null AS 'Sim Type',
-        null AS 'Time of Session',
-        remarks AS 'Remarks'
-FROM flights
-INNER JOIN pilots on flights.pic = pilots.pilot_id
-INNER JOIN tails on flights.acft = tails.tail_id
-UNION
-SELECT (session_id * -1),
-        date,
-        null,  null,  null,  null,
-        aircraftType,
-        registration,
-        null,  null,  null,
-        'SIM',
-        null,  null,  null,  null,  null,  null,  null,  null,  null,  null, null,
-        deviceType,  printf('%02d',(totalTime/60))||':'||printf('%02d',(totalTime%60)),
-        remarks
-FROM simulators
-ORDER BY date DESC;
+DROP TABLE IF EXISTS 'pilots';
+CREATE TABLE IF NOT EXISTS 'pilots' (
+	'pilot_id'	INTEGER NOT NULL,
+	'lastname'	TEXT NOT NULL,
+	'firstname'	TEXT,
+        'alias'		TEXT,
+	'company'	TEXT,
+	'employeeid'	TEXT,
+        'phone'		TEXT,
+        'email'		TEXT,
+	PRIMARY KEY('pilot_id' AUTOINCREMENT)
+);
+DROP TABLE IF EXISTS 'tails';
+CREATE TABLE IF NOT EXISTS 'tails' (
+	'tail_id'	INTEGER NOT NULL,
+	'registration'	TEXT NOT NULL,
+	'company'	TEXT,
+        'make'		TEXT,
+        'model'		TEXT,
+	'variant'	TEXT,
+	'multipilot'	INTEGER,
+	'multiengine'	INTEGER,
+	'engineType'	INTEGER,
+	'weightClass'	INTEGER,
+        'typeString'	TEXT,
+	PRIMARY KEY('tail_id' AUTOINCREMENT)
+);
+DROP TABLE IF EXISTS 'flights';
+CREATE TABLE IF NOT EXISTS 'flights' (
+	'flight_id'	INTEGER NOT NULL,
+        'doft'		NUMERIC NOT NULL,
+        'dept'		TEXT NOT NULL,
+        'dest'		TEXT NOT NULL,
+        'tofb'		INTEGER NOT NULL,
+        'tonb'		INTEGER NOT NULL,
+        'pic'		INTEGER NOT NULL,
+        'acft'		INTEGER NOT NULL,
+        'tblk'		INTEGER NOT NULL,
+        'tSPSE'		INTEGER,
+        'tSPME'		INTEGER,
+        'tMP'		INTEGER,
+	'tNIGHT'	INTEGER,
+        'tIFR'		INTEGER,
+        'tPIC'		INTEGER,
+	'tPICUS'	INTEGER,
+        'tSIC'		INTEGER,
+        'tDUAL'		INTEGER,
+        'tFI'		INTEGER,
+        'tSIM'		INTEGER,
+	'pilotFlying'	INTEGER,
+        'toDay'		INTEGER,
+	'toNight'	INTEGER,
+	'ldgDay'	INTEGER,
+	'ldgNight'	INTEGER,
+	'autoland'	INTEGER,
+	'secondPilot'	INTEGER,
+	'thirdPilot'	INTEGER,
+	'approachType'	TEXT,
+	'flightNumber'	TEXT,
+	'remarks'	TEXT,
+	FOREIGN KEY('pic') REFERENCES 'pilots'('pilot_id') ON DELETE RESTRICT,
+	FOREIGN KEY('acft') REFERENCES 'tails'('tail_id') ON DELETE RESTRICT,
+	PRIMARY KEY('flight_id' AUTOINCREMENT)
+);
+DROP TABLE IF EXISTS 'aircraft';
+CREATE TABLE IF NOT EXISTS 'aircraft' (
+	'aircraft_id'	INTEGER NOT NULL,
+        'make'		TEXT,
+        'model'		TEXT,
+	'variant'	TEXT,
+        'name'		TEXT,
+        'iata'		TEXT,
+        'icao'		TEXT,
+	'multipilot'	INTEGER,
+	'multiengine'	INTEGER,
+	'engineType'	INTEGER,
+	'weightClass'	INTEGER,
+	PRIMARY KEY('aircraft_id' AUTOINCREMENT)
+);
+DROP TABLE IF EXISTS 'airports';
+CREATE TABLE IF NOT EXISTS 'airports' (
+	'airport_id'	INTEGER NOT NULL,
+        'icao'		TEXT NOT NULL,
+        'iata'		TEXT,
+        'name'		TEXT,
+        'lat'		REAL,
+        'long'		REAL,
+	'country'	TEXT,
+	'tzolson'	TEXT,
+	PRIMARY KEY('airport_id' AUTOINCREMENT)
+);
+DROP TABLE IF EXISTS 'currencies';
+CREATE TABLE IF NOT EXISTS "currencies" (
+        "currency_id"	INTEGER NOT NULL,
+        "currencyName"	TEXT,
+        "expiryDate"	NUMERIC,
+        PRIMARY KEY('currency_id' AUTOINCREMENT)
+);
+DROP TABLE IF EXISTS 'simulators';
+CREATE TABLE IF NOT EXISTS 'simulators' (
+	'session_id'	INTEGER NOT NULL,
+        'date'		NUMERIC NOT NULL,
+	'totalTime'	INTEGER NOT NULL,
+	'deviceType'	TEXT NOT NULL,
+	'aircraftType'	TEXT,
+	'registration'	TEXT,
+	'remarks'	TEXT,
+	PRIMARY KEY('session_id' AUTOINCREMENT)
+);
+DROP TABLE IF EXISTS 'previousExperience';
+CREATE TABLE 'previousExperience' (
+        'tblk'		INTEGER,
+        'tSPSE'		INTEGER,
+        'tSPME'		INTEGER,
+        'tMP'		INTEGER,
+        'tNIGHT'	INTEGER,
+        'tIFR'		INTEGER,
+        'tPIC'		INTEGER,
+        'tPICUS'	INTEGER,
+        'tSIC'		INTEGER,
+        'tDUAL'		INTEGER,
+        'tFI'		INTEGER,
+        'tSIM'		INTEGER,
+        'toDay'		INTEGER,
+        'toNight'	INTEGER,
+        'ldgDay'	INTEGER,
+        'ldgNight'	INTEGER,
+        'autoland'	INTEGER
+);
+DROP VIEW IF EXISTS 'viewDefault';
+CREATE VIEW viewDefault AS
+SELECT 	flight_id,
+        doft,
+        dept,
+        tofb,
+        dest,
+        tonb,
+        tblk,
+        pilots.pilot_id,
+        tails.tail_id,
+        tails.registration,
+        flightNumber,
+        remarks
+FROM flights
+INNER JOIN pilots on flights.pic = pilots.pilot_id
+INNER JOIN tails on flights.acft = tails.tail_id
+ORDER BY doft DESC;
+DROP VIEW IF EXISTS 'viewDefaultSim';
+CREATE VIEW viewDefaultSim AS
+SELECT 	flights.flight_id,
+        flights.doft,
+        flights.dept,
+        flights.tofb,
+        flights.dest,
+        flights.tonb,
+        flights.tblk,
+        pilots.pilot_id ,
+        tails.tail_id,
+        tails.registration,
+        null AS 'deviceType',
+        null AS 'SimTime',
+        flights.remarks
+FROM flights
+INNER JOIN pilots on flights.pic = pilots.pilot_id
+INNER JOIN tails on flights.acft = tails.tail_id
+UNION
+        SELECT (simulators.session_id * -1),
+        simulators.date,
+        null, null, null, null, null, null,
+        simulators.aircraftType,
+        simulators.registration,
+        simulators.deviceType,
+        simulators.totalTime,
+        remarks
+FROM simulators
+ORDER BY date DESC;
+DROP VIEW IF EXISTS 'viewEasa';
+CREATE VIEW viewEasa AS
+SELECT
+        flight_id,
+        doft,
+        dept,
+        tofb,
+        dest,
+        tonb,
+        tail_id,
+        registration,
+        tSPSE,
+        tSPME,
+        tMP,
+        tblk,
+        pilot_id,
+        ldgDay,
+        ldgNight,
+        tNight,
+        tIFR,
+        tPIC,
+        tSIC,
+        tDUAL,
+        tFI,
+        remarks
+FROM flights
+INNER JOIN pilots on flights.pic = pilots.pilot_id
+INNER JOIN tails on flights.acft = tails.tail_id  ORDER BY doft DESC;
+DROP VIEW IF EXISTS 'viewEasaSim';
+CREATE VIEW viewEasaSim AS
+SELECT  flight_id,
+        flights.doft as 'Date',
+        flights.dept,
+        flights.tofb,
+        flights.dest,
+        flights.tonb,
+        tails.tail_id AS 'Type',
+        tails.registration AS 'Registration',
+        flights.tSPSE,
+        flights.tSPME,
+        flights.tMP,
+        flights.tblk,
+        pilots.pilot_id AS 'PIC',
+        flights.ldgDay,
+        flights.ldgNight,
+        flights.tNight,
+        flights.tIFR,
+        flights.tPIC,
+        flights.tSIC,
+        flights.tDual,
+        flights.tFI,
+        null AS 'deviceType',
+        null AS 'simTime',
+        flights.remarks
+FROM flights
+INNER JOIN pilots on flights.pic = pilots.pilot_id
+INNER JOIN tails on flights.acft = tails.tail_id
+UNION
+SELECT (session_id * -1),
+        simulators.date,
+        null, null, null, null,
+        simulators.aircraftType,
+        simulators.registration,
+        null, null, null, null,
+        null, null, null, null,
+        null, null, null, null,
+        null,
+        simulators.deviceType,
+        simulators.totalTime,
+        simulators.remarks
+FROM simulators
+ORDER BY date DESC;
+DROP VIEW IF EXISTS 'viewSimulators';
+CREATE VIEW viewSimulators AS
+SELECT (session_id * -1),
+        date,
+        registration,
+        aircraftType,
+        deviceType,
+        totalTime,
+        remarks
+FROM simulators
+ORDER BY date DESC;
+DROP VIEW IF EXISTS 'viewTails';
+CREATE VIEW viewTails AS  
+SELECT  tail_id AS 'ID',  
+        registration AS 'Registration',
+        make||' '||model AS 'Type',
+        company AS 'Company'
+FROM tails WHERE model IS NOT NULL AND variant IS NULL  
+UNION  
+SELECT  tail_id AS 'ID',  
+        registration AS 'Registration',
+        make||' '||model||'-'||variant AS 'Type',
+        company AS 'Company'
+FROM tails WHERE variant IS NOT NULL;
+DROP VIEW IF EXISTS 'viewPilots';
+CREATE VIEW viewPilots AS  
+SELECT  pilot_id AS 'ID',  
+        lastname AS 'Last Name',
+        firstname AS 'First Name',
+        company AS 'Company'
+FROM pilots;
+DROP VIEW IF EXISTS 'viewTotals';
+CREATE VIEW viewTotals AS  
+SELECT  printf('%02d',CAST(SUM(tblk) AS INT)/60)||':'||printf('%02d',CAST(SUM(tblk) AS INT)%60) AS 'TOTAL',  
+        printf('%02d',CAST(SUM(tSPSE) AS INT)/60)||':'||printf('%02d',CAST(SUM(tSPSE) AS INT)%60) AS 'SP SE',
+        printf('%02d',CAST(SUM(tSPME) AS INT)/60)||':'||printf('%02d',CAST(SUM(tSPME) AS INT)%60) AS 'SP ME',
+        printf('%02d',CAST(SUM(tNIGHT) AS INT)/60)||':'||printf('%02d',CAST(SUM(tNIGHT) AS INT)%60) AS 'NIGHT',
+        printf('%02d',CAST(SUM(tIFR) AS INT)/60)||':'||printf('%02d',CAST(SUM(tIFR) AS INT)%60) AS 'IFR',
+        printf('%02d',CAST(SUM(tPIC) AS INT)/60)||':'||printf('%02d',CAST(SUM(tPIC) AS INT)%60) AS 'PIC',
+        printf('%02d',CAST(SUM(tPICUS) AS INT)/60)||':'||printf('%02d',CAST(SUM(tPICUS) AS INT)%60) AS 'PICUS',
+        printf('%02d',CAST(SUM(tSIC) AS INT)/60)||':'||printf('%02d',CAST(SUM(tSIC) AS INT)%60) AS 'SIC',
+        printf('%02d',CAST(SUM(tDual) AS INT)/60)||':'||printf('%02d',CAST(SUM(tDual) AS INT)%60) AS 'DUAL',
+        printf('%02d',CAST(SUM(tFI) AS INT)/60)||':'||printf('%02d',CAST(SUM(tFI) AS INT)%60) AS 'INSTRUCTOR',
+        printf('%02d',CAST(SUM(tSIM) AS INT)/60)||':'||printf('%02d',CAST(SUM(tSIM) AS INT)%60) AS 'SIMULATOR',
+        printf('%02d',CAST(SUM(tMP) AS INT)/60)||':'||printf('%02d',CAST(SUM(tMP) AS INT)%60) AS 'MultPilot',
+        CAST(SUM(toDay) AS INT) AS 'TO Day',
+        CAST(SUM(toNight) AS INT) AS 'TO Night',
+        CAST(SUM(ldgDay) AS INT) AS 'LDG Day',
+        CAST(SUM(ldgNight) AS INT) AS 'LDG Night'
+FROM flights;
+DROP VIEW IF EXISTS 'viewExport';
+CREATE VIEW viewExport AS
+SELECT  flight_id,
+        doft as 'Date',
+        dept AS 'Dept',
+        printf('%02d',(tofb/60))||':'||printf('%02d',(tofb%60)) AS 'Time Out',
+        dest AS 'Dest',
+        printf('%02d',(tonb/60))||':'||printf('%02d',(tonb%60)) AS 'Time In ',
+        CASE  WHEN variant IS NOT NULL THEN make||' '||model||'-'||variant  ELSE make||' '||model  END  AS 'Type',
+        registration AS 'Registration',
+        (SELECT printf('%02d',(tSPSE/60))||':'||printf('%02d',(tSPSE%60)) WHERE tSPSE IS NOT NULL) AS 'SP SE',
+        (SELECT printf('%02d',(tSPME/60))||':'||printf('%02d',(tSPME%60)) WHERE tSPME IS NOT NULL) AS 'SP ME',
+        (SELECT printf('%02d',(tMP/60))||':'||printf('%02d',(tMP%60)) WHERE tMP IS NOT NULL) AS 'MP',
+        printf('%02d',(tblk/60))||':'||printf('%02d',(tblk%60)) AS 'Total',
+        CASE  WHEN pilot_id = 1 THEN alias  ELSE lastname||', '||substr(firstname, 1, 1)||'.'  END  AS 'Name PIC',
+        toDay AS 'Take-Off Day',
+        ldgDay AS 'Landings Day',
+        toNight AS 'Take-Off Night',
+        ldgNight AS 'Landings Night',
+        (SELECT printf('%02d',(tNight/60))||':'||printf('%02d',(tNight%60)) WHERE tNight IS NOT NULL)  AS 'Night',
+        (SELECT printf('%02d',(tIFR/60))||':'||printf('%02d',(tIFR%60)) WHERE tIFR IS NOT NULL)  AS 'IFR',
+        (SELECT printf('%02d',(tPIC/60))||':'||printf('%02d',(tPIC%60)) WHERE tPIC IS NOT NULL)  AS 'PIC',
+        (SELECT printf('%02d',(tSIC/60))||':'||printf('%02d',(tSIC%60)) WHERE tSIC IS NOT NULL)  AS 'SIC',
+        (SELECT printf('%02d',(tDual/60))||':'||printf('%02d',(tDual%60)) WHERE tDual IS NOT NULL)  AS 'Dual',
+        (SELECT printf('%02d',(tFI/60))||':'||printf('%02d',(tFI%60)) WHERE tFI IS NOT NULL)  AS 'FI',
+        null AS 'Sim Type',
+        null AS 'Time of Session',
+        remarks AS 'Remarks'
+FROM flights
+INNER JOIN pilots on flights.pic = pilots.pilot_id
+INNER JOIN tails on flights.acft = tails.tail_id
+UNION
+SELECT (session_id * -1),
+        date,
+        null,  null,  null,  null,
+        aircraftType,
+        registration,
+        null,  null,  null,
+        'SIM',
+        null,  null,  null,  null,  null,  null,  null,  null,  null,  null, null,
+        deviceType,  printf('%02d',(totalTime/60))||':'||printf('%02d',(totalTime%60)),
+        remarks
+FROM simulators
+ORDER BY date DESC;

+ 7 - 8
assets/database/templates.qrc

@@ -1,8 +1,7 @@
-<RCC>
-    <qresource prefix="/database">
-        <file>templates/aircraft.json</file>
-        <file>templates/airports.json</file>
-        <file>templates/changelog.json</file>
-        <file>database_schema.sql</file>
-    </qresource>
-</RCC>
+<RCC>
+    <qresource prefix="/database">
+        <file>templates/aircraft.json</file>
+        <file>templates/airports.json</file>
+        <file>database_schema.sql</file>
+    </qresource>
+</RCC>

+ 0 - 92
assets/database/templates/changelog.json

@@ -1,92 +0,0 @@
-[
-    {
-        "comment": "Added ON DELETE RESTRICT constraint to flights table",
-        "date": null,
-        "revision": 1
-    },
-    {
-        "comment": "Added Airports",
-        "date": null,
-        "revision": 2
-    },
-    {
-        "comment": "Edited headers in Logbook view",
-        "date": null,
-        "revision": 3
-    },
-    {
-        "comment": "Added viewEASA for EASA logbook like display",
-        "date": "2020-11-02",
-        "revision": 4
-    },
-    {
-        "comment": "Added Diamond Aircraft to aircraft table",
-        "date": "2020-11-19",
-        "revision": 5
-    },
-    {
-        "comment": "Added icao/iata codes for Boeing in aircraft table",
-        "date": "2020-11-19",
-        "revision": 6
-    },
-    {
-        "comment": "Added autoincrement for flights pilots and tails tables",
-        "date": "2020-11-21",
-        "revision": 7
-    },
-    {
-        "comment": "changed column tDual to tDUAL in table flights for consistency in naming",
-        "date": "2020-11-23",
-        "revision": 8
-    },
-    {
-        "comment": "changed column id to flight_id in table flights for consistency in naming",
-        "date": "2020-11-23",
-        "revision": 9
-    },
-    {
-        "comment": "added viewDefault as a copy of Logbook (deprecated)",
-        "date": "2020-11-23",
-        "revision": 10
-    },
-    {
-        "comment": "added viewQCompleter as a copy of QCompleterView (deprecated)",
-        "date": "2020-11-23",
-        "revision": 11
-    },
-    {
-        "comment": "reworked views to display self or picname according CASE",
-        "date": "2020-12-11",
-        "revision": 12
-    },
-    {
-        "comment": "Reworked viewEASA to incorporate NULL handling",
-        "date": "2020-12-17",
-        "revision": 13
-    },
-    {
-        "comment": "Edited tails and aircraft tables - engineClass and weightClass instead of columns for each element",
-        "date": "2020-12-19",
-        "revision": 14
-    },
-    {
-        "comment": "Renamed some columns to have consistent naming.",
-        "date": "2020-12-26",
-        "revision": 15
-    },
-    {
-        "comment": "Added CASE for views to account for occasions where aircraft variant is NULL",
-        "date": "2021-02-03",
-        "revision": 16
-    },
-    {
-        "comment": "Added currencies table",
-        "date": "2021-02-12",
-        "revision": 17
-    },
-    {
-        "comment": "Revised currencies table column names",
-        "date": "2022-07-10",
-        "revision": 17
-    }
-]

+ 0 - 1
assets/database/templates/changelog.md5

@@ -1 +0,0 @@
-0d31231993099ea01b5f50b528702c50  changelog.json

+ 0 - 1
assets/database/templates/currencies.md5

@@ -1 +0,0 @@
-f89063deb0b19c09b567abdbe74d3e46  currencies.json

+ 135 - 135
main.cpp

@@ -1,135 +1,135 @@
-/*
- *openPilotLog - A FOSS Pilot Logbook Application
- *Copyright (C) 2020-2023 Felix Turowsky
- *
- *This program is free software: you can redistribute it and/or modify
- *it under the terms of the GNU General Public License as published by
- *the Free Software Foundation, either version 3 of the License, or
- *(at your option) any later version.
- *
- *This program is distributed in the hope that it will be useful,
- *but WITHOUT ANY WARRANTY; without even the implied warranty of
- *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *GNU General Public License for more details.
- *
- *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 "mainwindow.h"
-#include "src/opl.h"
-#include "src/functions/log.h"
-#include "src/gui/dialogues/firstrundialog.h"
-#include "src/classes/runguard.h"
-#include "src/classes/settings.h"
-#include "src/classes/settings.h"
-#include "src/classes/style.h"
-#include "src/functions/log.h"
-#include "src/classes/paths.h"
-#include <QApplication>
-#include <QProcess>
-#include <QSettings>
-#include <QFileInfo>
-#include <QStandardPaths>
-#include <QDebug>
-#include <QTranslator>
-
-
-
-/*!
- * \brief firstRun - is run if the application is run for the first time and launches
- * the FirstRunDialog which guides the user through the initial set-up process.
- */
-bool firstRun()
-{
-    if(FirstRunDialog().exec() == QDialog::Rejected){
-        LOG << "Initial setup incomplete or unsuccessfull.";
-        return false;
-    }
-
-    Settings::setSetupCompleted(true);
-    LOG << "Initial Setup Completed successfully";
-    QMessageBox mb;
-    mb.setText("Initial set-up has been completed successfully.<br><br>Please re-start the application.");
-    mb.exec();
-    return true;
-}
-
-
-/*!
- * \brief init - Sets up the logging facilities, loads the user settings and sets
- * up the application style before the MainWindow is instantiated
- */
-bool init()
-{
-    // Check if another instance of the application is already running, we don't want
-    // different processes writing to the same database
-    RunGuard guard(QStringLiteral("opl_single_key"));
-    if ( !guard.tryToRun() ){
-        LOG << "Another Instance of openPilotLog is already running. Exiting.";
-        return false;
-    }
-
-    LOG << "Setting up / verifying Application Directories...";
-    if(OPL::Paths::setup()) {
-        LOG << "Application Directories... verified";
-    } else {
-        return false;
-        LOG << "Unable to create directories.";
-    }
-
-    LOG << "Setting up logging facilities...";
-    if(OPL::Log::init(true)) {
-        LOG << "Logging enabled.";
-    } else {
-        LOG << "Unable to initalise logging.";
-    }
-
-    LOG << "Reading Settings...";
-    Settings::init();
-    LOG << "Setting up application style...";
-    OPL::Style::setup();
-    // Translations to be done at a later stage
-    //LOG << "Installing translator...";
-    //ATranslator::installTranslator(OPL::Translations::English);
-
-    // Check for First Run and launch Setup Wizard
-    if(!Settings::getSetupCompleted())
-        return firstRun();
-
-    return true;
-}
-
-int main(int argc, char *argv[])
-{
-    QApplication openPilotLog(argc, argv);
-    QCoreApplication::setOrganizationName(ORGNAME);
-    QCoreApplication::setOrganizationDomain(ORGDOMAIN);
-    QCoreApplication::setApplicationName(APPNAME);
-
-    // Set Up the Application
-    if(!init())
-        return 1;
-
-    // Create Main Window and set Window Icon acc. to Platform
-    MainWindow w;
-#ifdef __linux__
-    w.setWindowIcon(QIcon(OPL::Assets::ICON_APPICON_LINUX));
-#elif defined(_WIN32) || defined(_WIN64)
-    w.setWindowIcon(QIcon(OPL::Assets::ICON_APPICON_WIN));
-#elif __APPLE__
-    #include <TargetConditionals.h>
-    #if TARGET_IPHONE_SIMULATOR
-         // iOS Simulator
-    #elif TARGET_OS_IPHONE
-        // iOS device
-    #elif TARGET_OS_MAC
-    w.setWindowIcon(QIcon(OPL::Assets::ICON_APPICON_IOS));
-    #else
-    #   error "Unknown Apple platform"
-    #endif
-#endif
-
-    //w.showMaximized();
-    w.show();
-    return openPilotLog.exec();
-}
+/*
+ *openPilotLog - A FOSS Pilot Logbook Application
+ *Copyright (C) 2020-2023 Felix Turowsky
+ *
+ *This program is free software: you can redistribute it and/or modify
+ *it under the terms of the GNU General Public License as published by
+ *the Free Software Foundation, either version 3 of the License, or
+ *(at your option) any later version.
+ *
+ *This program is distributed in the hope that it will be useful,
+ *but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *GNU General Public License for more details.
+ *
+ *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 "mainwindow.h"
+#include "src/opl.h"
+#include "src/functions/log.h"
+#include "src/gui/dialogues/firstrundialog.h"
+#include "src/classes/runguard.h"
+#include "src/classes/settings.h"
+#include "src/classes/settings.h"
+#include "src/classes/style.h"
+#include "src/functions/log.h"
+#include "src/classes/paths.h"
+#include <QApplication>
+#include <QProcess>
+#include <QSettings>
+#include <QFileInfo>
+#include <QStandardPaths>
+#include <QDebug>
+#include <QTranslator>
+
+
+
+/*!
+ * \brief firstRun - is run if the application is run for the first time and launches
+ * the FirstRunDialog which guides the user through the initial set-up process.
+ */
+bool firstRun()
+{
+    if(FirstRunDialog().exec() == QDialog::Rejected){
+        LOG << "Initial setup incomplete or unsuccessfull.";
+        return false;
+    }
+
+    Settings::setSetupCompleted(true);
+    LOG << "Initial Setup Completed successfully";
+    QMessageBox mb;
+    mb.setText("Initial set-up has been completed successfully.<br><br>Please re-start the application.");
+    mb.exec();
+    return true;
+}
+
+
+/*!
+ * \brief init - Sets up the logging facilities, loads the user settings and sets
+ * up the application style before the MainWindow is instantiated
+ */
+bool init()
+{
+    // Check if another instance of the application is already running, we don't want
+    // different processes writing to the same database
+    RunGuard guard(QStringLiteral("opl_single_key"));
+    if ( !guard.tryToRun() ){
+        LOG << "Another Instance of openPilotLog is already running. Exiting.";
+        return false;
+    }
+
+    LOG << "Setting up / verifying Application Directories...";
+    if(OPL::Paths::setup()) {
+        LOG << "Application Directories... verified";
+    } else {
+        return false;
+        LOG << "Unable to create directories.";
+    }
+
+    LOG << "Setting up logging facilities...";
+    if(OPL::Log::init(true)) {
+        LOG << "Logging enabled.";
+    } else {
+        LOG << "Unable to initalise logging.";
+    }
+
+    LOG << "Reading Settings...";
+    Settings::init();
+    LOG << "Setting up application style...";
+    OPL::Style::setup();
+    // Translations to be done at a later stage
+    //LOG << "Installing translator...";
+    //ATranslator::installTranslator(OPL::Translations::English);
+
+    // Check for First Run and launch Setup Wizard
+    if(!Settings::getSetupCompleted())
+        return firstRun();
+
+    return true;
+}
+
+int main(int argc, char *argv[])
+{
+    QApplication openPilotLog(argc, argv);
+    QCoreApplication::setOrganizationName(ORGNAME);
+    QCoreApplication::setOrganizationDomain(ORGDOMAIN);
+    QCoreApplication::setApplicationName(APPNAME);
+
+    // Set Up the Application
+    if(!init())
+        return 1;
+
+    // Create Main Window and set Window Icon acc. to Platform
+    MainWindow w;
+#ifdef __linux__
+    w.setWindowIcon(QIcon(OPL::Assets::ICON_APPICON_LINUX));
+#elif defined(_WIN32) || defined(_WIN64)
+    w.setWindowIcon(QIcon(OPL::Assets::ICON_APPICON_WIN));
+#elif __APPLE__
+    #include <TargetConditionals.h>
+    #if TARGET_IPHONE_SIMULATOR
+         // iOS Simulator
+    #elif TARGET_OS_IPHONE
+        // iOS device
+    #elif TARGET_OS_MAC
+    w.setWindowIcon(QIcon(OPL::Assets::ICON_APPICON_IOS));
+    #else
+    #   error "Unknown Apple platform"
+    #endif
+#endif
+
+    //w.showMaximized();
+    w.show();
+    return openPilotLog.exec();
+}

+ 39 - 39
src/classes/paths.cpp

@@ -1,39 +1,39 @@
-#include "paths.h"
-#include "src/opl.h"
-
-namespace OPL {
-
-const bool Paths::setup() {
-    LOG << "Setting up directories at: " << basePath;
-        for(const auto& str : qAsConst(directories)){
-        QDir dir(basePath + str);
-        if(!dir.exists()) {
-            if (!dir.mkpath(dir.absolutePath()))
-                return false;
-        }
-    }
-    return true;
-}
-
-const QDir Paths::directory(Directories location)
-{
-    return QDir(basePath + directories[location]);
-}
-
-const QString Paths::path(Directories location)
-{
-    return QDir::toNativeSeparators(basePath + directories[location]);
-}
-
-const QString Paths::filePath(Directories location, const QString &filename)
-{
-    QDir dir(basePath + directories[location]);
-    return dir.absoluteFilePath(filename);
-}
-
-const QFileInfo Paths::databaseFileInfo()
-{
-    return QFileInfo(directory(Database), QStringLiteral("logbook.db"));
-}
-
-} // namespace OPL
+#include "paths.h"
+#include "src/opl.h"
+
+namespace OPL {
+
+const bool Paths::setup() {
+    LOG << "Setting up directories at: " << basePath;
+        for(const auto& str : std::as_const(directories)){
+        QDir dir(basePath + str);
+        if(!dir.exists()) {
+            if (!dir.mkpath(dir.absolutePath()))
+                return false;
+        }
+    }
+    return true;
+}
+
+const QDir Paths::directory(Directories location)
+{
+    return QDir(basePath + directories[location]);
+}
+
+const QString Paths::path(Directories location)
+{
+    return QDir::toNativeSeparators(basePath + directories[location]);
+}
+
+const QString Paths::filePath(Directories location, const QString &filename)
+{
+    QDir dir(basePath + directories[location]);
+    return dir.absoluteFilePath(filename);
+}
+
+const QFileInfo Paths::databaseFileInfo()
+{
+    return QFileInfo(directory(Database), QStringLiteral("logbook.db"));
+}
+
+} // namespace OPL

+ 798 - 805
src/database/database.cpp

@@ -1,805 +1,798 @@
-/*
- *openPilotLog - A FOSS Pilot Logbook Application
- *Copyright (C) 2020-2023 Felix Turowsky
- *
- *This program is free software: you can redistribute it and/or modify
- *it under the terms of the GNU General Public License as published by
- *the Free Software Foundation, either version 3 of the License, or
- *(at your option) any later version.
- *
- *This program is distributed in the hope that it will be useful,
- *but WITHOUT ANY WARRANTY; without even the implied warranty of
- *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *GNU General Public License for more details.
- *
- *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 "database.h"
-#include "src/opl.h"
-#include "src/classes/jsonhelper.h"
-
-namespace OPL {
-
-Database* Database::self = nullptr;
-
-bool Database::connect()
-{
-    if (!QSqlDatabase::isDriverAvailable(SQLITE_DRIVER)) {
-        LOG << "Error: No SQLITE Driver availabe.";
-        return false;
-    }
-
-    QSqlDatabase db = QSqlDatabase::addDatabase(SQLITE_DRIVER);
-    db.setDatabaseName(databaseFile.absoluteFilePath());
-
-    if (!db.open()) {
-        LOG << QString("Unable to establish database connection.<br>The following error has ocurred:<br><br>%1")
-               .arg(db.lastError().databaseText());
-        lastError = db.lastError();
-        return false;
-    }
-
-
-    LOG << "Database connection established: " + databaseFile.absoluteFilePath();
-    // Enable foreign key restrictions
-    QSqlQuery query;
-    query.prepare(QStringLiteral("PRAGMA foreign_keys = ON;"));
-    query.exec();
-    updateLayout();
-    return true;
-}
-
-void Database::disconnect()
-{
-    QString connection_name;
-    {
-    auto db = Database::database();
-    connection_name = db.connectionName();
-    db.close();
-    }
-    QSqlDatabase::removeDatabase(connection_name);
-    LOG << "Database connection closed.";
-}
-
-const QString Database::version() const
-{
-    QSqlQuery query(QStringLiteral("SELECT COUNT(*) FROM changelog"));
-    query.next();
-    return QStringLiteral("OPL Database Revision: ") + QString::number(query.value(0).toInt());
-}
-
-const QList<OPL::DbTable> &Database::getTemplateTables() const
-{
-    return TEMPLATE_TABLES;
-}
-
-const QList<OPL::DbTable> &Database::getUserTables() const
-{
-    return USER_TABLES;
-}
-
-const QStringList Database::getTableColumns(OPL::DbTable table_name) const
-{
-    return tableColumns.value(OPL::GLOBALS->getDbTableName(table_name));
-}
-
-const QStringList Database::getTableNames() const
-{
-    return tableNames;
-}
-
-void Database::updateLayout()
-{
-    auto db = Database::database();
-    tableNames = db.tables();
-
-    tableColumns.clear();
-    for (const auto &table_name : qAsConst(tableNames)) {
-        QStringList table_columns;
-        QSqlRecord fields = db.record(table_name);
-        for (int i = 0; i < fields.count(); i++) {
-            table_columns.append(fields.field(i).name());
-        }
-        tableColumns.insert(table_name, table_columns);
-    }
-    emit dataBaseUpdated(DbTable::Any);
-}
-
-Database* Database::instance()
-{
-    if(!self)
-        self = new Database();
-
-    return self;
-}
-
-const QString Database::sqliteVersion() const
-{
-    QSqlQuery query;
-    query.prepare(QStringLiteral("SELECT sqlite_version()"));
-    query.exec();
-    query.next();
-    return query.value(0).toString();
-}
-
-QSqlDatabase Database::database()
-{
-    return QSqlDatabase::database(QStringLiteral("qt_sql_default_connection"));
-}
-
-bool Database::commit(const OPL::Row &row)
-{
-    if (!row.isValid())
-        return false;
-
-    if (exists(row))
-        return update(row);
-    else
-        return insert(row);
-}
-
-bool Database::commit(const QJsonArray &json_arr, const OPL::DbTable table)
-{
-    // create statement
-    const QString table_name = OPL::GLOBALS->getDbTableName(table);
-    QString statement = QLatin1String("INSERT INTO ") + table_name + QLatin1String(" (");
-    QString placeholder = QStringLiteral(") VALUES (");
-    for (const auto &column_name : DB->getTableColumns(table)) {
-        statement += column_name + ',';
-        placeholder.append(QLatin1Char(':') + column_name + QLatin1Char(','));
-    }
-
-    statement.chop(1);
-    placeholder.chop(1);
-    placeholder.append(')');
-    statement.append(placeholder);
-
-    // Create query and commit
-    QSqlQuery q;
-    q.prepare(QStringLiteral("BEGIN EXCLUSIVE TRANSACTION"));
-    q.exec();
-    for (const auto &entry : json_arr) {
-        q.prepare(statement);
-        auto object = entry.toObject();
-        const auto keys = object.keys();
-
-        for (const auto &key : keys){
-//use QMetaType for binding null value in QT >= 6
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
-            object.value(key).isNull() ? q.bindValue(key, QVariant(QMetaType(QMetaType::Int))) :
-#else
-            object.value(key).isNull() ? q.bindValue(key, QVariant(QVariant::String)) :
-#endif
-                                         q.bindValue(QLatin1Char(':') + key, object.value(key).toVariant());
-        }
-        q.exec();
-    }
-
-    q.prepare(QStringLiteral("COMMIT"));
-    if (q.exec())
-        return true;
-    else
-        return false;
-}
-
-bool Database::remove(const OPL::Row &row)
-{
-    if (!exists(row)) {
-        LOG << "Error: Database entry not found.";
-        return false;
-    }
-
-    const QString table_name = OPL::GLOBALS->getDbTableName(row.getTable());
-
-    QString statement = QLatin1String("DELETE FROM ") + table_name
-            + QLatin1String(" WHERE ROWID=?");
-
-    QSqlQuery query;
-    query.prepare(statement);
-    query.addBindValue(row.getRowId());
-
-    if (query.exec())
-    {
-        LOG << "Entry removed:";
-        LOG << row;
-        emit dataBaseUpdated(row.getTable());
-        return true;
-    } else {
-        DEB << "Unable to delete.";
-        DEB << "Query: " << statement;
-        DEB << "Query Error: " << query.lastError().text();
-        lastError = query.lastError();
-        return false;
-    }
-}
-
-bool Database::removeMany(OPL::DbTable table, const QList<int> &row_id_list)
-{
-    const QString table_name = OPL::GLOBALS->getDbTableName(table);
-    int errorCount = 0;
-
-    QSqlQuery query;
-    query.prepare(QStringLiteral("BEGIN EXCLUSIVE TRANSACTION"));
-    query.exec();
-
-    for (const auto row_id : row_id_list) {
-        const QString statement = QLatin1String("DELETE FROM ") + table_name +
-                QLatin1String(" WHERE ROWID=?");
-
-        query.prepare(statement);
-        query.addBindValue(row_id);
-
-        if (!query.exec())
-            errorCount++;
-    }
-
-    if (errorCount == 0) {
-        query.prepare(QStringLiteral("COMMIT"));
-        if(query.exec()) {
-            emit dataBaseUpdated(table);
-            LOG << "Transaction successfull.";
-            return true;
-        } else {
-            LOG << "Transaction unsuccessful (Interrupted). Error count: "
-                        + QString::number(errorCount);
-            DEB << query.lastError().text();
-            lastError = query.lastError();
-            return false;
-        }
-    } else {
-        query.prepare(QStringLiteral("ROLLBACK"));
-        query.exec();
-        LOG << "Transaction unsuccessful (no changes have been made). Error count: "
-                    + QString::number(errorCount);
-        return false;
-    }
-}
-
-bool Database::exists(const OPL::Row &row)
-{
-    if (row.getRowId() == 0)
-        return false;
-
-    //Check database for row id
-    QString statement = QLatin1String("SELECT COUNT(*) FROM ") + OPL::GLOBALS->getDbTableName(row.getTable())
-            + QLatin1String(" WHERE ROWID=?");
-    QSqlQuery query;
-    query.prepare(statement);
-    query.addBindValue(row.getRowId());
-    query.setForwardOnly(true);
-    query.exec();
-    //this returns either 1 or 0 since row ids are unique
-    if (!query.isActive()) {
-        lastError = query.lastError();
-        DEB << "Query Error: " << query.lastError().text() << statement;
-        return false;
-    }
-    query.next();
-    int rowId = query.value(0).toInt();
-    if (rowId) {
-        return true;
-    } else {
-        LOG << "Database entry not found.";
-        return false;
-    }
-}
-
-bool Database::clear()
-{
-    QSqlQuery q;
-
-    for (const auto &table : USER_TABLES) {
-        q.prepare(QLatin1String("DELETE FROM ") + OPL::GLOBALS->getDbTableName(table));
-        if (!q.exec()) {
-            DEB << "Error: " << q.lastError().text();
-            lastError = q.lastError();
-            return false;
-        }
-    }
-    return true;
-}
-
-bool Database::update(const OPL::Row &updated_row)
-{
-    QString statement = QLatin1String("UPDATE ") + OPL::GLOBALS->getDbTableName(updated_row.getTable()) + QLatin1String(" SET ");
-    const auto& data = updated_row.getData();
-    for (auto i = data.constBegin(); i != data.constEnd(); ++i) {
-        statement.append(i.key() + "=?,");
-    }
-    statement.chop(1);
-    statement.append(QLatin1String(" WHERE ROWID=?"));
-    QSqlQuery query;
-    query.prepare(statement);
-    DEB << "Statement: " << statement;
-    for (auto i = data.constBegin(); i != data.constEnd(); ++i) {
-//use QMetaType for binding null value in QT >= 6
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
-        if (i.value() == QVariant(QString())) {
-            query.addBindValue(QVariant(QMetaType(QMetaType::Int)));
-#else
-        if (i.value() == QVariant(QString())) {
-            query.addBindValue(QVariant(QVariant::String));
-#endif
-        } else {
-            query.addBindValue(i.value());
-        }
-
-    }
-    query.addBindValue(updated_row.getRowId());
-    DEB << "Bound values: " << query.boundValues();
-
-    if (query.exec())
-    {
-        LOG << QString("Entry successfully committed. %1").arg(updated_row.getPosition());
-        emit dataBaseUpdated(updated_row.getTable());
-        return true;
-    } else {
-        DEB << "Unable to commit.";
-        DEB << "Query: " << statement;
-        DEB << "Query Error: " << query.lastError().text();
-        lastError = query.lastError();
-        return false;
-    }
-}
-
-bool Database::insert(const OPL::Row &new_row)
-{
-    QString statement = QLatin1String("INSERT INTO ") + OPL::GLOBALS->getDbTableName(new_row.getTable()) + QLatin1String(" (");
-    const auto& data = new_row.getData();
-    QHash<QString, QVariant>::const_iterator i;
-    for (i = data.constBegin(); i != data.constEnd(); ++i) {
-        statement.append(i.key() + QLatin1Char(','));
-    }
-    statement.chop(1);
-    statement += QLatin1String(") VALUES (");
-
-    for (int i=0; i < new_row.getData().size(); ++i) {
-        statement += QLatin1String("?,");
-    }
-    statement.chop(1);
-    statement += QLatin1Char(')');
-
-    QSqlQuery query;
-    query.prepare(statement);
-
-    for (i = data.constBegin(); i != data.constEnd(); ++i) {
-//use QMetaType for binding null value in QT >= 6
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
-        if (i.value() == QVariant(QString())) {
-            query.addBindValue(QVariant(QMetaType(QMetaType::Int)));
-#else
-        if (i.value() == QVariant(QString())) {
-            query.addBindValue(QVariant(QVariant::String));
-#endif
-        } else {
-            query.addBindValue(i.value());
-        }
-    }
-
-    //check result.
-    if (query.exec())
-    {
-        LOG << QString("Entry successfully committed. %1").arg(new_row.getPosition());
-        emit dataBaseUpdated(new_row.getTable());
-        return true;
-    } else {
-        DEB << "Unable to commit.";
-        DEB << "Query: " << statement;
-        DEB << "Bound Values: " << query.boundValues();
-        DEB << "Query Error: " << query.lastError().text();
-        lastError = query.lastError();
-        return false;
-    }
-}
-
-OPL::Row Database::getRow(const OPL::DbTable table, const int row_id)
-{
-    QString statement = QLatin1String("SELECT * FROM ") + OPL::GLOBALS->getDbTableName(table)
-            + QLatin1String(" WHERE ROWID=?");
-    QSqlQuery q;
-    q.prepare(statement);
-    q.addBindValue(row_id);
-    q.setForwardOnly(true);
-
-    if (!q.exec()) {
-        DEB << "SQL error: " << q.lastError().text();
-        DEB << "Statement: " << q.lastQuery();
-        lastError = q.lastError();
-        return {}; // return invalid Row
-    }
-
-    RowData_T entry_data;
-    if(q.next()) {
-        auto r = q.record(); // retreive record
-        if (r.count() == 0)  // row is empty
-            return {};
-
-        for (int i = 0; i < r.count(); i++){ // iterate through fields to get key:value map
-            if(!r.value(i).isNull()) {
-                entry_data.insert(r.fieldName(i), r.value(i));
-            }
-        }
-    }
-
-    return OPL::Row(table, row_id, entry_data);
-}
-
-RowData_T Database::getRowData(const OPL::DbTable table, const int row_id)
-{
-    QString statement = QLatin1String("SELECT * FROM ") + OPL::GLOBALS->getDbTableName(table)
-            + QLatin1String(" WHERE ROWID=?");
-    QSqlQuery q;
-    q.prepare(statement);
-    q.addBindValue(row_id);
-    q.setForwardOnly(true);
-
-    if (!q.exec()) {
-        DEB << "SQL error: " << q.lastError().text();
-        DEB << "Statement: " << q.lastQuery();
-        lastError = q.lastError();
-        return {}; // return invalid Row
-    }
-
-    RowData_T entry_data;
-    if(q.next()) {
-        auto r = q.record(); // retreive record
-        if (r.count() == 0)  // row is empty
-            return {};
-
-        for (int i = 0; i < r.count(); i++){ // iterate through fields to get key:value map
-            if(!r.value(i).isNull()) {
-                entry_data.insert(r.fieldName(i), r.value(i));
-            }
-        }
-    }
-
-    return entry_data;
-}
-
-int Database::getLastEntry(OPL::DbTable table)
-{
-    QString statement = QLatin1String("SELECT MAX(ROWID) FROM ") + OPL::GLOBALS->getDbTableName(table);
-
-    auto query = QSqlQuery(statement);
-    if (query.first()) {
-        return query.value(0).toInt();
-    } else {
-        LOG << "No entry found. (Database empty?)" << query.lastError().text();
-        return 0;
-    }
-}
-
-const RowData_T Database::getTotals(bool includePreviousExperience)
-{
-    QString statement = "SELECT"
-        " SUM(tblk) AS tblk,"
-        " SUM(tSPSE) AS tSPSE,"
-        " SUM(tSPME) AS tSPME,"
-        " SUM(tMP) AS tMP,"
-        " SUM(tPIC) AS tPIC,"
-        " SUM(tSIC) AS tSIC,"
-        " SUM(tDUAL) AS tDUAL,"
-        " SUM(tFI) AS tFI,"
-        " SUM(tPICUS) AS tPICUS,"
-        " SUM(tNIGHT) AS tNIGHT,"
-        " SUM(tIFR) AS tIFR,"
-        " SUM(tSIM) AS tSIM,"
-        " SUM(toDay) AS toDay,"
-        " SUM(toNight) AS toNight,"
-        " SUM(ldgDay) AS ldgDay,"
-        " SUM(ldgNight) AS ldgNight"
-        " FROM flights";
-
-    QSqlQuery query;
-    query.prepare(statement);
-    if (!query.exec()) {
-        DEB << "SQL error: " << query.lastError().text();
-        DEB << "Statement: " << query.lastQuery();
-        lastError = query.lastError();
-        return {}; // return invalid Row
-    }
-
-    RowData_T entry_data;
-    if(query.next()) {
-        auto r = query.record(); // retreive record
-        if (r.count() == 0)  // row is empty
-            return {};
-
-        for (int i = 0; i < r.count(); i++){ // iterate through fields to get key:value map
-            if(!r.value(i).isNull()) {
-                entry_data.insert(r.fieldName(i), r.value(i));
-            }
-        }
-    }
-
-    if(!includePreviousExperience) {
-        return entry_data;
-    }
-
-    // name the return types for easy mapping to QLineEdit names
-    statement = "SELECT"
-                " SUM(tblk) AS tblk,"
-                " SUM(tSPSE) AS tSPSE,"
-                " SUM(tSPME) AS tSPME,"
-                " SUM(tMP) AS tMP,"
-                " SUM(tPIC) AS tPIC,"
-                " SUM(tSIC) AS tSIC,"
-                " SUM(tDUAL) AS tDUAL,"
-                " SUM(tFI) AS tFI,"
-                " SUM(tPICUS) AS tPICUS,"
-                " SUM(tNIGHT) AS tNIGHT,"
-                " SUM(tIFR) AS tIFR,"
-                " SUM(tSIM) AS tSIM,"
-                " SUM(toDay) AS toDay,"
-                " SUM(toNight) AS toNight,"
-                " SUM(ldgDay) AS ldgDay,"
-                " SUM(ldgNight) AS ldgNight"
-                " FROM previousExperience";
-    query.prepare(statement);
-
-    if (!query.exec()) {
-        DEB << "SQL error: " << query.lastError().text();
-        DEB << "Statement: " << query.lastQuery();
-        lastError = query.lastError();
-        return {}; // return invalid Row
-    }
-
-    RowData_T prev_exp_data;
-    if(query.next()) {
-        auto r = query.record(); // retreive record
-        if (r.count() == 0)  // row is empty
-            return {};
-
-        for (int i = 0; i < r.count(); i++){ // iterate through fields to get key:value map
-            if(!r.value(i).isNull()) {
-                prev_exp_data.insert(r.fieldName(i), r.value(i));
-            }
-        }
-    }
-
-    // add up the two query results
-    for(auto it = prev_exp_data.begin(); it != prev_exp_data.end(); it++) {
-        int prevXpValue = it.value().toInt();
-        int entryValue = entry_data.value(it.key()).toInt();
-
-        const QVariant sum = prevXpValue + entryValue;
-        entry_data.insert(it.key(), sum);
-    }
-
-    return entry_data;
-}
-
-QList<int> Database::getForeignKeyConstraints(int foreign_row_id, OPL::DbTable table)
-{
-    QString statement = QLatin1String("SELECT ROWID FROM flights WHERE ");
-
-    switch (table) {
-    case OPL::DbTable::Pilots:
-        statement.append(QLatin1String("pic=?"));
-        break;
-    case OPL::DbTable::Tails:
-        statement.append(QLatin1String("acft=?"));
-        break;
-    default:
-        DEB << "Not a valid target for this function.";
-        return QList<int>();
-        break;
-    }
-
-    QSqlQuery query;
-    query.prepare(statement);
-    query.addBindValue(foreign_row_id);
-    query.exec();
-
-    if (!query.isActive()) {
-        lastError = query.lastError();
-        DEB << "Error";
-        DEB << statement;
-        DEB << query.lastError().text();
-        return QList<int>();
-    }
-
-    QList<int> row_ids;
-    while (query.next()) {
-        row_ids.append(query.value(0).toInt());
-    }
-    return row_ids;
-}
-
-QVector<QVariant> Database::customQuery(QString statement, int return_values)
-{
-    QSqlQuery query(statement);
-    if(!query.exec()) {
-        lastError = query.lastError();
-        DEB << "Query Error: " << lastError.text();
-        return {};
-    }
-
-    if (!query.first()) {
-        LOG << "No result found. Check Query and Error.";
-        DEB << "Error: " << query.lastError().text();
-        DEB << "Statement: " << statement;
-        return QVector<QVariant>();
-    } else {
-        query.first();
-        query.previous();
-        QVector<QVariant> result;
-        while (query.next()) {
-            for (int i = 0; i < return_values ; i++) {
-                result.append(query.value(i));
-            }
-        }
-        lastError = QString();
-        return result;
-    }
-}
-
-QVector<RowData_T> Database::getTable(OPL::DbTable table)
-{
-    const QString query_str = QStringLiteral("SELECT * FROM ") + GLOBALS->getDbTableName(table);
-
-    QSqlQuery q;
-    q.prepare(query_str);
-    q.setForwardOnly(true);
-
-    if (!q.exec()) {
-        LOG << "SQL error: " << q.lastError().text();
-        LOG << "Statement: " << query_str;
-        lastError = q.lastError();
-        return {};
-    }
-
-    QVector<RowData_T> entry_data;
-    while(q.next()) { // iterate through records
-        auto r = q.record();
-        //DEB << r;
-        RowData_T row;
-        for (int i = 0; i < r.count(); i++){
-            if(!r.value(i).isNull()) {
-                row.insert(r.fieldName(i), r.value(i));
-            }
-        }
-        entry_data.append(row);
-    }
-    return entry_data;
-}
-
-bool Database::createBackup(const QString& dest_file)
-{
-    LOG << "Backing up current database to: " << dest_file;
-    Database::disconnect();
-    QFile db_file(QDir::toNativeSeparators(databaseFile.absoluteFilePath()));
-
-    if (!db_file.copy(QDir::toNativeSeparators(dest_file))) {
-        LOG << "Unable to backup old database:" << db_file.errorString();
-        return false;
-    }
-
-    LOG << "Backed up old database as:" << dest_file;
-    Database::connect();
-    emit connectionReset();
-    return true;
-}
-
-bool Database::restoreBackup(const QString& backup_file)
-{
-    Database::disconnect();
-    LOG << "Restoring backup from file:" << backup_file;
-
-    QString databaseFilePath = QDir::toNativeSeparators(databaseFile.absoluteFilePath());
-    DEB << "DB File Path: " << databaseFilePath;
-    QString backupFilePath = QDir::toNativeSeparators(backup_file);
-
-    QFile dbFile(databaseFilePath);
-    if(dbFile.exists())
-        if(!dbFile.remove()) {
-            LOG << dbFile.errorString() << "Unable to remove current db file";
-            return false;
-        }
-
-    QFile backupFile(backupFilePath);
-    if(!backupFile.copy(databaseFilePath)) {
-        LOG << backupFile.errorString() << "Could not copy" << backupFile.fileName() << " to " << databaseFilePath;
-        return false;
-    }
-
-    LOG << "Backup successfully restored!";
-    Database::connect();
-    emit connectionReset();
-    return true;
-}
-
-bool Database::createSchema()
-{
-    // Read Database layout from sql file
-    QFile f(OPL::Assets::DATABASE_SCHEMA);
-    f.open(QIODevice::ReadOnly);
-    QByteArray filedata = f.readAll();
-    // create individual queries for each table/view
-    auto list = filedata.split(';');
-
-    // make sure last empty line in sql file has not been parsed
-    if(list.last() == QByteArray("\n") || list.last() == QByteArray("\r\n"))
-        list.removeLast();
-
-    // Create Tables
-    QSqlQuery q;
-    QVector<QSqlError> errors;
-    for (const auto &query_string : list) {
-        q.prepare(query_string);
-        if (!q.exec()) {
-            errors.append(q.lastError());
-            LOG << "Unable to execute query: ";
-            LOG << q.lastQuery();
-            LOG << q.lastError();
-        }
-    }
-    DB->updateLayout();
-
-    if (errors.isEmpty()) {
-        LOG << "Database succesfully created.";
-        return true;
-    } else {
-        LOG << "Database creation has failed. The following error(s) have ocurred: ";
-        for (const auto &error : qAsConst(errors)) {
-            LOG << error.type() << error.text();
-        }
-        return false;
-    }
-}
-
-bool Database::importTemplateData(bool use_local_ressources)
-{
-    for (const auto& table : DB->getTemplateTables()) {
-        const QString table_name = OPL::GLOBALS->getDbTableName(table);
-
-        //clear table
-        QSqlQuery q;
-        q.prepare(QLatin1String("DELETE FROM ") + table_name);
-        if (!q.exec()) {
-            LOG << "Error clearing tables: " << q.lastError().text();
-            return false;
-        }
-
-        //Prepare data
-        QJsonArray data_to_commit;
-        QString error_message("Error importing data ");
-
-        if (use_local_ressources) {
-            data_to_commit = JsonHelper::readFileToDoc(QLatin1String(":database/templates/")
-                                      + table_name + QLatin1String(".json")).array();
-            error_message.append(QLatin1String(" (ressource) "));
-        } else {
-            const QString file_path = OPL::Paths::filePath(OPL::Paths::Templates,
-                                                           table_name + QLatin1String(".json"));
-            data_to_commit = JsonHelper::readFileToDoc(file_path).array();
-            //data_to_commit = AJson::readFileToDoc(AStandardPaths::directory(
-            //                              AStandardPaths::Templates).absoluteFilePath(
-            //                              table_name + QLatin1String(".json"))).array();
-            error_message.append(QLatin1String(" (downloaded) "));
-        }
-
-        // commit Data from Array
-        if (!DB->commit(data_to_commit, table)) {
-            LOG << error_message;
-            return false;
-        }
-    } // for table_name
-    return true;
-}
-
-bool Database::resetUserData()
-{
-    QSqlQuery query;
-    for (const auto& table : DB->getUserTables()) {
-        query.prepare(QLatin1String("DELETE FROM ") + OPL::GLOBALS->getDbTableName(table));
-        if (!query.exec()) {
-            lastError = query.lastError();
-            return false;
-        }
-    }
-    return true;
-}
-
-} // namespace OPL
+/*
+ *openPilotLog - A FOSS Pilot Logbook Application
+ *Copyright (C) 2020-2023 Felix Turowsky
+ *
+ *This program is free software: you can redistribute it and/or modify
+ *it under the terms of the GNU General Public License as published by
+ *the Free Software Foundation, either version 3 of the License, or
+ *(at your option) any later version.
+ *
+ *This program is distributed in the hope that it will be useful,
+ *but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *GNU General Public License for more details.
+ *
+ *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 "database.h"
+#include "src/opl.h"
+#include "src/classes/jsonhelper.h"
+
+namespace OPL {
+
+Database* Database::self = nullptr;
+
+bool Database::connect()
+{
+    if (!QSqlDatabase::isDriverAvailable(SQLITE_DRIVER)) {
+        LOG << "Error: No SQLITE Driver availabe.";
+        return false;
+    }
+
+    QSqlDatabase db = QSqlDatabase::addDatabase(SQLITE_DRIVER);
+    db.setDatabaseName(databaseFile.absoluteFilePath());
+
+    if (!db.open()) {
+        LOG << QString("Unable to establish database connection.<br>The following error has ocurred:<br><br>%1")
+               .arg(db.lastError().databaseText());
+        lastError = db.lastError();
+        return false;
+    }
+
+
+    LOG << "Database connection established: " + databaseFile.absoluteFilePath();
+    // Enable foreign key restrictions
+    QSqlQuery query;
+    query.prepare(QStringLiteral("PRAGMA foreign_keys = ON;"));
+    query.exec();
+    updateLayout();
+    return true;
+}
+
+void Database::disconnect()
+{
+    QString connection_name;
+    {
+    auto db = Database::database();
+    connection_name = db.connectionName();
+    db.close();
+    }
+    QSqlDatabase::removeDatabase(connection_name);
+    LOG << "Database connection closed.";
+}
+
+const QList<OPL::DbTable> &Database::getTemplateTables() const
+{
+    return TEMPLATE_TABLES;
+}
+
+const QList<OPL::DbTable> &Database::getUserTables() const
+{
+    return USER_TABLES;
+}
+
+const QStringList Database::getTableColumns(OPL::DbTable table_name) const
+{
+    return tableColumns.value(OPL::GLOBALS->getDbTableName(table_name));
+}
+
+const QStringList Database::getTableNames() const
+{
+    return tableNames;
+}
+
+void Database::updateLayout()
+{
+    auto db = Database::database();
+    tableNames = db.tables();
+
+    tableColumns.clear();
+    for (const auto &table_name : std::as_const(tableNames)) {
+        QStringList table_columns;
+        QSqlRecord fields = db.record(table_name);
+        for (int i = 0; i < fields.count(); i++) {
+            table_columns.append(fields.field(i).name());
+        }
+        tableColumns.insert(table_name, table_columns);
+    }
+    emit dataBaseUpdated(DbTable::Any);
+}
+
+Database* Database::instance()
+{
+    if(!self)
+        self = new Database();
+
+    return self;
+}
+
+const QString Database::sqliteVersion() const
+{
+    QSqlQuery query;
+    query.prepare(QStringLiteral("SELECT sqlite_version()"));
+    query.exec();
+    query.next();
+    return query.value(0).toString();
+}
+
+QSqlDatabase Database::database()
+{
+    return QSqlDatabase::database(QStringLiteral("qt_sql_default_connection"));
+}
+
+bool Database::commit(const OPL::Row &row)
+{
+    if (!row.isValid())
+        return false;
+
+    if (exists(row))
+        return update(row);
+    else
+        return insert(row);
+}
+
+bool Database::commit(const QJsonArray &json_arr, const OPL::DbTable table)
+{
+    // create statement
+    const QString table_name = OPL::GLOBALS->getDbTableName(table);
+    QString statement = QLatin1String("INSERT INTO ") + table_name + QLatin1String(" (");
+    QString placeholder = QStringLiteral(") VALUES (");
+    for (const auto &column_name : DB->getTableColumns(table)) {
+        statement += column_name + ',';
+        placeholder.append(QLatin1Char(':') + column_name + QLatin1Char(','));
+    }
+
+    statement.chop(1);
+    placeholder.chop(1);
+    placeholder.append(')');
+    statement.append(placeholder);
+
+    // Create query and commit
+    QSqlQuery q;
+    q.prepare(QStringLiteral("BEGIN EXCLUSIVE TRANSACTION"));
+    q.exec();
+    for (const auto &entry : json_arr) {
+        q.prepare(statement);
+        auto object = entry.toObject();
+        const auto keys = object.keys();
+
+        for (const auto &key : keys){
+//use QMetaType for binding null value in QT >= 6
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+            object.value(key).isNull() ? q.bindValue(key, QVariant(QMetaType(QMetaType::Int))) :
+#else
+            object.value(key).isNull() ? q.bindValue(key, QVariant(QVariant::String)) :
+#endif
+                                         q.bindValue(QLatin1Char(':') + key, object.value(key).toVariant());
+        }
+        q.exec();
+    }
+
+    q.prepare(QStringLiteral("COMMIT"));
+    if (q.exec())
+        return true;
+    else
+        return false;
+}
+
+bool Database::remove(const OPL::Row &row)
+{
+    if (!exists(row)) {
+        LOG << "Error: Database entry not found.";
+        return false;
+    }
+
+    const QString table_name = OPL::GLOBALS->getDbTableName(row.getTable());
+
+    QString statement = QLatin1String("DELETE FROM ") + table_name
+            + QLatin1String(" WHERE ROWID=?");
+
+    QSqlQuery query;
+    query.prepare(statement);
+    query.addBindValue(row.getRowId());
+
+    if (query.exec())
+    {
+        LOG << "Entry removed:";
+        LOG << row;
+        emit dataBaseUpdated(row.getTable());
+        return true;
+    } else {
+        DEB << "Unable to delete.";
+        DEB << "Query: " << statement;
+        DEB << "Query Error: " << query.lastError().text();
+        lastError = query.lastError();
+        return false;
+    }
+}
+
+bool Database::removeMany(OPL::DbTable table, const QList<int> &row_id_list)
+{
+    const QString table_name = OPL::GLOBALS->getDbTableName(table);
+    int errorCount = 0;
+
+    QSqlQuery query;
+    query.prepare(QStringLiteral("BEGIN EXCLUSIVE TRANSACTION"));
+    query.exec();
+
+    for (const auto row_id : row_id_list) {
+        const QString statement = QLatin1String("DELETE FROM ") + table_name +
+                QLatin1String(" WHERE ROWID=?");
+
+        query.prepare(statement);
+        query.addBindValue(row_id);
+
+        if (!query.exec())
+            errorCount++;
+    }
+
+    if (errorCount == 0) {
+        query.prepare(QStringLiteral("COMMIT"));
+        if(query.exec()) {
+            emit dataBaseUpdated(table);
+            LOG << "Transaction successfull.";
+            return true;
+        } else {
+            LOG << "Transaction unsuccessful (Interrupted). Error count: "
+                        + QString::number(errorCount);
+            DEB << query.lastError().text();
+            lastError = query.lastError();
+            return false;
+        }
+    } else {
+        query.prepare(QStringLiteral("ROLLBACK"));
+        query.exec();
+        LOG << "Transaction unsuccessful (no changes have been made). Error count: "
+                    + QString::number(errorCount);
+        return false;
+    }
+}
+
+bool Database::exists(const OPL::Row &row)
+{
+    if (row.getRowId() == 0)
+        return false;
+
+    //Check database for row id
+    QString statement = QLatin1String("SELECT COUNT(*) FROM ") + OPL::GLOBALS->getDbTableName(row.getTable())
+            + QLatin1String(" WHERE ROWID=?");
+    QSqlQuery query;
+    query.prepare(statement);
+    query.addBindValue(row.getRowId());
+    query.setForwardOnly(true);
+    query.exec();
+    //this returns either 1 or 0 since row ids are unique
+    if (!query.isActive()) {
+        lastError = query.lastError();
+        DEB << "Query Error: " << query.lastError().text() << statement;
+        return false;
+    }
+    query.next();
+    int rowId = query.value(0).toInt();
+    if (rowId) {
+        return true;
+    } else {
+        LOG << "Database entry not found.";
+        return false;
+    }
+}
+
+bool Database::clear()
+{
+    QSqlQuery q;
+
+    for (const auto &table : USER_TABLES) {
+        q.prepare(QLatin1String("DELETE FROM ") + OPL::GLOBALS->getDbTableName(table));
+        if (!q.exec()) {
+            DEB << "Error: " << q.lastError().text();
+            lastError = q.lastError();
+            return false;
+        }
+    }
+    return true;
+}
+
+bool Database::update(const OPL::Row &updated_row)
+{
+    QString statement = QLatin1String("UPDATE ") + OPL::GLOBALS->getDbTableName(updated_row.getTable()) + QLatin1String(" SET ");
+    const auto& data = updated_row.getData();
+    for (auto i = data.constBegin(); i != data.constEnd(); ++i) {
+        statement.append(i.key() + "=?,");
+    }
+    statement.chop(1);
+    statement.append(QLatin1String(" WHERE ROWID=?"));
+    QSqlQuery query;
+    query.prepare(statement);
+    DEB << "Statement: " << statement;
+    for (auto i = data.constBegin(); i != data.constEnd(); ++i) {
+//use QMetaType for binding null value in QT >= 6
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+        if (i.value() == QVariant(QString())) {
+            query.addBindValue(QVariant(QMetaType(QMetaType::Int)));
+#else
+        if (i.value() == QVariant(QString())) {
+            query.addBindValue(QVariant(QVariant::String));
+#endif
+        } else {
+            query.addBindValue(i.value());
+        }
+
+    }
+    query.addBindValue(updated_row.getRowId());
+    DEB << "Bound values: " << query.boundValues();
+
+    if (query.exec())
+    {
+        LOG << QString("Entry successfully committed. %1").arg(updated_row.getPosition());
+        emit dataBaseUpdated(updated_row.getTable());
+        return true;
+    } else {
+        DEB << "Unable to commit.";
+        DEB << "Query: " << statement;
+        DEB << "Query Error: " << query.lastError().text();
+        lastError = query.lastError();
+        return false;
+    }
+}
+
+bool Database::insert(const OPL::Row &new_row)
+{
+    QString statement = QLatin1String("INSERT INTO ") + OPL::GLOBALS->getDbTableName(new_row.getTable()) + QLatin1String(" (");
+    const auto& data = new_row.getData();
+    QHash<QString, QVariant>::const_iterator i;
+    for (i = data.constBegin(); i != data.constEnd(); ++i) {
+        statement.append(i.key() + QLatin1Char(','));
+    }
+    statement.chop(1);
+    statement += QLatin1String(") VALUES (");
+
+    for (int i=0; i < new_row.getData().size(); ++i) {
+        statement += QLatin1String("?,");
+    }
+    statement.chop(1);
+    statement += QLatin1Char(')');
+
+    QSqlQuery query;
+    query.prepare(statement);
+
+    for (i = data.constBegin(); i != data.constEnd(); ++i) {
+//use QMetaType for binding null value in QT >= 6
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+        if (i.value() == QVariant(QString())) {
+            query.addBindValue(QVariant(QMetaType(QMetaType::Int)));
+#else
+        if (i.value() == QVariant(QString())) {
+            query.addBindValue(QVariant(QVariant::String));
+#endif
+        } else {
+            query.addBindValue(i.value());
+        }
+    }
+
+    //check result.
+    if (query.exec())
+    {
+        LOG << QString("Entry successfully committed. %1").arg(new_row.getPosition());
+        emit dataBaseUpdated(new_row.getTable());
+        return true;
+    } else {
+        DEB << "Unable to commit.";
+        DEB << "Query: " << statement;
+        DEB << "Bound Values: " << query.boundValues();
+        DEB << "Query Error: " << query.lastError().text();
+        lastError = query.lastError();
+        return false;
+    }
+}
+
+OPL::Row Database::getRow(const OPL::DbTable table, const int row_id)
+{
+    QString statement = QLatin1String("SELECT * FROM ") + OPL::GLOBALS->getDbTableName(table)
+            + QLatin1String(" WHERE ROWID=?");
+    QSqlQuery q;
+    q.prepare(statement);
+    q.addBindValue(row_id);
+    q.setForwardOnly(true);
+
+    if (!q.exec()) {
+        DEB << "SQL error: " << q.lastError().text();
+        DEB << "Statement: " << q.lastQuery();
+        lastError = q.lastError();
+        return {}; // return invalid Row
+    }
+
+    RowData_T entry_data;
+    if(q.next()) {
+        auto r = q.record(); // retreive record
+        if (r.count() == 0)  // row is empty
+            return {};
+
+        for (int i = 0; i < r.count(); i++){ // iterate through fields to get key:value map
+            if(!r.value(i).isNull()) {
+                entry_data.insert(r.fieldName(i), r.value(i));
+            }
+        }
+    }
+
+    return OPL::Row(table, row_id, entry_data);
+}
+
+RowData_T Database::getRowData(const OPL::DbTable table, const int row_id)
+{
+    QString statement = QLatin1String("SELECT * FROM ") + OPL::GLOBALS->getDbTableName(table)
+            + QLatin1String(" WHERE ROWID=?");
+    QSqlQuery q;
+    q.prepare(statement);
+    q.addBindValue(row_id);
+    q.setForwardOnly(true);
+
+    if (!q.exec()) {
+        DEB << "SQL error: " << q.lastError().text();
+        DEB << "Statement: " << q.lastQuery();
+        lastError = q.lastError();
+        return {}; // return invalid Row
+    }
+
+    RowData_T entry_data;
+    if(q.next()) {
+        auto r = q.record(); // retreive record
+        if (r.count() == 0)  // row is empty
+            return {};
+
+        for (int i = 0; i < r.count(); i++){ // iterate through fields to get key:value map
+            if(!r.value(i).isNull()) {
+                entry_data.insert(r.fieldName(i), r.value(i));
+            }
+        }
+    }
+
+    return entry_data;
+}
+
+int Database::getLastEntry(OPL::DbTable table)
+{
+    QString statement = QLatin1String("SELECT MAX(ROWID) FROM ") + OPL::GLOBALS->getDbTableName(table);
+
+    auto query = QSqlQuery(statement);
+    if (query.first()) {
+        return query.value(0).toInt();
+    } else {
+        LOG << "No entry found. (Database empty?)" << query.lastError().text();
+        return 0;
+    }
+}
+
+const RowData_T Database::getTotals(bool includePreviousExperience)
+{
+    QString statement = "SELECT"
+        " SUM(tblk) AS tblk,"
+        " SUM(tSPSE) AS tSPSE,"
+        " SUM(tSPME) AS tSPME,"
+        " SUM(tMP) AS tMP,"
+        " SUM(tPIC) AS tPIC,"
+        " SUM(tSIC) AS tSIC,"
+        " SUM(tDUAL) AS tDUAL,"
+        " SUM(tFI) AS tFI,"
+        " SUM(tPICUS) AS tPICUS,"
+        " SUM(tNIGHT) AS tNIGHT,"
+        " SUM(tIFR) AS tIFR,"
+        " SUM(tSIM) AS tSIM,"
+        " SUM(toDay) AS toDay,"
+        " SUM(toNight) AS toNight,"
+        " SUM(ldgDay) AS ldgDay,"
+        " SUM(ldgNight) AS ldgNight"
+        " FROM flights";
+
+    QSqlQuery query;
+    query.prepare(statement);
+    if (!query.exec()) {
+        DEB << "SQL error: " << query.lastError().text();
+        DEB << "Statement: " << query.lastQuery();
+        lastError = query.lastError();
+        return {}; // return invalid Row
+    }
+
+    RowData_T entry_data;
+    if(query.next()) {
+        auto r = query.record(); // retreive record
+        if (r.count() == 0)  // row is empty
+            return {};
+
+        for (int i = 0; i < r.count(); i++){ // iterate through fields to get key:value map
+            if(!r.value(i).isNull()) {
+                entry_data.insert(r.fieldName(i), r.value(i));
+            }
+        }
+    }
+
+    if(!includePreviousExperience) {
+        return entry_data;
+    }
+
+    // name the return types for easy mapping to QLineEdit names
+    statement = "SELECT"
+                " SUM(tblk) AS tblk,"
+                " SUM(tSPSE) AS tSPSE,"
+                " SUM(tSPME) AS tSPME,"
+                " SUM(tMP) AS tMP,"
+                " SUM(tPIC) AS tPIC,"
+                " SUM(tSIC) AS tSIC,"
+                " SUM(tDUAL) AS tDUAL,"
+                " SUM(tFI) AS tFI,"
+                " SUM(tPICUS) AS tPICUS,"
+                " SUM(tNIGHT) AS tNIGHT,"
+                " SUM(tIFR) AS tIFR,"
+                " SUM(tSIM) AS tSIM,"
+                " SUM(toDay) AS toDay,"
+                " SUM(toNight) AS toNight,"
+                " SUM(ldgDay) AS ldgDay,"
+                " SUM(ldgNight) AS ldgNight"
+                " FROM previousExperience";
+    query.prepare(statement);
+
+    if (!query.exec()) {
+        DEB << "SQL error: " << query.lastError().text();
+        DEB << "Statement: " << query.lastQuery();
+        lastError = query.lastError();
+        return {}; // return invalid Row
+    }
+
+    RowData_T prev_exp_data;
+    if(query.next()) {
+        auto r = query.record(); // retreive record
+        if (r.count() == 0)  // row is empty
+            return {};
+
+        for (int i = 0; i < r.count(); i++){ // iterate through fields to get key:value map
+            if(!r.value(i).isNull()) {
+                prev_exp_data.insert(r.fieldName(i), r.value(i));
+            }
+        }
+    }
+
+    // add up the two query results
+    for(auto it = prev_exp_data.begin(); it != prev_exp_data.end(); it++) {
+        int prevXpValue = it.value().toInt();
+        int entryValue = entry_data.value(it.key()).toInt();
+
+        const QVariant sum = prevXpValue + entryValue;
+        entry_data.insert(it.key(), sum);
+    }
+
+    return entry_data;
+}
+
+QList<int> Database::getForeignKeyConstraints(int foreign_row_id, OPL::DbTable table)
+{
+    QString statement = QLatin1String("SELECT ROWID FROM flights WHERE ");
+
+    switch (table) {
+    case OPL::DbTable::Pilots:
+        statement.append(QLatin1String("pic=?"));
+        break;
+    case OPL::DbTable::Tails:
+        statement.append(QLatin1String("acft=?"));
+        break;
+    default:
+        DEB << "Not a valid target for this function.";
+        return QList<int>();
+        break;
+    }
+
+    QSqlQuery query;
+    query.prepare(statement);
+    query.addBindValue(foreign_row_id);
+    query.exec();
+
+    if (!query.isActive()) {
+        lastError = query.lastError();
+        DEB << "Error";
+        DEB << statement;
+        DEB << query.lastError().text();
+        return QList<int>();
+    }
+
+    QList<int> row_ids;
+    while (query.next()) {
+        row_ids.append(query.value(0).toInt());
+    }
+    return row_ids;
+}
+
+QVector<QVariant> Database::customQuery(QString statement, int return_values)
+{
+    QSqlQuery query(statement);
+    if(!query.exec()) {
+        lastError = query.lastError();
+        DEB << "Query Error: " << lastError.text();
+        return {};
+    }
+
+    if (!query.first()) {
+        LOG << "No result found. Check Query and Error.";
+        DEB << "Error: " << query.lastError().text();
+        DEB << "Statement: " << statement;
+        return QVector<QVariant>();
+    } else {
+        query.first();
+        query.previous();
+        QVector<QVariant> result;
+        while (query.next()) {
+            for (int i = 0; i < return_values ; i++) {
+                result.append(query.value(i));
+            }
+        }
+        lastError = QString();
+        return result;
+    }
+}
+
+QVector<RowData_T> Database::getTable(OPL::DbTable table)
+{
+    const QString query_str = QStringLiteral("SELECT * FROM ") + GLOBALS->getDbTableName(table);
+
+    QSqlQuery q;
+    q.prepare(query_str);
+    q.setForwardOnly(true);
+
+    if (!q.exec()) {
+        LOG << "SQL error: " << q.lastError().text();
+        LOG << "Statement: " << query_str;
+        lastError = q.lastError();
+        return {};
+    }
+
+    QVector<RowData_T> entry_data;
+    while(q.next()) { // iterate through records
+        auto r = q.record();
+        //DEB << r;
+        RowData_T row;
+        for (int i = 0; i < r.count(); i++){
+            if(!r.value(i).isNull()) {
+                row.insert(r.fieldName(i), r.value(i));
+            }
+        }
+        entry_data.append(row);
+    }
+    return entry_data;
+}
+
+bool Database::createBackup(const QString& dest_file)
+{
+    LOG << "Backing up current database to: " << dest_file;
+    Database::disconnect();
+    QFile db_file(QDir::toNativeSeparators(databaseFile.absoluteFilePath()));
+
+    if (!db_file.copy(QDir::toNativeSeparators(dest_file))) {
+        LOG << "Unable to backup old database:" << db_file.errorString();
+        return false;
+    }
+
+    LOG << "Backed up old database as:" << dest_file;
+    Database::connect();
+    emit connectionReset();
+    return true;
+}
+
+bool Database::restoreBackup(const QString& backup_file)
+{
+    Database::disconnect();
+    LOG << "Restoring backup from file:" << backup_file;
+
+    QString databaseFilePath = QDir::toNativeSeparators(databaseFile.absoluteFilePath());
+    DEB << "DB File Path: " << databaseFilePath;
+    QString backupFilePath = QDir::toNativeSeparators(backup_file);
+
+    QFile dbFile(databaseFilePath);
+    if(dbFile.exists())
+        if(!dbFile.remove()) {
+            LOG << dbFile.errorString() << "Unable to remove current db file";
+            return false;
+        }
+
+    QFile backupFile(backupFilePath);
+    if(!backupFile.copy(databaseFilePath)) {
+        LOG << backupFile.errorString() << "Could not copy" << backupFile.fileName() << " to " << databaseFilePath;
+        return false;
+    }
+
+    LOG << "Backup successfully restored!";
+    Database::connect();
+    emit connectionReset();
+    return true;
+}
+
+bool Database::createSchema()
+{
+    // Read Database layout from sql file
+    QFile f(OPL::Assets::DATABASE_SCHEMA);
+    f.open(QIODevice::ReadOnly);
+    QByteArray filedata = f.readAll();
+    // create individual queries for each table/view
+    auto list = filedata.split(';');
+
+    // make sure last empty line in sql file has not been parsed
+    if(list.last() == QByteArray("\n") || list.last() == QByteArray("\r\n"))
+        list.removeLast();
+
+    // Create Tables
+    QSqlQuery q;
+    QVector<QSqlError> errors;
+    for (const auto &query_string : list) {
+        q.prepare(query_string);
+        if (!q.exec()) {
+            errors.append(q.lastError());
+            LOG << "Unable to execute query: ";
+            LOG << q.lastQuery();
+            LOG << q.lastError();
+        }
+    }
+    DB->updateLayout();
+
+    if (errors.isEmpty()) {
+        LOG << "Database succesfully created.";
+        return true;
+    } else {
+        LOG << "Database creation has failed. The following error(s) have ocurred: ";
+        for (const auto &error : std::as_const(errors)) {
+            LOG << error.type() << error.text();
+        }
+        return false;
+    }
+}
+
+bool Database::importTemplateData(bool use_local_ressources)
+{
+    for (const auto& table : DB->getTemplateTables()) {
+        const QString table_name = OPL::GLOBALS->getDbTableName(table);
+
+        //clear table
+        QSqlQuery q;
+        q.prepare(QLatin1String("DELETE FROM ") + table_name);
+        if (!q.exec()) {
+            LOG << "Error clearing tables: " << q.lastError().text();
+            return false;
+        }
+
+        //Prepare data
+        QJsonArray data_to_commit;
+        QString error_message("Error importing data ");
+
+        if (use_local_ressources) {
+            data_to_commit = JsonHelper::readFileToDoc(QLatin1String(":database/templates/")
+                                      + table_name + QLatin1String(".json")).array();
+            error_message.append(QLatin1String(" (ressource) "));
+        } else {
+            const QString file_path = OPL::Paths::filePath(OPL::Paths::Templates,
+                                                           table_name + QLatin1String(".json"));
+            data_to_commit = JsonHelper::readFileToDoc(file_path).array();
+            //data_to_commit = AJson::readFileToDoc(AStandardPaths::directory(
+            //                              AStandardPaths::Templates).absoluteFilePath(
+            //                              table_name + QLatin1String(".json"))).array();
+            error_message.append(QLatin1String(" (downloaded) "));
+        }
+
+        // commit Data from Array
+        if (!DB->commit(data_to_commit, table)) {
+            LOG << error_message;
+            return false;
+        }
+    } // for table_name
+    return true;
+}
+
+bool Database::resetUserData()
+{
+    QSqlQuery query;
+    for (const auto& table : DB->getUserTables()) {
+        query.prepare(QLatin1String("DELETE FROM ") + OPL::GLOBALS->getDbTableName(table));
+        if (!query.exec()) {
+            lastError = query.lastError();
+            return false;
+        }
+    }
+    return true;
+}
+
+} // namespace OPL

+ 379 - 385
src/database/database.h

@@ -1,385 +1,379 @@
-/*
- *openPilotLog - A FOSS Pilot Logbook Application
- *Copyright (C) 2020-2023 Felix Turowsky
- *
- *This program is free software: you can redistribute it and/or modify
- *it under the terms of the GNU General Public License as published by
- *the Free Software Foundation, either version 3 of the License, or
- *(at your option) any later version.
- *
- *This program is distributed in the hope that it will be useful,
- *but WITHOUT ANY WARRANTY; without even the implied warranty of
- *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *GNU General Public License for more details.
- *
- *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 DATABASE_H
-#define DATABASE_H
-
-#include <QPair>
-#include <QHash>
-#include <QString>
-#include <QDir>
-#include <QSqlDatabase>
-#include <QSqlDriver>
-#include <QSqlQuery>
-#include <QSqlError>
-#include <QSqlTableModel>
-#include <QSqlQuery>
-#include <QSqlRecord>
-#include <QSqlField>
-
-#include "src/classes/paths.h"
-#include "src/database/aircraftentry.h"
-#include "src/database/airportentry.h"
-#include "src/database/currencyentry.h"
-#include "src/database/flightentry.h"
-#include "src/database/pilotentry.h"
-#include "src/database/simulatorentry.h"
-#include "src/database/tailentry.h"
-#include "src/opl.h"
-#include "src/database/row.h"
-
-
-
-namespace OPL {
-
-/*!
- * \brief Convenience macro that returns instance of DataBase.
- * Instead of this:
- * OPL::DataBase::getInstance().commit(...)
- * Use this:
- * DB->commit(...)
- */
-#define DB OPL::Database::instance()
-
-/*!
- * \brief The DB class encapsulates the SQL database by providing fast access
- * to hot database data.
- */
-class Database : public QObject {
-
-private:
-    Q_OBJECT
-    Database()
-        : databaseFile(OPL::Paths::databaseFileInfo())
-    {}
-    static Database* self;
-    const QFileInfo databaseFile;
-    QStringList tableNames;
-    QHash<QString, QStringList> tableColumns;
-
-    inline const static QString SQLITE_DRIVER  = QStringLiteral("QSQLITE");
-    inline const static QList<OPL::DbTable> USER_TABLES = {
-        OPL::DbTable::Flights,
-        OPL::DbTable::Pilots,
-        OPL::DbTable::Tails
-    };
-    inline const static QList<OPL::DbTable> TEMPLATE_TABLES = {
-        OPL::DbTable::Aircraft,
-        OPL::DbTable::Airports,
-        OPL::DbTable::Currencies,
-        OPL::DbTable::Changelog,
-    };
-
-
-public:
-    Database(const Database&) = delete;
-    void operator=(const Database&) = delete;
-    static Database* instance();
-
-    /*!
-     * \brief Holds information about the last error that ocurred during
-     * a SQL operation. If the error type is QSqlError::UnknownError, the error is related to data
-     * from the database (entry not found,...), otherwise the error is related to SQL execution. In this
-     * case error.type() provides further information.
-     *
-     * If the error type is QSqlError::NoError, the last executed database query was successful.
-     */
-    QSqlError lastError;
-
-    /*!
-     * \brief Connect to the database and populate database information.
-     */
-    bool connect();
-
-    /*!
-     * \brief closes the database connection.
-     */
-    void disconnect();
-
-    /*!
-     * \brief Updates the member variables tableNames and tableColumns with up-to-date layout information
-     * if the database has been altered. This function is normally only required during database setup or maintenance.
-     */
-    void updateLayout();
-
-    /*!
-     * \brief Return the database revision number (not the sqlite version number).
-     */
-    const QString version() const;
-
-    /*!
-     * \brief Database::sqliteVersion returns the database sqlite version. See also dbRevision()
-     * \return sqlite version string
-     */
-    const QString sqliteVersion() const;
-
-    /*!
-     * \brief Return the names of all tables in the database
-     */
-    const QStringList getTableNames() const;
-
-    /*!
-     * \brief Return the names of a given table in the database.
-     */
-    const QStringList getTableColumns(OPL::DbTable table_name) const;
-
-    /*!
-     * \brief Can be used to access the database connection.
-     * \return The QSqlDatabase object pertaining to the connection.
-     */
-    static QSqlDatabase database();
-
-    /*!
-     * \brief Can be used to send a complex query to the database.
-     * \param query - the full sql query statement
-     * \param returnValues - the number of return values
-     */
-    QVector<QVariant> customQuery(QString statement, int return_values);
-
-    /*!
-     * \brief Checks if an entry exists in the database, based on position data
-     */
-    bool exists(const OPL::Row &row);
-
-    /*!
-     * \brief clear resets the database, i.e. deletes all content in the tables containing
-     * userdata (pilots, flights, tails)
-     */
-    bool clear();
-
-    /*!
-     * \brief commits an entry to the database, calls either insert or update,
-     * based on position data
-     */
-    bool commit(const OPL::Row &row);
-
-    /*!
-     * \brief commits data imported from JSON
-     * \details This function is used to import values to the databases which are held in JSON documents.
-     * These entries are pre-filled data used for providing completion data, such as Airport or Aircraft Type Data.
-     */
-    bool commit(const QJsonArray &json_arr, const OPL::DbTable table);
-
-    /*!
-     * \brief Create new entry in the databse based on UserInput
-     */
-    bool insert(const OPL::Row &new_row);
-
-    /*!
-     * \brief Updates entry in database from existing entry tweaked by the user.
-     */
-    bool update(const OPL::Row &updated_row);
-
-    /*!
-     * \brief deletes an entry from the database.
-     */
-    bool remove(const OPL::Row &row);
-
-    /*!
-     * \brief deletes a batch of entries from the database. Optimised for speed when
-     * deleting many entries. The entries are identified using their row id
-     */
-    bool removeMany(OPL::DbTable table, const QList<int> &row_id_list);
-
-    /*!
-     * \brief retreive a Row from the database
-     */
-    OPL::Row getRow(const OPL::DbTable table, const int row_id);
-
-    /*!
-     * \brief retreive a Map of <column name, column content> for a specific row in the database.
-     */
-    RowData_T getRowData(const OPL::DbTable table, const int row_id);
-
-    /*!
-     * \brief retreives a PilotEntry from the database. See row class for details.
-     */
-    inline OPL::PilotEntry getPilotEntry(int row_id)
-    {
-        const auto data = getRowData(OPL::DbTable::Pilots, row_id);
-        return OPL::PilotEntry(row_id, data);
-    }
-
-    /*!
-     * \brief get the database entry for the logbook owner (self)
-     */
-    inline OPL::PilotEntry getLogbookOwner()
-    {
-        auto data = getRowData(OPL::DbTable::Pilots, 1);
-        data.insert(OPL::PilotEntry::ROWID, 1);
-        return OPL::PilotEntry(1, data);
-    }
-
-    /*!
-     * \brief Set the database entry for the logbook owner (self)
-     */
-    inline bool setLogbookOwner(RowData_T &ownerData)
-    {
-        if(ownerData.value(OPL::PilotEntry::LASTNAME).isNull()) {
-            lastError = QSqlError("Logbook owners last name is mandatory.");
-            return false;
-        }
-
-        ownerData.insert(OPL::PilotEntry::ROWID, 1);
-        return commit(OPL::PilotEntry(1, ownerData));
-    }
-
-    /*!
-     * \brief retreives a TailEntry from the database. See row class for details.
-     */
-    inline OPL::TailEntry getTailEntry(int row_id)
-    {
-        const auto data = getRowData(OPL::DbTable::Tails, row_id);
-        return OPL::TailEntry(row_id, data);
-    }
-
-    /*!
-     * \brief retreives a TailEntry from the database. See row class for details.
-     */
-    inline OPL::AircraftEntry getAircraftEntry(int row_id)
-    {
-        const auto data = getRowData(OPL::DbTable::Aircraft, row_id);
-        return OPL::AircraftEntry(row_id, data);
-    }
-
-    /*!
-     * \brief retreives a flight entry from the database. See row class for details.
-     */
-    inline OPL::FlightEntry getFlightEntry(int row_id)
-    {
-        const auto data = getRowData(OPL::DbTable::Flights, row_id);
-        return OPL::FlightEntry(row_id, data);
-    }
-
-    /*!
-     * \brief retreives a Simulator entry from the database. See row class for details.
-     */
-    inline OPL::SimulatorEntry getSimEntry(int row_id)
-    {
-        const auto data = getRowData(OPL::DbTable::Simulators, row_id);
-        return OPL::SimulatorEntry(row_id, data);
-    }
-
-    /*!
-     * \brief Retreives a currency entry from the database. See row class for details.
-     */
-    inline OPL::CurrencyEntry getCurrencyEntry(OPL::CurrencyEntry::Currency currency)
-    {
-        const auto data = getRowData(OPL::DbTable::Currencies, currency);
-        return OPL::CurrencyEntry(currency, data);
-    }
-
-    /*!
-     * \brief Retreives an airport entry from the database. See row class for details.
-     */
-    inline OPL::AirportEntry getAirportEntry(int row_id)
-    {
-        const auto data = getRowData(OPL::DbTable::Airports, row_id);
-        return OPL::AirportEntry(row_id, data);
-    }
-
-    /*!
-     * \brief returns the ROWID for the newest entry in the respective table.
-     */
-    int getLastEntry(OPL::DbTable table);
-
-    /*!
-     * \brief returns a list of ROWID's in the flights table for which foreign key constraints
-     * exist.
-     */
-    QList<int> getForeignKeyConstraints(int foreign_row_id, OPL::DbTable table);
-
-    /*!
-     * \brief getTable returns all contents of a given table from the database
-     * \return
-     */
-    QVector<RowData_T> getTable(OPL::DbTable table);
-
-    /*!
-     * \brief getUserTables returns a list of the of the tables that contain user-created data
-     * (flights, pilots,..)
-     */
-    const QList<OPL::DbTable> &getUserTables() const;
-
-    /*!
-     * \brief getTemplateTables returns a list of the tables that contain template data
-     * (aiports, aircraft,..)
-     */
-    const QList<OPL::DbTable> &getTemplateTables() const;
-
-    // Maintenance and setup
-
-    /*!
-     * \brief Create or restore the database to its ready-to-use but empty state
-     * \details The SQL code for the database creation is stored in a .sql file which is available as a ressource.
-     * This file gets read, and the querys executed. If errors occur, returns false.
-     */
-    bool createSchema();
-    /*!
-     * \brief importTemplateData fills an empty database with the template
-     * data (Aircraft, Airports, currencies, changelog) as read from the JSON
-     * templates.
-     * \param use_local_ressources determines whether the included ressource files
-     * or a previously downloaded file should be used.
-     * \return
-     */
-    bool importTemplateData(bool use_local_ressources);
-
-    /*!
-     * \brief Delete all rows from the user data tables (flights, pliots, tails)
-     */
-    bool resetUserData();
-
-    /*!
-     * \brief Database::createBackup copies the currently used database to an external backup location provided by the user
-     * \param dest_file This is the full path and filename of where the backup will be created, e.g. 'home/Sully/myBackups/backupFromOpl.db'
-     */
-    bool createBackup(const QString& dest_file);
-
-    /*!
-     * \brief Database::restoreBackup restores the database from a given backup file and replaces the currently active database.
-     * \param backup_file This is the full path and filename of the backup, e.g. 'home/Sully/myBackups/backupFromOpl.db'
-     */
-    bool restoreBackup(const QString& backup_file);
-
-
-
-    /*!
-     * @brief Retreive the total time of all flight entries in the databas
-     * @param includePreviousExperience determines whether experience from previous logbooks
-     * is included.
-     * @return The sum of all entries in the flights table
-     */
-    const RowData_T getTotals(bool includePreviousExperience);
-signals:
-    /*!
-     * \brief updated is emitted whenever the database contents have been updated.
-     * This can be either a commit, update or remove. This signal should be used to
-     * 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 OPL::DbTable table);
-    /*!
-     * \brief connectionReset is emitted whenever the database connection is reset, for
-     * example when creating or restoring a backup.
-     */
-    void connectionReset();
-};
-
-} // namespace OPL
-
-#endif // DATABASE_H
+/*
+ *openPilotLog - A FOSS Pilot Logbook Application
+ *Copyright (C) 2020-2023 Felix Turowsky
+ *
+ *This program is free software: you can redistribute it and/or modify
+ *it under the terms of the GNU General Public License as published by
+ *the Free Software Foundation, either version 3 of the License, or
+ *(at your option) any later version.
+ *
+ *This program is distributed in the hope that it will be useful,
+ *but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *GNU General Public License for more details.
+ *
+ *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 DATABASE_H
+#define DATABASE_H
+
+#include <QPair>
+#include <QHash>
+#include <QString>
+#include <QDir>
+#include <QSqlDatabase>
+#include <QSqlDriver>
+#include <QSqlQuery>
+#include <QSqlError>
+#include <QSqlTableModel>
+#include <QSqlQuery>
+#include <QSqlRecord>
+#include <QSqlField>
+
+#include "src/classes/paths.h"
+#include "src/database/aircraftentry.h"
+#include "src/database/airportentry.h"
+#include "src/database/currencyentry.h"
+#include "src/database/flightentry.h"
+#include "src/database/pilotentry.h"
+#include "src/database/simulatorentry.h"
+#include "src/database/tailentry.h"
+#include "src/opl.h"
+#include "src/database/row.h"
+
+
+
+namespace OPL {
+
+/*!
+ * \brief Convenience macro that returns instance of DataBase.
+ * Instead of this:
+ * OPL::DataBase::getInstance().commit(...)
+ * Use this:
+ * DB->commit(...)
+ */
+#define DB OPL::Database::instance()
+
+/*!
+ * \brief The DB class encapsulates the SQL database by providing fast access
+ * to hot database data.
+ */
+class Database : public QObject {
+
+private:
+    Q_OBJECT
+    Database()
+        : databaseFile(OPL::Paths::databaseFileInfo())
+    {}
+    static Database* self;
+    const QFileInfo databaseFile;
+    QStringList tableNames;
+    QHash<QString, QStringList> tableColumns;
+
+    inline const static QString SQLITE_DRIVER  = QStringLiteral("QSQLITE");
+    inline const static QList<OPL::DbTable> USER_TABLES = {
+        OPL::DbTable::Flights,
+        OPL::DbTable::Pilots,
+        OPL::DbTable::Tails,
+        OPL::DbTable::Currencies,
+    };
+    inline const static QList<OPL::DbTable> TEMPLATE_TABLES = {
+        OPL::DbTable::Aircraft,
+        OPL::DbTable::Airports,
+    };
+
+
+public:
+    Database(const Database&) = delete;
+    void operator=(const Database&) = delete;
+    static Database* instance();
+
+    /*!
+     * \brief Holds information about the last error that ocurred during
+     * a SQL operation. If the error type is QSqlError::UnknownError, the error is related to data
+     * from the database (entry not found,...), otherwise the error is related to SQL execution. In this
+     * case error.type() provides further information.
+     *
+     * If the error type is QSqlError::NoError, the last executed database query was successful.
+     */
+    QSqlError lastError;
+
+    /*!
+     * \brief Connect to the database and populate database information.
+     */
+    bool connect();
+
+    /*!
+     * \brief closes the database connection.
+     */
+    void disconnect();
+
+    /*!
+     * \brief Updates the member variables tableNames and tableColumns with up-to-date layout information
+     * if the database has been altered. This function is normally only required during database setup or maintenance.
+     */
+    void updateLayout();
+
+    /*!
+     * \brief Database::sqliteVersion returns the database sqlite version. See also dbRevision()
+     * \return sqlite version string
+     */
+    const QString sqliteVersion() const;
+
+    /*!
+     * \brief Return the names of all tables in the database
+     */
+    const QStringList getTableNames() const;
+
+    /*!
+     * \brief Return the names of a given table in the database.
+     */
+    const QStringList getTableColumns(OPL::DbTable table_name) const;
+
+    /*!
+     * \brief Can be used to access the database connection.
+     * \return The QSqlDatabase object pertaining to the connection.
+     */
+    static QSqlDatabase database();
+
+    /*!
+     * \brief Can be used to send a complex query to the database.
+     * \param query - the full sql query statement
+     * \param returnValues - the number of return values
+     */
+    QVector<QVariant> customQuery(QString statement, int return_values);
+
+    /*!
+     * \brief Checks if an entry exists in the database, based on position data
+     */
+    bool exists(const OPL::Row &row);
+
+    /*!
+     * \brief clear resets the database, i.e. deletes all content in the tables containing
+     * userdata (pilots, flights, tails)
+     */
+    bool clear();
+
+    /*!
+     * \brief commits an entry to the database, calls either insert or update,
+     * based on position data
+     */
+    bool commit(const OPL::Row &row);
+
+    /*!
+     * \brief commits data imported from JSON
+     * \details This function is used to import values to the databases which are held in JSON documents.
+     * These entries are pre-filled data used for providing completion data, such as Airport or Aircraft Type Data.
+     */
+    bool commit(const QJsonArray &json_arr, const OPL::DbTable table);
+
+    /*!
+     * \brief Create new entry in the databse based on UserInput
+     */
+    bool insert(const OPL::Row &new_row);
+
+    /*!
+     * \brief Updates entry in database from existing entry tweaked by the user.
+     */
+    bool update(const OPL::Row &updated_row);
+
+    /*!
+     * \brief deletes an entry from the database.
+     */
+    bool remove(const OPL::Row &row);
+
+    /*!
+     * \brief deletes a batch of entries from the database. Optimised for speed when
+     * deleting many entries. The entries are identified using their row id
+     */
+    bool removeMany(OPL::DbTable table, const QList<int> &row_id_list);
+
+    /*!
+     * \brief retreive a Row from the database
+     */
+    OPL::Row getRow(const OPL::DbTable table, const int row_id);
+
+    /*!
+     * \brief retreive a Map of <column name, column content> for a specific row in the database.
+     */
+    RowData_T getRowData(const OPL::DbTable table, const int row_id);
+
+    /*!
+     * \brief retreives a PilotEntry from the database. See row class for details.
+     */
+    inline OPL::PilotEntry getPilotEntry(int row_id)
+    {
+        const auto data = getRowData(OPL::DbTable::Pilots, row_id);
+        return OPL::PilotEntry(row_id, data);
+    }
+
+    /*!
+     * \brief get the database entry for the logbook owner (self)
+     */
+    inline OPL::PilotEntry getLogbookOwner()
+    {
+        auto data = getRowData(OPL::DbTable::Pilots, 1);
+        data.insert(OPL::PilotEntry::ROWID, 1);
+        return OPL::PilotEntry(1, data);
+    }
+
+    /*!
+     * \brief Set the database entry for the logbook owner (self)
+     */
+    inline bool setLogbookOwner(RowData_T &ownerData)
+    {
+        if(ownerData.value(OPL::PilotEntry::LASTNAME).isNull()) {
+            lastError = QSqlError("Logbook owners last name is mandatory.");
+            return false;
+        }
+
+        ownerData.insert(OPL::PilotEntry::ROWID, 1);
+        return commit(OPL::PilotEntry(1, ownerData));
+    }
+
+    /*!
+     * \brief retreives a TailEntry from the database. See row class for details.
+     */
+    inline OPL::TailEntry getTailEntry(int row_id)
+    {
+        const auto data = getRowData(OPL::DbTable::Tails, row_id);
+        return OPL::TailEntry(row_id, data);
+    }
+
+    /*!
+     * \brief retreives a TailEntry from the database. See row class for details.
+     */
+    inline OPL::AircraftEntry getAircraftEntry(int row_id)
+    {
+        const auto data = getRowData(OPL::DbTable::Aircraft, row_id);
+        return OPL::AircraftEntry(row_id, data);
+    }
+
+    /*!
+     * \brief retreives a flight entry from the database. See row class for details.
+     */
+    inline OPL::FlightEntry getFlightEntry(int row_id)
+    {
+        const auto data = getRowData(OPL::DbTable::Flights, row_id);
+        return OPL::FlightEntry(row_id, data);
+    }
+
+    /*!
+     * \brief retreives a Simulator entry from the database. See row class for details.
+     */
+    inline OPL::SimulatorEntry getSimEntry(int row_id)
+    {
+        const auto data = getRowData(OPL::DbTable::Simulators, row_id);
+        return OPL::SimulatorEntry(row_id, data);
+    }
+
+    /*!
+     * \brief Retreives a currency entry from the database. See row class for details.
+     */
+    inline OPL::CurrencyEntry getCurrencyEntry(OPL::CurrencyEntry::Currency currency)
+    {
+        const auto data = getRowData(OPL::DbTable::Currencies, currency);
+        return OPL::CurrencyEntry(currency, data);
+    }
+
+    /*!
+     * \brief Retreives an airport entry from the database. See row class for details.
+     */
+    inline OPL::AirportEntry getAirportEntry(int row_id)
+    {
+        const auto data = getRowData(OPL::DbTable::Airports, row_id);
+        return OPL::AirportEntry(row_id, data);
+    }
+
+    /*!
+     * \brief returns the ROWID for the newest entry in the respective table.
+     */
+    int getLastEntry(OPL::DbTable table);
+
+    /*!
+     * \brief returns a list of ROWID's in the flights table for which foreign key constraints
+     * exist.
+     */
+    QList<int> getForeignKeyConstraints(int foreign_row_id, OPL::DbTable table);
+
+    /*!
+     * \brief getTable returns all contents of a given table from the database
+     * \return
+     */
+    QVector<RowData_T> getTable(OPL::DbTable table);
+
+    /*!
+     * \brief getUserTables returns a list of the of the tables that contain user-created data
+     * (flights, pilots,..)
+     */
+    const QList<OPL::DbTable> &getUserTables() const;
+
+    /*!
+     * \brief getTemplateTables returns a list of the tables that contain template data
+     * (aiports, aircraft,..)
+     */
+    const QList<OPL::DbTable> &getTemplateTables() const;
+
+    // Maintenance and setup
+
+    /*!
+     * \brief Create or restore the database to its ready-to-use but empty state
+     * \details The SQL code for the database creation is stored in a .sql file which is available as a ressource.
+     * This file gets read, and the querys executed. If errors occur, returns false.
+     */
+    bool createSchema();
+    /*!
+     * \brief importTemplateData fills an empty database with the template
+     * data (Aircraft, Airports) as read from the JSON
+     * templates.
+     * \param use_local_ressources determines whether the included ressource files
+     * or a previously downloaded file should be used.
+     * \return
+     */
+    bool importTemplateData(bool use_local_ressources);
+
+    /*!
+     * \brief Delete all rows from the user data tables (flights, pliots, tails)
+     */
+    bool resetUserData();
+
+    /*!
+     * \brief Database::createBackup copies the currently used database to an external backup location provided by the user
+     * \param dest_file This is the full path and filename of where the backup will be created, e.g. 'home/Sully/myBackups/backupFromOpl.db'
+     */
+    bool createBackup(const QString& dest_file);
+
+    /*!
+     * \brief Database::restoreBackup restores the database from a given backup file and replaces the currently active database.
+     * \param backup_file This is the full path and filename of the backup, e.g. 'home/Sully/myBackups/backupFromOpl.db'
+     */
+    bool restoreBackup(const QString& backup_file);
+
+
+
+    /*!
+     * @brief Retreive the total time of all flight entries in the databas
+     * @param includePreviousExperience determines whether experience from previous logbooks
+     * is included.
+     * @return The sum of all entries in the flights table
+     */
+    const RowData_T getTotals(bool includePreviousExperience);
+signals:
+    /*!
+     * \brief updated is emitted whenever the database contents have been updated.
+     * This can be either a commit, update or remove. This signal should be used to
+     * 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 OPL::DbTable table);
+    /*!
+     * \brief connectionReset is emitted whenever the database connection is reset, for
+     * example when creating or restoring a backup.
+     */
+    void connectionReset();
+};
+
+} // namespace OPL
+
+#endif // DATABASE_H

+ 1 - 1
src/database/views/logbookviewinfo.h

@@ -44,7 +44,7 @@ public:
     /*!
      * \brief Return the column(s) in the view which contain Time entries
      */
-    static constexpr std::vector<int> getTimeColumns(LogbookView view)
+    static const std::vector<int> getTimeColumns(LogbookView view)
     {
         switch (view) {
         case LogbookView::Default:

+ 311 - 311
src/gui/dialogues/newtaildialog.cpp

@@ -1,311 +1,311 @@
-/*
- *openPilotLog - A FOSS Pilot Logbook Application
- *Copyright (C) 2020-2023 Felix Turowsky
- *
- *This program is free software: you can redistribute it and/or modify
- *it under the terms of the GNU General Public License as published by
- *the Free Software Foundation, either version 3 of the License, or
- *(at your option) any later version.
- *
- *This program is distributed in the hope that it will be useful,
- *but WITHOUT ANY WARRANTY; without even the implied warranty of
- *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *GNU General Public License for more details.
- *
- *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 "newtaildialog.h"
-#include "src/database/database.h"
-#include "src/database/databasecache.h"
-#include "ui_newtail.h"
-#include "src/opl.h"
-
-NewTailDialog::NewTailDialog(const QString &new_registration, QWidget *parent) :
-    EntryEditDialog(parent),
-    ui(new Ui::NewTail)
-{
-    ui->setupUi(this);
-
-    setupCompleter();
-    setupValidators();
-
-    ui->registrationLineEdit->setText(new_registration);
-    ui->searchLineEdit->setStyleSheet(QStringLiteral("border: 1px solid blue"));
-    ui->searchLineEdit->setFocus();
-
-    //entry = OPL::TailEntry();
-}
-
-NewTailDialog::NewTailDialog(int row_id, QWidget *parent) :
-    EntryEditDialog(parent),
-    ui(new Ui::NewTail)
-{
-    ui->setupUi(this);
-
-    ui->searchLabel->hide();
-    ui->searchLineEdit->hide();
-    ui->line->hide();
-
-    setupValidators();
-    entry = DB->getTailEntry(row_id);
-    LOG << "Editing: " << entry;
-    fillForm(entry, false);
-}
-
-NewTailDialog::~NewTailDialog()
-{
-    delete ui;
-}
-
-/*!
- * \brief NewTail::setupCompleter obtains a QHash<QString searchstring, int aircaft_id> for auto completion
- * and obtains a QStringList for QCompleter. This function then sets up the search line edit where
- * the user can select a template from the aircraft database to pre-fill the form with the details
- * for the selected type.
- */
-void NewTailDialog::setupCompleter()
-{
-    idMap = DBCache->getAircraftMap();
-    aircraftList = DBCache->getAircraftList();
-
-    QCompleter *completer = new QCompleter(aircraftList, ui->searchLineEdit);
-    completer->setCaseSensitivity(Qt::CaseInsensitive);
-    completer->setCompletionMode(QCompleter::PopupCompletion);
-    completer->setFilterMode(Qt::MatchContains);
-    ui->searchLineEdit->setCompleter(completer);
-
-    QObject::connect(completer, static_cast<void(QCompleter::*)(const QString &)>(&QCompleter::activated),
-                     this, &NewTailDialog::onSearchCompleterActivated);
-    QObject::connect(completer, static_cast<void(QCompleter::*)(const QString &)>(&QCompleter::highlighted),
-                     this, &NewTailDialog::onSearchCompleterActivated);
-}
-
-void NewTailDialog::setupValidators()
-{
-    const QHash<QLatin1String, QRegularExpression> line_edit_validators = {
-        {QLatin1String("registrationLineEdit"), QRegularExpression(QLatin1String("\\w+-\\w+"))},
-        {QLatin1String("makeLineEdit"),         QRegularExpression(QLatin1String("[-a-zA-Z\\s]+"))},
-        {QLatin1String("modelLineEdit"),        QRegularExpression(QLatin1String("[\\s\\w-]+"))},
-        {QLatin1String("variantLineEdit"),      QRegularExpression(QLatin1String("[\\s\\w-]+"))},
-    };
-
-    QHash<QLatin1String, QRegularExpression>::const_iterator i;
-    for (i = line_edit_validators.constBegin(); i != line_edit_validators.constEnd(); ++i) {
-        const auto line_edit = this->findChild<QLineEdit*>(i.key());
-        auto validator = new QRegularExpressionValidator(i.value(), line_edit);
-        line_edit->setValidator(validator);
-    }
-}
-
-/*!
- * \brief NewTailDialog::fillForm populates the Dialog with the
- * information contained in an entry object. This can be either
- * a template (AircraftEntry, used when creating a new entry) or
- * a tail (TailEntry, used when editing an existing entry)
- * \param is_template - determines whether we are adding a new entry
- * or editing an existing one.
- */
-void NewTailDialog::fillForm(OPL::Row entry, bool is_template)
-{
-    DEB << "Filling Form for a/c" << entry;
-    //fill Line Edits
-    auto line_edits = this->findChildren<QLineEdit *>();
-
-    if (is_template)
-        line_edits.removeOne(ui->registrationLineEdit);
-
-    auto data = entry.getData();
-
-    for (const auto &le : qAsConst(line_edits)) {
-        auto key = le->objectName().remove(QStringLiteral("LineEdit"));
-        le->setText(data.value(key).toString());
-    }
-
-    ui->operationComboBox->setCurrentIndex(data.value(OPL::TailEntry::MULTI_PILOT).toInt() + 1);
-    ui->ppNumberComboBox ->setCurrentIndex(data.value(OPL::TailEntry::MULTI_ENGINE).toInt() + 1);
-    ui->ppTypeComboBox->setCurrentIndex(data.value(OPL::TailEntry::ENGINE_TYPE).toInt() + 1);
-    ui->weightComboBox->setCurrentIndex(data.value(OPL::TailEntry::WEIGHT_CLASS).toInt() + 1);
-}
-
-/*!
- * \brief NewTail::verify A simple check for empty recommended fields in the form
- * \return true if all reconmmended fields are populated
- */
-bool NewTailDialog::verify()
-{
-    auto recommended_line_edits = this->findChildren<QLineEdit *>(QStringLiteral("registrationLineEdit"));
-    recommended_line_edits.append(this->findChild<QLineEdit *>(QStringLiteral("makeLineEdit")));
-    recommended_line_edits.append(this->findChild<QLineEdit *>(QStringLiteral("modelLineEdit")));
-
-    auto recommended_combo_boxes = this->findChildren<QComboBox *>(QStringLiteral("operationComboBox"));
-    recommended_combo_boxes.append(this->findChild<QComboBox *>(QStringLiteral("ppNumberComboBox")));
-    recommended_combo_boxes.append(this->findChild<QComboBox *>(QStringLiteral("ppTypeComboBox")));
-
-    for (const auto &le : qAsConst(recommended_line_edits)) {
-        if (le->text() != "") {
-            DEB << "Good: " << le;
-            recommended_line_edits.removeOne(le);
-            le->setStyleSheet("");
-        } else {
-            le->setStyleSheet(QStringLiteral("border: 1px solid red"));
-            DEB << "Not Good: " << le;
-        }
-    }
-    for (const auto &cb : qAsConst(recommended_combo_boxes)) {
-        if (cb->currentIndex() != 0) {
-
-            recommended_combo_boxes.removeOne(cb);
-            cb->setStyleSheet(QString());
-        } else {
-            cb->setStyleSheet(QStringLiteral("background: orange"));
-            DEB << "Not Good: " << cb;
-        }
-    }
-
-    if (recommended_line_edits.isEmpty() && recommended_combo_boxes.isEmpty()) {
-        return true;
-    } else {
-        return false;
-    }
-}
-/*!
- * \brief NewTail::submitForm collects input from Line Edits and creates
- * or updates a database entry and commits or updates the database
- * \param edRole editExisting or createNew
- */
-void NewTailDialog::submitForm()
-{
-    OPL::RowData_T new_data;
-    //retreive Line Edits
-    auto line_edits = this->findChildren<QLineEdit *>();
-    line_edits.removeOne(this->findChild<QLineEdit *>(QStringLiteral("searchLineEdit")));
-
-    for (const auto &le : qAsConst(line_edits)) {
-        auto key = le->objectName().remove(QStringLiteral("LineEdit"));
-        new_data.insert(key, le->text());
-    }
-
-    if (ui->operationComboBox->currentIndex() != 0) { // bool Multipilot
-        new_data.insert(OPL::TailEntry::MULTI_PILOT, ui->operationComboBox->currentIndex() - 1);
-    }
-    if (ui->ppNumberComboBox->currentIndex() != 0) { // bool MultiEngine
-        new_data.insert(OPL::TailEntry::MULTI_ENGINE, ui->ppNumberComboBox->currentIndex() - 1);
-    }
-    if (ui->ppTypeComboBox->currentIndex() != 0) { // int 0=unpowered,....4=jet
-        new_data.insert(OPL::TailEntry::ENGINE_TYPE, ui->ppTypeComboBox->currentIndex() - 1);
-    }
-    if (ui->weightComboBox->currentIndex() != 0) { // int 0=light...3=super
-        new_data.insert(OPL::TailEntry::WEIGHT_CLASS, ui->weightComboBox->currentIndex() - 1);
-    }
-
-    //create db object
-
-    entry.setData(new_data);
-
-    // add type string
-    auto data = entry.getData();
-    data.insert(OPL::TailEntry::TYPE_STRING, entry.type());
-    entry.setData(data);
-
-    LOG << "Commiting: " << entry;
-    if (!DB->commit(entry)) {
-        QMessageBox message_box(this);
-        message_box.setText(tr("The following error has ocurred:"
-                               "<br><br>%1<br><br>"
-                               "The entry has not been saved."
-                               ).arg(DB->lastError.text()));
-        message_box.exec();
-        return;
-    } else {
-        emit tailDataChanged();
-        QDialog::accept();
-    }
-}
-
-/// Slots
-
-void NewTailDialog::on_operationComboBox_currentIndexChanged(int index)
-{
-    if (index != 0)
-        ui->operationComboBox->setStyleSheet(QString());
-}
-
-void NewTailDialog::on_ppTypeComboBox_currentIndexChanged(int index)
-{
-    if (index != 0)
-        ui->ppTypeComboBox->setStyleSheet(QString());
-}
-
-void NewTailDialog::on_ppNumberComboBox_currentIndexChanged(int index)
-{
-    if (index != 0)
-        ui->ppNumberComboBox->setStyleSheet(QString());
-}
-
-void NewTailDialog::on_weightComboBox_currentIndexChanged(int index)
-{
-    if (index != 0)
-        ui->weightComboBox->setStyleSheet(QString());
-}
-
-void NewTailDialog::on_buttonBox_accepted()
-{
-    DEB << "Button Box Accepted.";
-    if (ui->registrationLineEdit->text().isEmpty()) {
-        QMessageBox message_box(this);
-        message_box.setText(tr("Registration cannot be empty."));
-        message_box.exec();
-        return;
-    }
-
-    if (!verify()) {
-        QMessageBox message_box(this);
-        message_box.setIcon(QMessageBox::Warning);
-        message_box.setText(tr("Some or all recommended fields are empty.<br>"
-                               "Please fill out the mandatory fields. You can use "
-                               "the search function to automatically fill out all "
-                               "the required fields for a known aircraft type."));
-        message_box.exec();
-        return;
-    }
-    submitForm();
-}
-
-void NewTailDialog::onSearchCompleterActivated()
-{
-    const auto &text = ui->searchLineEdit->text();
-    if (aircraftList.contains(text)) {
-
-        DEB << "Template Selected. aircraft_id is: " << idMap.key(text);
-        //call autofiller for dialog
-        fillForm(DB->getAircraftEntry(idMap.key(text)), true);
-        ui->searchLineEdit->setStyleSheet(QStringLiteral("border: 1px solid green"));
-        ui->searchLabel->setText(text);
-    } else {
-        //for example, editing finished without selecting a result from Qcompleter
-        ui->searchLineEdit->setStyleSheet(QStringLiteral("border: 1px solid orange"));
-    }
-}
-
-bool NewTailDialog::deleteEntry(int rowID)
-{
-    auto entry = DB->getTailEntry(rowID);
-    return DB->remove(entry);
-}
-
-void NewTailDialog::loadEntry(int rowId)
-{
-    ui->searchLabel->hide();
-    ui->searchLineEdit->hide();
-    ui->line->hide();
-
-    setupValidators();
-    entry = DB->getTailEntry(rowId);
-    fillForm(entry, false);
-}
-
-void NewTailDialog::on_registrationLineEdit_textChanged(const QString &arg1)
-{
-    ui->registrationLineEdit->setText(arg1.toUpper());
-}
+/*
+ *openPilotLog - A FOSS Pilot Logbook Application
+ *Copyright (C) 2020-2023 Felix Turowsky
+ *
+ *This program is free software: you can redistribute it and/or modify
+ *it under the terms of the GNU General Public License as published by
+ *the Free Software Foundation, either version 3 of the License, or
+ *(at your option) any later version.
+ *
+ *This program is distributed in the hope that it will be useful,
+ *but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *GNU General Public License for more details.
+ *
+ *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 "newtaildialog.h"
+#include "src/database/database.h"
+#include "src/database/databasecache.h"
+#include "ui_newtail.h"
+#include "src/opl.h"
+
+NewTailDialog::NewTailDialog(const QString &new_registration, QWidget *parent) :
+    EntryEditDialog(parent),
+    ui(new Ui::NewTail)
+{
+    ui->setupUi(this);
+
+    setupCompleter();
+    setupValidators();
+
+    ui->registrationLineEdit->setText(new_registration);
+    ui->searchLineEdit->setStyleSheet(QStringLiteral("border: 1px solid blue"));
+    ui->searchLineEdit->setFocus();
+
+    //entry = OPL::TailEntry();
+}
+
+NewTailDialog::NewTailDialog(int row_id, QWidget *parent) :
+    EntryEditDialog(parent),
+    ui(new Ui::NewTail)
+{
+    ui->setupUi(this);
+
+    ui->searchLabel->hide();
+    ui->searchLineEdit->hide();
+    ui->line->hide();
+
+    setupValidators();
+    entry = DB->getTailEntry(row_id);
+    LOG << "Editing: " << entry;
+    fillForm(entry, false);
+}
+
+NewTailDialog::~NewTailDialog()
+{
+    delete ui;
+}
+
+/*!
+ * \brief NewTail::setupCompleter obtains a QHash<QString searchstring, int aircaft_id> for auto completion
+ * and obtains a QStringList for QCompleter. This function then sets up the search line edit where
+ * the user can select a template from the aircraft database to pre-fill the form with the details
+ * for the selected type.
+ */
+void NewTailDialog::setupCompleter()
+{
+    idMap = DBCache->getAircraftMap();
+    aircraftList = DBCache->getAircraftList();
+
+    QCompleter *completer = new QCompleter(aircraftList, ui->searchLineEdit);
+    completer->setCaseSensitivity(Qt::CaseInsensitive);
+    completer->setCompletionMode(QCompleter::PopupCompletion);
+    completer->setFilterMode(Qt::MatchContains);
+    ui->searchLineEdit->setCompleter(completer);
+
+    QObject::connect(completer, static_cast<void(QCompleter::*)(const QString &)>(&QCompleter::activated),
+                     this, &NewTailDialog::onSearchCompleterActivated);
+    QObject::connect(completer, static_cast<void(QCompleter::*)(const QString &)>(&QCompleter::highlighted),
+                     this, &NewTailDialog::onSearchCompleterActivated);
+}
+
+void NewTailDialog::setupValidators()
+{
+    const QHash<QLatin1String, QRegularExpression> line_edit_validators = {
+        {QLatin1String("registrationLineEdit"), QRegularExpression(QLatin1String("\\w+-\\w+"))},
+        {QLatin1String("makeLineEdit"),         QRegularExpression(QLatin1String("[-a-zA-Z\\s]+"))},
+        {QLatin1String("modelLineEdit"),        QRegularExpression(QLatin1String("[\\s\\w-]+"))},
+        {QLatin1String("variantLineEdit"),      QRegularExpression(QLatin1String("[\\s\\w-]+"))},
+    };
+
+    QHash<QLatin1String, QRegularExpression>::const_iterator i;
+    for (i = line_edit_validators.constBegin(); i != line_edit_validators.constEnd(); ++i) {
+        const auto line_edit = this->findChild<QLineEdit*>(i.key());
+        auto validator = new QRegularExpressionValidator(i.value(), line_edit);
+        line_edit->setValidator(validator);
+    }
+}
+
+/*!
+ * \brief NewTailDialog::fillForm populates the Dialog with the
+ * information contained in an entry object. This can be either
+ * a template (AircraftEntry, used when creating a new entry) or
+ * a tail (TailEntry, used when editing an existing entry)
+ * \param is_template - determines whether we are adding a new entry
+ * or editing an existing one.
+ */
+void NewTailDialog::fillForm(OPL::Row entry, bool is_template)
+{
+    DEB << "Filling Form for a/c" << entry;
+    //fill Line Edits
+    auto line_edits = this->findChildren<QLineEdit *>();
+
+    if (is_template)
+        line_edits.removeOne(ui->registrationLineEdit);
+
+    auto data = entry.getData();
+
+    for (const auto &le : std::as_const(line_edits)) {
+        auto key = le->objectName().remove(QStringLiteral("LineEdit"));
+        le->setText(data.value(key).toString());
+    }
+
+    ui->operationComboBox->setCurrentIndex(data.value(OPL::TailEntry::MULTI_PILOT).toInt() + 1);
+    ui->ppNumberComboBox ->setCurrentIndex(data.value(OPL::TailEntry::MULTI_ENGINE).toInt() + 1);
+    ui->ppTypeComboBox->setCurrentIndex(data.value(OPL::TailEntry::ENGINE_TYPE).toInt() + 1);
+    ui->weightComboBox->setCurrentIndex(data.value(OPL::TailEntry::WEIGHT_CLASS).toInt() + 1);
+}
+
+/*!
+ * \brief NewTail::verify A simple check for empty recommended fields in the form
+ * \return true if all reconmmended fields are populated
+ */
+bool NewTailDialog::verify()
+{
+    auto recommended_line_edits = this->findChildren<QLineEdit *>(QStringLiteral("registrationLineEdit"));
+    recommended_line_edits.append(this->findChild<QLineEdit *>(QStringLiteral("makeLineEdit")));
+    recommended_line_edits.append(this->findChild<QLineEdit *>(QStringLiteral("modelLineEdit")));
+
+    auto recommended_combo_boxes = this->findChildren<QComboBox *>(QStringLiteral("operationComboBox"));
+    recommended_combo_boxes.append(this->findChild<QComboBox *>(QStringLiteral("ppNumberComboBox")));
+    recommended_combo_boxes.append(this->findChild<QComboBox *>(QStringLiteral("ppTypeComboBox")));
+
+    for (const auto &le : std::as_const(recommended_line_edits)) {
+        if (le->text() != "") {
+            DEB << "Good: " << le;
+            recommended_line_edits.removeOne(le);
+            le->setStyleSheet("");
+        } else {
+            le->setStyleSheet(QStringLiteral("border: 1px solid red"));
+            DEB << "Not Good: " << le;
+        }
+    }
+    for (const auto &cb : std::as_const(recommended_combo_boxes)) {
+        if (cb->currentIndex() != 0) {
+
+            recommended_combo_boxes.removeOne(cb);
+            cb->setStyleSheet(QString());
+        } else {
+            cb->setStyleSheet(QStringLiteral("background: orange"));
+            DEB << "Not Good: " << cb;
+        }
+    }
+
+    if (recommended_line_edits.isEmpty() && recommended_combo_boxes.isEmpty()) {
+        return true;
+    } else {
+        return false;
+    }
+}
+/*!
+ * \brief NewTail::submitForm collects input from Line Edits and creates
+ * or updates a database entry and commits or updates the database
+ * \param edRole editExisting or createNew
+ */
+void NewTailDialog::submitForm()
+{
+    OPL::RowData_T new_data;
+    //retreive Line Edits
+    auto line_edits = this->findChildren<QLineEdit *>();
+    line_edits.removeOne(this->findChild<QLineEdit *>(QStringLiteral("searchLineEdit")));
+
+    for (const auto &le : std::as_const(line_edits)) {
+        auto key = le->objectName().remove(QStringLiteral("LineEdit"));
+        new_data.insert(key, le->text());
+    }
+
+    if (ui->operationComboBox->currentIndex() != 0) { // bool Multipilot
+        new_data.insert(OPL::TailEntry::MULTI_PILOT, ui->operationComboBox->currentIndex() - 1);
+    }
+    if (ui->ppNumberComboBox->currentIndex() != 0) { // bool MultiEngine
+        new_data.insert(OPL::TailEntry::MULTI_ENGINE, ui->ppNumberComboBox->currentIndex() - 1);
+    }
+    if (ui->ppTypeComboBox->currentIndex() != 0) { // int 0=unpowered,....4=jet
+        new_data.insert(OPL::TailEntry::ENGINE_TYPE, ui->ppTypeComboBox->currentIndex() - 1);
+    }
+    if (ui->weightComboBox->currentIndex() != 0) { // int 0=light...3=super
+        new_data.insert(OPL::TailEntry::WEIGHT_CLASS, ui->weightComboBox->currentIndex() - 1);
+    }
+
+    //create db object
+
+    entry.setData(new_data);
+
+    // add type string
+    auto data = entry.getData();
+    data.insert(OPL::TailEntry::TYPE_STRING, entry.type());
+    entry.setData(data);
+
+    LOG << "Commiting: " << entry;
+    if (!DB->commit(entry)) {
+        QMessageBox message_box(this);
+        message_box.setText(tr("The following error has ocurred:"
+                               "<br><br>%1<br><br>"
+                               "The entry has not been saved."
+                               ).arg(DB->lastError.text()));
+        message_box.exec();
+        return;
+    } else {
+        emit tailDataChanged();
+        QDialog::accept();
+    }
+}
+
+/// Slots
+
+void NewTailDialog::on_operationComboBox_currentIndexChanged(int index)
+{
+    if (index != 0)
+        ui->operationComboBox->setStyleSheet(QString());
+}
+
+void NewTailDialog::on_ppTypeComboBox_currentIndexChanged(int index)
+{
+    if (index != 0)
+        ui->ppTypeComboBox->setStyleSheet(QString());
+}
+
+void NewTailDialog::on_ppNumberComboBox_currentIndexChanged(int index)
+{
+    if (index != 0)
+        ui->ppNumberComboBox->setStyleSheet(QString());
+}
+
+void NewTailDialog::on_weightComboBox_currentIndexChanged(int index)
+{
+    if (index != 0)
+        ui->weightComboBox->setStyleSheet(QString());
+}
+
+void NewTailDialog::on_buttonBox_accepted()
+{
+    DEB << "Button Box Accepted.";
+    if (ui->registrationLineEdit->text().isEmpty()) {
+        QMessageBox message_box(this);
+        message_box.setText(tr("Registration cannot be empty."));
+        message_box.exec();
+        return;
+    }
+
+    if (!verify()) {
+        QMessageBox message_box(this);
+        message_box.setIcon(QMessageBox::Warning);
+        message_box.setText(tr("Some or all recommended fields are empty.<br>"
+                               "Please fill out the mandatory fields. You can use "
+                               "the search function to automatically fill out all "
+                               "the required fields for a known aircraft type."));
+        message_box.exec();
+        return;
+    }
+    submitForm();
+}
+
+void NewTailDialog::onSearchCompleterActivated()
+{
+    const auto &text = ui->searchLineEdit->text();
+    if (aircraftList.contains(text)) {
+
+        DEB << "Template Selected. aircraft_id is: " << idMap.key(text);
+        //call autofiller for dialog
+        fillForm(DB->getAircraftEntry(idMap.key(text)), true);
+        ui->searchLineEdit->setStyleSheet(QStringLiteral("border: 1px solid green"));
+        ui->searchLabel->setText(text);
+    } else {
+        //for example, editing finished without selecting a result from Qcompleter
+        ui->searchLineEdit->setStyleSheet(QStringLiteral("border: 1px solid orange"));
+    }
+}
+
+bool NewTailDialog::deleteEntry(int rowID)
+{
+    auto entry = DB->getTailEntry(rowID);
+    return DB->remove(entry);
+}
+
+void NewTailDialog::loadEntry(int rowId)
+{
+    ui->searchLabel->hide();
+    ui->searchLineEdit->hide();
+    ui->line->hide();
+
+    setupValidators();
+    entry = DB->getTailEntry(rowId);
+    fillForm(entry, false);
+}
+
+void NewTailDialog::on_registrationLineEdit_textChanged(const QString &arg1)
+{
+    ui->registrationLineEdit->setText(arg1.toUpper());
+}

+ 111 - 111
src/gui/widgets/tailtableeditwidget.cpp

@@ -1,111 +1,111 @@
-#include "tailtableeditwidget.h"
-#include "src/database/database.h"
-#include "src/gui/dialogues/newtaildialog.h"
-
-TailTableEditWidget::TailTableEditWidget(QWidget *parent)
-    : TableEditWidget(Horizontal, parent)
-{}
-
-void TailTableEditWidget::setupModelAndView()
-{
-    m_model = new QSqlTableModel(this, DB->database());
-    m_model->setTable(OPL::GLOBALS->getDbTableName(OPL::DbTable::Tails));
-    m_model->select();
-    m_model->setHeaderData(COL_REGISTRATION, Qt::Horizontal, COLUMN_NAME_REGISTRATION);
-    m_model->setHeaderData(COL_TYPE, Qt::Horizontal, COLUMN_NAME_TYPE);
-    m_model->setHeaderData(COL_COMPANY, Qt::Horizontal, COLUMN_NAME_COMPANY);
-
-    m_view->setModel(m_model);
-    m_view->setSelectionMode(QAbstractItemView::SingleSelection);
-    m_view->setSelectionBehavior(QAbstractItemView::SelectRows);
-    m_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
-    m_view->horizontalHeader()->setStretchLastSection(QHeaderView::Stretch);
-    m_view->resizeColumnsToContents();
-    m_view->verticalHeader()->hide();
-    m_view->setAlternatingRowColors(true);
-    for(const int i : COLS_TO_HIDE)
-        m_view->hideColumn(i);
-}
-
-void TailTableEditWidget::setupUI()
-{
-    // the base class does most of the setup
-    TableEditWidget::setupUI();
-
-    // only need to set the table specific labels and combo box items
-    m_addNewEntryPushButton->setText(tr("Add New Tail"));
-    m_deleteEntryPushButton->setText(tr("Delete Selected Tail"));
-    m_filterSelectionComboBox->addItems(FILTER_COLUMNS);
-}
-
-QString TailTableEditWidget::deleteErrorString(int rowId)
-{
-    QList<int> foreign_key_constraints = DB->getForeignKeyConstraints(rowId,
-                                                                      OPL::DbTable::Tails);
-    QList<OPL::FlightEntry> constrained_flights;
-    for (const auto &row_id : qAsConst(foreign_key_constraints)) {
-        constrained_flights.append(DB->getFlightEntry(row_id));
-    }
-
-    QMessageBox message_box(this);
-    if (constrained_flights.isEmpty()) {
-        // error is a database error
-        return tr("<br>Unable to delete.<br><br>The following error has ocurred: %1"
-                  ).arg(DB->lastError.text());
-    } else {
-        QString constrained_flights_string;
-        for (int i=0; i<constrained_flights.length(); i++) {
-            constrained_flights_string.append(constrained_flights[i].getFlightSummary()
-                                              + QLatin1String("&nbsp;&nbsp;&nbsp;&nbsp;<br>"));
-            if (i>10) {
-                constrained_flights_string.append(QLatin1String("<br>[...]<br>"));
-                break;
-            }
-        }
-        return (tr("Unable to delete.<br><br>"
-                   "This is most likely the case because a flight exists with the aircraft "
-                   "you are trying to delete.<br><br>"
-                   "%1 flight(s) with this aircraft have been found:<br><br><br><b><tt>"
-                   "%2"
-                   "</b></tt><br><br>You have to change or remove the conflicting flight(s) "
-                   "before removing this aircraft from the database.<br><br>"
-                   ).arg(
-                        QString::number(constrained_flights.length()),
-                        constrained_flights_string)
-                );
-    }
-}
-
-QString TailTableEditWidget::confirmDeleteString(int rowId)
-{
-    const auto entry = DB->getTailEntry(rowId);
-    return tr("You are deleting the following aircraft:<br><br><b><tt>"
-              "%1 (%2)</b></tt><br><br>Are you sure?"
-              ).arg(
-              entry.getData().value(OPL::TailEntry::REGISTRATION).toString(),
-              entry.type()
-              );
-}
-
-EntryEditDialog *TailTableEditWidget::getEntryEditDialog(QWidget *parent)
-{
-    QString empty;
-    return new NewTailDialog(empty, parent);
-}
-
-void TailTableEditWidget::filterTextChanged(const QString &filterString)
-{
-    if(filterString.isEmpty()) {
-        m_model->setFilter(QString());
-        return;
-    }
-
-    int i = m_filterSelectionComboBox->currentIndex();
-    const QString filter =
-        QLatin1Char('\"')
-        + FILTER_COLUMN_NAMES.at(i)
-        + QLatin1String("\" LIKE '%")
-        + filterString
-        + QLatin1String("%'");
-    m_model->setFilter(filter);
-}
+#include "tailtableeditwidget.h"
+#include "src/database/database.h"
+#include "src/gui/dialogues/newtaildialog.h"
+
+TailTableEditWidget::TailTableEditWidget(QWidget *parent)
+    : TableEditWidget(Horizontal, parent)
+{}
+
+void TailTableEditWidget::setupModelAndView()
+{
+    m_model = new QSqlTableModel(this, DB->database());
+    m_model->setTable(OPL::GLOBALS->getDbTableName(OPL::DbTable::Tails));
+    m_model->select();
+    m_model->setHeaderData(COL_REGISTRATION, Qt::Horizontal, COLUMN_NAME_REGISTRATION);
+    m_model->setHeaderData(COL_TYPE, Qt::Horizontal, COLUMN_NAME_TYPE);
+    m_model->setHeaderData(COL_COMPANY, Qt::Horizontal, COLUMN_NAME_COMPANY);
+
+    m_view->setModel(m_model);
+    m_view->setSelectionMode(QAbstractItemView::SingleSelection);
+    m_view->setSelectionBehavior(QAbstractItemView::SelectRows);
+    m_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
+    m_view->horizontalHeader()->setStretchLastSection(QHeaderView::Stretch);
+    m_view->resizeColumnsToContents();
+    m_view->verticalHeader()->hide();
+    m_view->setAlternatingRowColors(true);
+    for(const int i : COLS_TO_HIDE)
+        m_view->hideColumn(i);
+}
+
+void TailTableEditWidget::setupUI()
+{
+    // the base class does most of the setup
+    TableEditWidget::setupUI();
+
+    // only need to set the table specific labels and combo box items
+    m_addNewEntryPushButton->setText(tr("Add New Tail"));
+    m_deleteEntryPushButton->setText(tr("Delete Selected Tail"));
+    m_filterSelectionComboBox->addItems(FILTER_COLUMNS);
+}
+
+QString TailTableEditWidget::deleteErrorString(int rowId)
+{
+    QList<int> foreign_key_constraints = DB->getForeignKeyConstraints(rowId,
+                                                                      OPL::DbTable::Tails);
+    QList<OPL::FlightEntry> constrained_flights;
+    for (const auto &row_id : std::as_const(foreign_key_constraints)) {
+        constrained_flights.append(DB->getFlightEntry(row_id));
+    }
+
+    QMessageBox message_box(this);
+    if (constrained_flights.isEmpty()) {
+        // error is a database error
+        return tr("<br>Unable to delete.<br><br>The following error has ocurred: %1"
+                  ).arg(DB->lastError.text());
+    } else {
+        QString constrained_flights_string;
+        for (int i=0; i<constrained_flights.length(); i++) {
+            constrained_flights_string.append(constrained_flights[i].getFlightSummary()
+                                              + QLatin1String("&nbsp;&nbsp;&nbsp;&nbsp;<br>"));
+            if (i>10) {
+                constrained_flights_string.append(QLatin1String("<br>[...]<br>"));
+                break;
+            }
+        }
+        return (tr("Unable to delete.<br><br>"
+                   "This is most likely the case because a flight exists with the aircraft "
+                   "you are trying to delete.<br><br>"
+                   "%1 flight(s) with this aircraft have been found:<br><br><br><b><tt>"
+                   "%2"
+                   "</b></tt><br><br>You have to change or remove the conflicting flight(s) "
+                   "before removing this aircraft from the database.<br><br>"
+                   ).arg(
+                        QString::number(constrained_flights.length()),
+                        constrained_flights_string)
+                );
+    }
+}
+
+QString TailTableEditWidget::confirmDeleteString(int rowId)
+{
+    const auto entry = DB->getTailEntry(rowId);
+    return tr("You are deleting the following aircraft:<br><br><b><tt>"
+              "%1 (%2)</b></tt><br><br>Are you sure?"
+              ).arg(
+              entry.getData().value(OPL::TailEntry::REGISTRATION).toString(),
+              entry.type()
+              );
+}
+
+EntryEditDialog *TailTableEditWidget::getEntryEditDialog(QWidget *parent)
+{
+    QString empty;
+    return new NewTailDialog(empty, parent);
+}
+
+void TailTableEditWidget::filterTextChanged(const QString &filterString)
+{
+    if(filterString.isEmpty()) {
+        m_model->setFilter(QString());
+        return;
+    }
+
+    int i = m_filterSelectionComboBox->currentIndex();
+    const QString filter =
+        QLatin1Char('\"')
+        + FILTER_COLUMN_NAMES.at(i)
+        + QLatin1String("\" LIKE '%")
+        + filterString
+        + QLatin1String("%'");
+    m_model->setFilter(filter);
+}

+ 211 - 211
src/gui/widgets/totalswidget.cpp

@@ -1,211 +1,211 @@
-/*
- *openPilotLog - A FOSS Pilot Logbook Application
- *Copyright (C) 2020-2023 Felix Turowsky
- *
- *This program is free software: you can redistribute it and/or modify
- *it under the terms of the GNU General Public License as published by
- *the Free Software Foundation, either version 3 of the License, or
- *(at your option) any later version.
- *
- *This program is distributed in the hope that it will be useful,
- *but WITHOUT ANY WARRANTY; without even the implied warranty of
- *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *GNU General Public License for more details.
- *
- *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 "totalswidget.h"
-#include "QtWidgets/qlineedit.h"
-#include "src/database/database.h"
-#include "src/database/previousexperienceentry.h"
-#include "src/opl.h"
-#include "src/classes/time.h"
-#include "ui_totalswidget.h"
-#include "src/classes/settings.h"
-
-TotalsWidget::TotalsWidget(WidgetType widgetType, QWidget *parent) :
-    QWidget(parent),
-    ui(new Ui::TotalsWidget)
-{
-    ui->setupUi(this);
-    setup(widgetType);
-}
-
-TotalsWidget::~TotalsWidget()
-{
-    delete ui;
-}
-
-/*!
- * \brief TotalsWidget::setup Sets the line edits as editable or read-only and connects signals if required
- * \details This widget can be used to either display the totals (in the home widget) or
- * to edit the total previous experience, from previous logbooks (in the settings widget).
- */
-void TotalsWidget::setup(const WidgetType widgetType)
-{
-    m_format = Settings::getDisplayFormat();
-    const QList<QLineEdit *> lineEdits = this->findChildren<QLineEdit *>();
-
-    switch (widgetType) {
-    case TotalTimeWidget:
-        LOG << "Setting up totals widget";
-        // disable editing
-        for (const auto &lineEdit : lineEdits) {
-            lineEdit->setFocusPolicy(Qt::FocusPolicy::NoFocus);
-        }
-        // populate the UI
-        fillTotals(widgetType);
-        break;
-    case PreviousExperienceWidget:
-        LOG << "Setting up previous XP widget";
-        for (const auto &lineEdit : lineEdits) {
-            lineEdit->setFocusPolicy(Qt::FocusPolicy::StrongFocus);
-            // set a validator for the TO/LDG line edits, the other ones get validated seperately
-            if(lineEdit->objectName().contains(QLatin1String("to")) || lineEdit->objectName().contains(QLatin1String("ldg"))) {
-                lineEdit->setValidator(new QIntValidator(0, std::numeric_limits<int>::max(), this));
-            }
-        }
-        // initialise m_rowData
-        m_rowData = DB->getRowData(OPL::DbTable::PreviousExperience, ROW_ID);
-
-        // populate the UI
-        fillTotals(widgetType);
-        connectSignalsAndSlots();
-        break;
-    default:
-        break;
-    }
-}
-
-/*!
- * \brief HomeWidget::fillTotals Retreives a Database Summary of Total Flight Time and fills the UI.
- */
-void TotalsWidget::fillTotals(const WidgetType widgetType)
-{
-    OPL::RowData_T time_data;
-
-    // retreive times from database
-    switch (widgetType) {
-    case TotalTimeWidget:
-        time_data = DB->getTotals(true);
-        break;
-    case PreviousExperienceWidget:
-        time_data = DB->getRowData(OPL::DbTable::PreviousExperience, ROW_ID);
-        break;
-    }
-
-    // fill the line edits with the data obtained
-    const OPL::RowData_T &const_time_data = qAsConst(time_data);
-    for (const auto &field : const_time_data) {
-        // match the db entries to the line edits using their object name
-        const QString search_term = time_data.key(field) + QLatin1String("LineEdit");
-        QLineEdit* line_edit = this->findChild<QLineEdit *>(search_term);
-        // fill the line edit with the corresponding data
-        if(line_edit != nullptr) {
-            const QString &le_name = line_edit->objectName();
-            if(le_name.contains("to") || le_name.contains("ldg")) {
-                // line edits for take offs and landings
-                line_edit->setText(field.toString());
-            } else {
-                // line edits for total time
-                OPL::Time time = OPL::Time(field.toInt(), m_format);
-                line_edit->setText(time.toString());
-            }
-        }
-
-    }
-}
-
-/*!
- * \brief TotalsWidget::connectSignalsAndSlots If the widget is editable, connects the signals and slots
- */
-void TotalsWidget::connectSignalsAndSlots()
-{
-    connect(ui->tblkLineEdit, &QLineEdit::editingFinished,
-            this, &TotalsWidget::timeLineEditEditingFinished);
-    connect(ui->tSPSELineEdit, &QLineEdit::editingFinished,
-            this, &TotalsWidget::timeLineEditEditingFinished);
-    connect(ui->tSPMELineEdit, &QLineEdit::editingFinished,
-            this, &TotalsWidget::timeLineEditEditingFinished);
-    connect(ui->tMPLineEdit, &QLineEdit::editingFinished,
-            this, &TotalsWidget::timeLineEditEditingFinished);
-
-    connect(ui->tPICLineEdit, &QLineEdit::editingFinished,
-            this, &TotalsWidget::timeLineEditEditingFinished);
-    connect(ui->tSICLineEdit, &QLineEdit::editingFinished,
-            this, &TotalsWidget::timeLineEditEditingFinished);
-    connect(ui->tDUALLineEdit, &QLineEdit::editingFinished,
-            this, &TotalsWidget::timeLineEditEditingFinished);
-    connect(ui->tFILineEdit, &QLineEdit::editingFinished,
-            this, &TotalsWidget::timeLineEditEditingFinished);
-    connect(ui->tPICUSLineEdit, &QLineEdit::editingFinished,
-            this, &TotalsWidget::timeLineEditEditingFinished);
-    connect(ui->tIFRLineEdit, &QLineEdit::editingFinished,
-            this, &TotalsWidget::timeLineEditEditingFinished);
-    connect(ui->tNIGHTLineEdit, &QLineEdit::editingFinished,
-            this, &TotalsWidget::timeLineEditEditingFinished);
-    connect(ui->tSIMLineEdit, &QLineEdit::editingFinished,
-            this, &TotalsWidget::timeLineEditEditingFinished);
-
-    connect(ui->toDayLineEdit, &QLineEdit::editingFinished,
-            this, &TotalsWidget::movementLineEditEditingFinished);
-    connect(ui->toNightLineEdit, &QLineEdit::editingFinished,
-            this, &TotalsWidget::movementLineEditEditingFinished);
-    connect(ui->ldgDayLineEdit, &QLineEdit::editingFinished,
-            this, &TotalsWidget::movementLineEditEditingFinished);
-    connect(ui->ldgNightLineEdit, &QLineEdit::editingFinished,
-            this, &TotalsWidget::movementLineEditEditingFinished);
-}
-
-
-void TotalsWidget::timeLineEditEditingFinished()
-{
-    LOG << sender()->objectName() + "Editing finished.";
-    QLineEdit* line_edit = this->findChild<QLineEdit*>(sender()->objectName());
-    const QString& text = line_edit->text();
-
-    // make sure the input is usable
-    if(!text.contains(QChar(':'))) {
-        WARN(tr("Please enter the time as: <br><br> hh:mm"));
-        line_edit->setText(QString());
-        return;
-    }
-
-    // write the updated value to the database
-    const QString db_field = line_edit->objectName().remove(QLatin1String("LineEdit"));
-    const QVariant value = OPL::Time::fromString(line_edit->text(), m_format).toMinutes();
-
-    m_rowData.insert(db_field, value);
-    LOG << "Added row data: " + db_field + ": " + value.toString();
-
-    const auto previous_experience = OPL::PreviousExperienceEntry(ROW_ID, m_rowData);
-    DB->commit(previous_experience);
-
-    // Read back the value and set the line edit to confirm input is correct and provide user feedback
-    m_rowData = DB->getRowData(OPL::DbTable::PreviousExperience, ROW_ID);
-    OPL::Time new_time = OPL::Time(m_rowData.value(db_field).toInt(), m_format);
-    line_edit->setText(new_time.toString());
-}
-
-void TotalsWidget::movementLineEditEditingFinished()
-{
-    // input validation is done by the QValidator
-    QLineEdit* line_edit = this->findChild<QLineEdit*>(sender()->objectName());
-    LOG << line_edit->objectName() + "Editing finished.";
-
-    // extract the value from the input and update the DB
-    const QString db_field = line_edit->objectName().remove(QLatin1String("LineEdit"));
-    const QVariant value = line_edit->text().toInt();
-
-    m_rowData.insert(db_field, value);
-
-    const auto previous_experience = OPL::PreviousExperienceEntry(ROW_ID, m_rowData);
-    DB->commit(previous_experience);
-
-    // read back the value and set the line edit to the retreived value to give user feedback
-    m_rowData = DB->getRowData(OPL::DbTable::PreviousExperience, ROW_ID);
-    const QString new_value = QString::number(m_rowData.value(db_field).toInt());
-    line_edit->setText(new_value);
-}
-
+/*
+ *openPilotLog - A FOSS Pilot Logbook Application
+ *Copyright (C) 2020-2023 Felix Turowsky
+ *
+ *This program is free software: you can redistribute it and/or modify
+ *it under the terms of the GNU General Public License as published by
+ *the Free Software Foundation, either version 3 of the License, or
+ *(at your option) any later version.
+ *
+ *This program is distributed in the hope that it will be useful,
+ *but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *GNU General Public License for more details.
+ *
+ *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 "totalswidget.h"
+#include "QtWidgets/qlineedit.h"
+#include "src/database/database.h"
+#include "src/database/previousexperienceentry.h"
+#include "src/opl.h"
+#include "src/classes/time.h"
+#include "ui_totalswidget.h"
+#include "src/classes/settings.h"
+
+TotalsWidget::TotalsWidget(WidgetType widgetType, QWidget *parent) :
+    QWidget(parent),
+    ui(new Ui::TotalsWidget)
+{
+    ui->setupUi(this);
+    setup(widgetType);
+}
+
+TotalsWidget::~TotalsWidget()
+{
+    delete ui;
+}
+
+/*!
+ * \brief TotalsWidget::setup Sets the line edits as editable or read-only and connects signals if required
+ * \details This widget can be used to either display the totals (in the home widget) or
+ * to edit the total previous experience, from previous logbooks (in the settings widget).
+ */
+void TotalsWidget::setup(const WidgetType widgetType)
+{
+    m_format = Settings::getDisplayFormat();
+    const QList<QLineEdit *> lineEdits = this->findChildren<QLineEdit *>();
+
+    switch (widgetType) {
+    case TotalTimeWidget:
+        LOG << "Setting up totals widget";
+        // disable editing
+        for (const auto &lineEdit : lineEdits) {
+            lineEdit->setFocusPolicy(Qt::FocusPolicy::NoFocus);
+        }
+        // populate the UI
+        fillTotals(widgetType);
+        break;
+    case PreviousExperienceWidget:
+        LOG << "Setting up previous XP widget";
+        for (const auto &lineEdit : lineEdits) {
+            lineEdit->setFocusPolicy(Qt::FocusPolicy::StrongFocus);
+            // set a validator for the TO/LDG line edits, the other ones get validated seperately
+            if(lineEdit->objectName().contains(QLatin1String("to")) || lineEdit->objectName().contains(QLatin1String("ldg"))) {
+                lineEdit->setValidator(new QIntValidator(0, std::numeric_limits<int>::max(), this));
+            }
+        }
+        // initialise m_rowData
+        m_rowData = DB->getRowData(OPL::DbTable::PreviousExperience, ROW_ID);
+
+        // populate the UI
+        fillTotals(widgetType);
+        connectSignalsAndSlots();
+        break;
+    default:
+        break;
+    }
+}
+
+/*!
+ * \brief HomeWidget::fillTotals Retreives a Database Summary of Total Flight Time and fills the UI.
+ */
+void TotalsWidget::fillTotals(const WidgetType widgetType)
+{
+    OPL::RowData_T time_data;
+
+    // retreive times from database
+    switch (widgetType) {
+    case TotalTimeWidget:
+        time_data = DB->getTotals(true);
+        break;
+    case PreviousExperienceWidget:
+        time_data = DB->getRowData(OPL::DbTable::PreviousExperience, ROW_ID);
+        break;
+    }
+
+    // fill the line edits with the data obtained
+    const OPL::RowData_T &const_time_data = std::as_const(time_data);
+    for (const auto &field : const_time_data) {
+        // match the db entries to the line edits using their object name
+        const QString search_term = time_data.key(field) + QLatin1String("LineEdit");
+        QLineEdit* line_edit = this->findChild<QLineEdit *>(search_term);
+        // fill the line edit with the corresponding data
+        if(line_edit != nullptr) {
+            const QString &le_name = line_edit->objectName();
+            if(le_name.contains("to") || le_name.contains("ldg")) {
+                // line edits for take offs and landings
+                line_edit->setText(field.toString());
+            } else {
+                // line edits for total time
+                OPL::Time time = OPL::Time(field.toInt(), m_format);
+                line_edit->setText(time.toString());
+            }
+        }
+
+    }
+}
+
+/*!
+ * \brief TotalsWidget::connectSignalsAndSlots If the widget is editable, connects the signals and slots
+ */
+void TotalsWidget::connectSignalsAndSlots()
+{
+    connect(ui->tblkLineEdit, &QLineEdit::editingFinished,
+            this, &TotalsWidget::timeLineEditEditingFinished);
+    connect(ui->tSPSELineEdit, &QLineEdit::editingFinished,
+            this, &TotalsWidget::timeLineEditEditingFinished);
+    connect(ui->tSPMELineEdit, &QLineEdit::editingFinished,
+            this, &TotalsWidget::timeLineEditEditingFinished);
+    connect(ui->tMPLineEdit, &QLineEdit::editingFinished,
+            this, &TotalsWidget::timeLineEditEditingFinished);
+
+    connect(ui->tPICLineEdit, &QLineEdit::editingFinished,
+            this, &TotalsWidget::timeLineEditEditingFinished);
+    connect(ui->tSICLineEdit, &QLineEdit::editingFinished,
+            this, &TotalsWidget::timeLineEditEditingFinished);
+    connect(ui->tDUALLineEdit, &QLineEdit::editingFinished,
+            this, &TotalsWidget::timeLineEditEditingFinished);
+    connect(ui->tFILineEdit, &QLineEdit::editingFinished,
+            this, &TotalsWidget::timeLineEditEditingFinished);
+    connect(ui->tPICUSLineEdit, &QLineEdit::editingFinished,
+            this, &TotalsWidget::timeLineEditEditingFinished);
+    connect(ui->tIFRLineEdit, &QLineEdit::editingFinished,
+            this, &TotalsWidget::timeLineEditEditingFinished);
+    connect(ui->tNIGHTLineEdit, &QLineEdit::editingFinished,
+            this, &TotalsWidget::timeLineEditEditingFinished);
+    connect(ui->tSIMLineEdit, &QLineEdit::editingFinished,
+            this, &TotalsWidget::timeLineEditEditingFinished);
+
+    connect(ui->toDayLineEdit, &QLineEdit::editingFinished,
+            this, &TotalsWidget::movementLineEditEditingFinished);
+    connect(ui->toNightLineEdit, &QLineEdit::editingFinished,
+            this, &TotalsWidget::movementLineEditEditingFinished);
+    connect(ui->ldgDayLineEdit, &QLineEdit::editingFinished,
+            this, &TotalsWidget::movementLineEditEditingFinished);
+    connect(ui->ldgNightLineEdit, &QLineEdit::editingFinished,
+            this, &TotalsWidget::movementLineEditEditingFinished);
+}
+
+
+void TotalsWidget::timeLineEditEditingFinished()
+{
+    LOG << sender()->objectName() + "Editing finished.";
+    QLineEdit* line_edit = this->findChild<QLineEdit*>(sender()->objectName());
+    const QString& text = line_edit->text();
+
+    // make sure the input is usable
+    if(!text.contains(QChar(':'))) {
+        WARN(tr("Please enter the time as: <br><br> hh:mm"));
+        line_edit->setText(QString());
+        return;
+    }
+
+    // write the updated value to the database
+    const QString db_field = line_edit->objectName().remove(QLatin1String("LineEdit"));
+    const QVariant value = OPL::Time::fromString(line_edit->text(), m_format).toMinutes();
+
+    m_rowData.insert(db_field, value);
+    LOG << "Added row data: " + db_field + ": " + value.toString();
+
+    const auto previous_experience = OPL::PreviousExperienceEntry(ROW_ID, m_rowData);
+    DB->commit(previous_experience);
+
+    // Read back the value and set the line edit to confirm input is correct and provide user feedback
+    m_rowData = DB->getRowData(OPL::DbTable::PreviousExperience, ROW_ID);
+    OPL::Time new_time = OPL::Time(m_rowData.value(db_field).toInt(), m_format);
+    line_edit->setText(new_time.toString());
+}
+
+void TotalsWidget::movementLineEditEditingFinished()
+{
+    // input validation is done by the QValidator
+    QLineEdit* line_edit = this->findChild<QLineEdit*>(sender()->objectName());
+    LOG << line_edit->objectName() + "Editing finished.";
+
+    // extract the value from the input and update the DB
+    const QString db_field = line_edit->objectName().remove(QLatin1String("LineEdit"));
+    const QVariant value = line_edit->text().toInt();
+
+    m_rowData.insert(db_field, value);
+
+    const auto previous_experience = OPL::PreviousExperienceEntry(ROW_ID, m_rowData);
+    DB->commit(previous_experience);
+
+    // read back the value and set the line edit to the retreived value to give user feedback
+    m_rowData = DB->getRowData(OPL::DbTable::PreviousExperience, ROW_ID);
+    const QString new_value = QString::number(m_rowData.value(db_field).toInt());
+    line_edit->setText(new_value);
+}
+

+ 380 - 382
src/opl.h

@@ -1,382 +1,380 @@
-/*
- *openPilotLog - A FOSS Pilot Logbook Application
- *Copyright (C) 2020-2023 Felix Turowsky
- *
- *This program is free software: you can redistribute it and/or modify
- *it under the terms of the GNU General Public License as published by
- *the Free Software Foundation, either version 3 of the License, or
- *(at your option) any later version.
- *
- *This program is distributed in the hope that it will be useful,
- *but WITHOUT ANY WARRANTY; without even the implied warranty of
- *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *GNU General Public License for more details.
- *
- *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 OPLCONSTANTS_H
-#define OPLCONSTANTS_H
-
-#include <QtCore>
-#include <QMessageBox>
-#include <QComboBox>
-
-#define APPNAME QStringLiteral("openPilotLog")
-#define ORGNAME QStringLiteral("opl")
-#define ORGDOMAIN QStringLiteral("https://github.com/fiffty-50/openpilotlog")
-#define OPL_VERSION 0
-#define OPL_SUBVERSION 1
-
-#if OPL_VERSION < 1
-    #define OPL_VERSION_STRING QString(QString::number(OPL_VERSION) + "." + QString::number(OPL_SUBVERSION)) + "-alpha"
-#else
-    #define OPL_VERSION_STRING QString(QString::number(OPL_VERSION) + "." + QString::number(OPL_SUBVERSION))
-#endif
-
-
-/*!
- *  \brief A namespace to collect constants and enums used throughout the application.
- *
- *  \details The opl namespace collects enums and constants that are used throughout
- *  the application and provide uniform access.
- *
- *  The date, time and datetime namespaces include enums used to differentiate
- *  date and time formats for QDate, QTime and QDateTime that deviate from standard values
- *  included in the Qt Framework like Qt::ISODate and are to be used in conjunction with the
- *  .toString() members of these classes.
- *
- *  The db namespace contains constants for programatically accessing the database in a fast
- *  and uniform manner.
- */
-namespace OPL {
-
-#if defined(__GNUC__) || defined(__clang__)
-    #define FUNC_IDENT __PRETTY_FUNCTION__
-#elif defined(_MSC_VER)
-    #define FUNC_IDENT __FUNCSIG__
-#else
-    #define FUNC_IDENT __func__
-#endif
-
-#define DEB qDebug()                            // Use for debugging
-#define LOG qInfo()                             // Use for logging milestones (silently, will be written to log file and console out only)
-#define TODO qCritical() << "TO DO:\t"
-
-#define INFO(msg) OPL::ANotificationHandler::info(msg, this)  // Use for messages of interest to the user (will be displayed in GUI)
-#define WARN(msg) OPL::ANotificationHandler::warn(msg, this)  // Use for warnings (will be displayed in GUI)
-#define CRIT(msg) OPL::ANotificationHandler::crit(msg, this)  // Use for critical warnings (will be displayed in GUI)
-
-/**
- * @brief Defines the row ID for non-user entries in the database;
- */
-constexpr static int STUB_ROW_ID = -1;
-
-/**
- * @brief Defines a four-letter code for a non-extistent (dummy) airport: "XXXX"
- */
-constexpr static auto STUB_AIRPORT_CODE = QLatin1String("XXXX");
-/**
- * @brief Defines a registration for a non-existent (dummy) aircraft: "XX-XXX"
- */
-constexpr static auto STUB_AIRCRAFT_REG = QLatin1String("XX-XXX");
-
-/*!
- * \brief The decimal seperator used internally
- */
-constexpr static char DECIMAL_SEPERATOR = '.';
-
-
-/*!
- * \brief The ANotificationHandler class handles displaying of user-directed messages. It displays
- * information to the user in a QMessageBox and forwards the displayed message to ALog so it is written
- * to the console and log files. The INFO, WARN and CRIT makros provide convenient access.
- */
-class ANotificationHandler {
-public:
-    static inline void info(const QString msg, QWidget *parent = nullptr){
-
-        qInfo() << msg;
-        auto mb = QMessageBox(QMessageBox::Information, QStringLiteral("Info"), msg, QMessageBox::StandardButton::Ok, parent);
-        mb.exec();
-    };
-    static inline void warn(const QString msg, QWidget *parent = nullptr){
-        qWarning() << msg;
-        auto mb = QMessageBox(QMessageBox::Warning, QStringLiteral("Warning"), msg, QMessageBox::StandardButton::Ok, parent);
-        mb.exec();
-    };
-    static inline void crit(const QString msg, QWidget *parent = nullptr){
-        qCritical() << msg;
-        auto mb = QMessageBox(QMessageBox::Critical, QStringLiteral("Warning"), msg, QMessageBox::StandardButton::Ok, parent);
-        mb.exec();
-    };
-}; // class ANotificationHandler
-
-using RowData_T = QHash<QString, QVariant>;
-
-struct ToLdgCount_T {
-    int toDay;
-    int toNight;
-    int ldgDay;
-    int ldgNight;
-
-    ToLdgCount_T(int toDay, int toNight, int ldgDay, int ldgNight)
-        : toDay(toDay), toNight(toNight), ldgDay(ldgDay), ldgNight(ldgNight) {}
-};
-
-/*!
- * \brief The DateFormat struct encapsulates how date and time values are displayed.
- * \details Stores how the user wishes to display and enter Date and Time Entries.
- * These are stored numerically in the database and thus need to be converted to
- * human-readably form.
- */
-struct DateTimeFormat {
-    /*!
-     * \brief Enumerates how dates can be formatted to a localised format.
-     * \value Default - The Application standard, Equivalent to Qt::ISODate
-     * \value SystemLocale - The current system locale date format
-     */
-    enum class DateFormat { Default, SystemLocale, Custom };
-
-    /*!
-     * \brief Enumerates how time values can be formatted
-     * \value Default - The application default is 'hh:mm'
-     * \value Decimal - Time as Decmial hours (01:30 == 1.5)
-     * \value Custom - A user-provided custom format string
-     */
-    enum class TimeFormat { Default, Decimal, Custom };
-
-    /*!
-     * \brief Initialise a DateTimeFormat instance with default values
-     */
-    DateTimeFormat()
-        : m_dateFormat(DateFormat::Default),
-        m_dateFormatString(QStringLiteral("yyyy-MM-dd")),
-        m_timeFormat(TimeFormat::Default),
-        m_timeFormatString(QStringLiteral("hh:mm"))
-    {}
-
-    DateTimeFormat(DateFormat dateFormat_,
-                   const QString &dateFormatString_,
-                   TimeFormat timeFormat_,
-                   const QString &timeFormatString_)
-        :
-        m_dateFormat(dateFormat_),
-        m_dateFormatString(dateFormatString_),
-        m_timeFormat(timeFormat_),
-        m_timeFormatString(timeFormatString_)
-    {}
-
-
-public:
-    DateFormat dateFormat() const { return m_dateFormat; }
-    TimeFormat timeFormat() const { return m_timeFormat; }
-    const QString &dateFormatString() const { return m_dateFormatString; }
-    const QString &timeFormatString() const { return m_timeFormatString; }
-
-private:
-    DateFormat m_dateFormat;
-    TimeFormat m_timeFormat;
-    QString m_dateFormatString;
-    QString m_timeFormatString;
-};
-
-/*!
- * \brief ADateFormats enumerates the accepted date formats for QDateEdits
- * \todo At the moment, only ISODate is accepet as a valid date format.
- */
-enum class DateFormat {ISODate, DE, EN };
-
-enum class FlightTimeFormat {Default, Decimal};
-
-enum class DateTimeFormat_deprecated {Default, Backup};
-
-/*!
- * \brief PilotFunction
- * Pilot in Command, Pilot in Command under Supervision, Second in Command (Co-Pilot), Dual, Flight Instructor
- */
-enum class PilotFunction {PIC = 0, PICUS = 1, SIC = 2, DUAL = 3, FI = 4};
-
-/*!
- * \brief Enumerates the available translations
- */
-enum class Translation {English, German, Spanish};
-
-/*!
- * \brief Enumerates the available SQL views in the database
- */
-enum class LogbookView {Default, DefaultWithSim, Easa, EasaWithSim, SimulatorOnly};
-
-/*!
- * \brief Enumerates the Simulator Types: Flight and Navigation Procedures Trainer 1/2, Flight Simulation Training Device
- */
-enum class SimulatorType {FNPTI = 0, FNPTII = 1, FSTD = 2};
-
-/*!
- * \brief Enumerates the tables in the database
- */
-enum class DbTable {Any, Flights, Simulators, Pilots, Tails, Aircraft, Airports, Currencies, Changelog, PreviousExperience};
-
-/*!
- * \brief Enumerates the currency names
- */
-enum class CurrencyName {Licence = 1, TypeRating = 2, LineCheck = 3, Medical = 4, Custom1 = 5, Custom2 = 6};
-
-/*!
- * \brief The OplGlobals class encapsulates non-POD globals to avoid making them static. It is available
- * as a global static object via the OPL::GLOBAL makro and may be used as if it were a pointer, guaranteed to be initialized exactly once.
- * For more information, see (Q_GLOBAL_STATIC)[https://doc.qt.io/qt-5/qglobalstatic.html#details]
- */
-class OplGlobals : public QObject {
-public:
-    OplGlobals() = default;
-
-    void fillLanguageComboBox(QComboBox *combo_box) const;
-    void fillViewNamesComboBox(QComboBox *combo_box) const;
-    void loadPilotFunctios(QComboBox *combo_box) const;
-    void loadSimulatorTypes(QComboBox *combo_box) const;
-    void loadApproachTypes(QComboBox *combo_box) const;
-    void loadFlightRules(QComboBox *combo_box) const;
-
-    inline const QStringList &getApproachTypes() const {return APPROACH_TYPES;}
-    inline const QString getLanguageFilePath(Translation language) const {return L10N_FilePaths.value(language);}
-    inline const QString getViewIdentifier(LogbookView view_name) const {return DATABASE_VIEWS.value(view_name);}
-    inline const QString getDbTableName(DbTable table_name) const {return DB_TABLES.value(table_name);}
-
-private:
-    Q_OBJECT
-    const static inline QMap<Translation, QString> L10N_FilePaths {
-        {Translation::English, QStringLiteral("l10n/openpilotlog_en")},
-        {Translation::German,  QStringLiteral("l10n/openpilotlog_de")},
-        {Translation::Spanish, QStringLiteral("l10n/openpilotlog_es")},
-    };
-    const static inline QMap<Translation, QString> L10N_DisplayNames {
-        {Translation::English, QStringLiteral("English")},
-        {Translation::German,  QStringLiteral("Deutsch")},
-        {Translation::Spanish, QStringLiteral("Español")},
-    };
-    const static inline QMap<LogbookView, QString> DATABASE_VIEWS = {
-        {LogbookView::Default,        QStringLiteral("viewDefault")},
-        {LogbookView::DefaultWithSim, QStringLiteral("viewDefaultSim")},
-        {LogbookView::Easa,           QStringLiteral("viewEasa")},
-        {LogbookView::EasaWithSim,    QStringLiteral("viewEasaSim")},
-        {LogbookView::SimulatorOnly,  QStringLiteral("viewSimulators")},
-    };
-    const QMap<LogbookView, QString> DATABASE_VIEW_DISPLAY_NAMES = {
-        {LogbookView::Default,        tr("Default")},
-        {LogbookView::DefaultWithSim, tr("Default with Simulator")},
-        {LogbookView::Easa,           tr("EASA-FCL")},
-        {LogbookView::EasaWithSim,    tr("EASA-FCL with Simulator")},
-        {LogbookView::SimulatorOnly,  tr("Simulator Sessions Only")},
-    };
-    const static inline QMap<PilotFunction, QString> PILOT_FUNCTIONS = {
-        {PilotFunction::PIC,   QStringLiteral("PIC")},
-        {PilotFunction::PICUS, QStringLiteral("PICUS")},
-        {PilotFunction::SIC,   QStringLiteral("SIC")},
-        {PilotFunction::DUAL,  QStringLiteral("DUAL")},
-        {PilotFunction::FI,    QStringLiteral("FI")},
-    };
-    const static inline QMap<SimulatorType, QString> SIMULATOR_TYPES = {
-        {SimulatorType::FNPTI,  QStringLiteral("FNPT I")},
-        {SimulatorType::FNPTII, QStringLiteral("FNPT II")},
-        {SimulatorType::FSTD,   QStringLiteral("FSTD")},
-    };
-    const static inline QMap<DbTable, QString> DB_TABLES = {
-        {DbTable::Flights,      QStringLiteral("flights")},
-        {DbTable::Simulators,   QStringLiteral("simulators")},
-        {DbTable::Pilots,       QStringLiteral("pilots")},
-        {DbTable::Tails,        QStringLiteral("tails")},
-        {DbTable::Aircraft,     QStringLiteral("aircraft")},
-        {DbTable::Airports,     QStringLiteral("airports")},
-        {DbTable::Currencies,   QStringLiteral("currencies")},
-        {DbTable::Changelog,    QStringLiteral("changelog")},
-        {DbTable::PreviousExperience,    QStringLiteral("previousExperience")},
-    };
-
-    const static inline QStringList APPROACH_TYPES = {
-            QStringLiteral("VISUAL"),
-            QStringLiteral("ILS CAT I"),
-            QStringLiteral("ILS CAT II"),
-            QStringLiteral("ILS CAT III"),
-            QStringLiteral("GLS"),
-            QStringLiteral("MLS"),
-            QStringLiteral("LOC"),
-            QStringLiteral("LOC/DME"),
-            QStringLiteral("RNAV"),
-            QStringLiteral("RNAV (LNAV)"),
-            QStringLiteral("RNAV (LNAV/VNAV)"),
-            QStringLiteral("RNAV (LPV)"),
-            QStringLiteral("RNAV (RNP)"),
-            QStringLiteral("RNAV (RNP-AR)"),
-            QStringLiteral("VOR"),
-            QStringLiteral("VOR/DME"),
-            QStringLiteral("NDB"),
-            QStringLiteral("NDB/DME"),
-            QStringLiteral("TACAN"),
-            QStringLiteral("SRA"),
-            QStringLiteral("PAR"),
-            QStringLiteral("OTHER")
-    };
-};
-
-//Make available as a global static
-Q_GLOBAL_STATIC(OplGlobals, GLOBALS)
-
-
-namespace Assets {
-
-const inline auto  DATABASE_SCHEMA               = QStringLiteral(":/database/database_schema.sql");
-const inline auto  DATABASE_TEMPLATE_AIRCRAFT    = QStringLiteral(":/database/templates/aircraft.json");
-const inline auto  DATABASE_TEMPLATE_AIRPORT     = QStringLiteral(":/database/templates/airports.json");
-const inline auto  DATABASE_TEMPLATE_CHANGELOG   = QStringLiteral(":/database/templates/changelog.json");
-
-const inline auto  LOGO                          = QStringLiteral(":/icons/opl-icons/logos/logo_text.png");
-const inline auto  ICON_MAIN                     = QStringLiteral(":/icons/opl-icons/app/icon_main.png");
-const inline auto  ICON_APPICON_LINUX            = QStringLiteral(":/icons/opl-icons/app/icon_linux.svg");
-const inline auto  ICON_APPICON_IOS              = QStringLiteral(":/icons/opl-icons/app/icon_ios.icns");
-const inline auto  ICON_APPICON_WIN              = QStringLiteral(":/icons/opl-icons/app/icon_windows.ico");
-
-const inline auto  ICON_TOOLBAR_HOME             = QStringLiteral(":/icons/opl-icons/toolbar/thick/light/icon_home.svg");
-const inline auto  ICON_TOOLBAR_NEW_FLIGHT       = QStringLiteral(":/icons/opl-icons/toolbar/thick/light/icon_new_flight.svg");
-const inline auto  ICON_TOOLBAR_LOGBOOK          = QStringLiteral(":/icons/opl-icons/toolbar/thick/light/icon_logbook.svg");
-const inline auto  ICON_TOOLBAR_AIRCRAFT         = QStringLiteral(":/icons/opl-icons/toolbar/thick/light/icon_airplane.svg");
-const inline auto  ICON_TOOLBAR_PILOT            = QStringLiteral(":/icons/opl-icons/toolbar/thick/light/icon_pilot.svg");
-const inline auto  ICON_TOOLBAR_SETTINGS         = QStringLiteral(":/icons/opl-icons/toolbar/thick/light/icon_settings.svg");
-const inline auto  ICON_TOOLBAR_QUIT             = QStringLiteral(":/icons/opl-icons/toolbar/thick/light/icon_exit.svg");
-
-const inline auto  ICON_TOOLBAR_BACKUP           = QStringLiteral(":/icons/opl-icons/toolbar/thick/light/icon_backup.svg");
-
-const inline auto  ICON_TOOLBAR_HOME_DARK        = QStringLiteral(":/icons/opl-icons/toolbar/thick/dark/icon_home_dm.svg");
-const inline auto  ICON_TOOLBAR_NEW_FLIGHT_DARK  = QStringLiteral(":/icons/opl-icons/toolbar/thick/dark/icon_new_flight_dm.svg");
-const inline auto  ICON_TOOLBAR_LOGBOOK_DARK     = QStringLiteral(":/icons/opl-icons/toolbar/thick/dark/icon_logbook_dm.svg");
-const inline auto  ICON_TOOLBAR_AIRCRAFT_DARK    = QStringLiteral(":/icons/opl-icons/toolbar/thick/dark/icon_airplane_dm.svg");
-const inline auto  ICON_TOOLBAR_PILOT_DARK       = QStringLiteral(":/icons/opl-icons/toolbar/thick/dark/icon_pilot_dm.svg");
-const inline auto  ICON_TOOLBAR_SETTINGS_DARK    = QStringLiteral(":/icons/opl-icons/toolbar/thick/dark/icon_settings_dm.svg");
-const inline auto  ICON_TOOLBAR_QUIT_DARK        = QStringLiteral(":/icons/opl-icons/toolbar/thick/dark/icon_exit_dm.svg");
-
-const inline auto  ICON_TOOLBAR_BACKUP_DARK      = QStringLiteral(":/icons/opl-icons/toolbar/thick/dark/icon_backup_dm.svg");
-
-}
-
-namespace CssStyles {
-
-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 RegEx {
-
-const inline auto RX_PHONE_NUMBER  = QRegularExpression(QStringLiteral("^[+]{0,1}[0-9\\-\\s]+"));
-const inline auto RX_TIME_ENTRY    = QRegularExpression(QStringLiteral("^(?:(?:([01]?\\d|2[0-3])(?::?)([0-5]\\d))|(?:([01]?\\d|2[0-3])([0-5]\\d))|(?:([1-9]|[1-9]\\d)\\:([0-5]\\d)?)|(?:([01]?\\d|2[0-3])\\.([0-5]?\\d)))$"));
-const inline auto RX_AIRPORT_CODE  = QRegularExpression(QStringLiteral("[a-zA-Z0-9]{1,4}"));
-
-} // namespace RegEx
-
-} // namespace opl
-
-#endif // OPLCONSTANTS_H
+/*
+ *openPilotLog - A FOSS Pilot Logbook Application
+ *Copyright (C) 2020-2023 Felix Turowsky
+ *
+ *This program is free software: you can redistribute it and/or modify
+ *it under the terms of the GNU General Public License as published by
+ *the Free Software Foundation, either version 3 of the License, or
+ *(at your option) any later version.
+ *
+ *This program is distributed in the hope that it will be useful,
+ *but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *GNU General Public License for more details.
+ *
+ *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 OPLCONSTANTS_H
+#define OPLCONSTANTS_H
+
+#include <QtCore>
+#include <QMessageBox>
+#include <QComboBox>
+
+#define APPNAME QStringLiteral("openPilotLog")
+#define ORGNAME QStringLiteral("opl")
+#define ORGDOMAIN QStringLiteral("https://github.com/fiffty-50/openpilotlog")
+#define OPL_VERSION 0
+#define OPL_SUBVERSION 1
+
+#if OPL_VERSION < 1
+    #define OPL_VERSION_STRING QString(QString::number(OPL_VERSION) + "." + QString::number(OPL_SUBVERSION)) + "-alpha"
+#else
+    #define OPL_VERSION_STRING QString(QString::number(OPL_VERSION) + "." + QString::number(OPL_SUBVERSION))
+#endif
+
+
+/*!
+ *  \brief A namespace to collect constants and enums used throughout the application.
+ *
+ *  \details The opl namespace collects enums and constants that are used throughout
+ *  the application and provide uniform access.
+ *
+ *  The date, time and datetime namespaces include enums used to differentiate
+ *  date and time formats for QDate, QTime and QDateTime that deviate from standard values
+ *  included in the Qt Framework like Qt::ISODate and are to be used in conjunction with the
+ *  .toString() members of these classes.
+ *
+ *  The db namespace contains constants for programatically accessing the database in a fast
+ *  and uniform manner.
+ */
+namespace OPL {
+
+#if defined(__GNUC__) || defined(__clang__)
+    #define FUNC_IDENT __PRETTY_FUNCTION__
+#elif defined(_MSC_VER)
+    #define FUNC_IDENT __FUNCSIG__
+#else
+    #define FUNC_IDENT __func__
+#endif
+
+#define DEB qDebug()                            // Use for debugging
+#define LOG qInfo()                             // Use for logging milestones (silently, will be written to log file and console out only)
+#define TODO qCritical() << "TO DO:\t"
+
+#define INFO(msg) OPL::ANotificationHandler::info(msg, this)  // Use for messages of interest to the user (will be displayed in GUI)
+#define WARN(msg) OPL::ANotificationHandler::warn(msg, this)  // Use for warnings (will be displayed in GUI)
+#define CRIT(msg) OPL::ANotificationHandler::crit(msg, this)  // Use for critical warnings (will be displayed in GUI)
+
+/**
+ * @brief Defines the row ID for non-user entries in the database;
+ */
+constexpr static int STUB_ROW_ID = -1;
+
+/**
+ * @brief Defines a four-letter code for a non-extistent (dummy) airport: "XXXX"
+ */
+constexpr static auto STUB_AIRPORT_CODE = QLatin1String("XXXX");
+/**
+ * @brief Defines a registration for a non-existent (dummy) aircraft: "XX-XXX"
+ */
+constexpr static auto STUB_AIRCRAFT_REG = QLatin1String("XX-XXX");
+
+/*!
+ * \brief The decimal seperator used internally
+ */
+constexpr static char DECIMAL_SEPERATOR = '.';
+
+
+/*!
+ * \brief The ANotificationHandler class handles displaying of user-directed messages. It displays
+ * information to the user in a QMessageBox and forwards the displayed message to ALog so it is written
+ * to the console and log files. The INFO, WARN and CRIT makros provide convenient access.
+ */
+class ANotificationHandler {
+public:
+    static inline void info(const QString msg, QWidget *parent = nullptr){
+
+        qInfo() << msg;
+        auto mb = QMessageBox(QMessageBox::Information, QStringLiteral("Info"), msg, QMessageBox::StandardButton::Ok, parent);
+        mb.exec();
+    };
+    static inline void warn(const QString msg, QWidget *parent = nullptr){
+        qWarning() << msg;
+        auto mb = QMessageBox(QMessageBox::Warning, QStringLiteral("Warning"), msg, QMessageBox::StandardButton::Ok, parent);
+        mb.exec();
+    };
+    static inline void crit(const QString msg, QWidget *parent = nullptr){
+        qCritical() << msg;
+        auto mb = QMessageBox(QMessageBox::Critical, QStringLiteral("Warning"), msg, QMessageBox::StandardButton::Ok, parent);
+        mb.exec();
+    };
+}; // class ANotificationHandler
+
+using RowData_T = QHash<QString, QVariant>;
+
+struct ToLdgCount_T {
+    int toDay;
+    int toNight;
+    int ldgDay;
+    int ldgNight;
+
+    ToLdgCount_T(int toDay, int toNight, int ldgDay, int ldgNight)
+        : toDay(toDay), toNight(toNight), ldgDay(ldgDay), ldgNight(ldgNight) {}
+};
+
+/*!
+ * \brief The DateFormat struct encapsulates how date and time values are displayed.
+ * \details Stores how the user wishes to display and enter Date and Time Entries.
+ * These are stored numerically in the database and thus need to be converted to
+ * human-readably form.
+ */
+struct DateTimeFormat {
+    /*!
+     * \brief Enumerates how dates can be formatted to a localised format.
+     * \value Default - The Application standard, Equivalent to Qt::ISODate
+     * \value SystemLocale - The current system locale date format
+     */
+    enum class DateFormat { Default, SystemLocale, Custom };
+
+    /*!
+     * \brief Enumerates how time values can be formatted
+     * \value Default - The application default is 'hh:mm'
+     * \value Decimal - Time as Decmial hours (01:30 == 1.5)
+     * \value Custom - A user-provided custom format string
+     */
+    enum class TimeFormat { Default, Decimal, Custom };
+
+    /*!
+     * \brief Initialise a DateTimeFormat instance with default values
+     */
+    DateTimeFormat()
+        : m_dateFormat(DateFormat::Default),
+        m_dateFormatString(QStringLiteral("yyyy-MM-dd")),
+        m_timeFormat(TimeFormat::Default),
+        m_timeFormatString(QStringLiteral("hh:mm"))
+    {}
+
+    DateTimeFormat(DateFormat dateFormat_,
+                   const QString &dateFormatString_,
+                   TimeFormat timeFormat_,
+                   const QString &timeFormatString_)
+        :
+        m_dateFormat(dateFormat_),
+        m_dateFormatString(dateFormatString_),
+        m_timeFormat(timeFormat_),
+        m_timeFormatString(timeFormatString_)
+    {}
+
+
+public:
+    DateFormat dateFormat() const { return m_dateFormat; }
+    TimeFormat timeFormat() const { return m_timeFormat; }
+    const QString &dateFormatString() const { return m_dateFormatString; }
+    const QString &timeFormatString() const { return m_timeFormatString; }
+
+private:
+    DateFormat m_dateFormat;
+    TimeFormat m_timeFormat;
+    QString m_dateFormatString;
+    QString m_timeFormatString;
+};
+
+/*!
+ * \brief ADateFormats enumerates the accepted date formats for QDateEdits
+ * \todo At the moment, only ISODate is accepet as a valid date format.
+ */
+enum class DateFormat {ISODate, DE, EN };
+
+enum class FlightTimeFormat {Default, Decimal};
+
+enum class DateTimeFormat_deprecated {Default, Backup};
+
+/*!
+ * \brief PilotFunction
+ * Pilot in Command, Pilot in Command under Supervision, Second in Command (Co-Pilot), Dual, Flight Instructor
+ */
+enum class PilotFunction {PIC = 0, PICUS = 1, SIC = 2, DUAL = 3, FI = 4};
+
+/*!
+ * \brief Enumerates the available translations
+ */
+enum class Translation {English, German, Spanish};
+
+/*!
+ * \brief Enumerates the available SQL views in the database
+ */
+enum class LogbookView {Default, DefaultWithSim, Easa, EasaWithSim, SimulatorOnly};
+
+/*!
+ * \brief Enumerates the Simulator Types: Flight and Navigation Procedures Trainer 1/2, Flight Simulation Training Device
+ */
+enum class SimulatorType {FNPTI = 0, FNPTII = 1, FSTD = 2};
+
+/*!
+ * \brief Enumerates the tables in the database
+ */
+enum class DbTable {Any, Flights, Simulators, Pilots, Tails, Aircraft, Airports, Currencies, PreviousExperience};
+
+/*!
+ * \brief Enumerates the currency names
+ */
+enum class CurrencyName {Licence = 1, TypeRating = 2, LineCheck = 3, Medical = 4, Custom1 = 5, Custom2 = 6};
+
+/*!
+ * \brief The OplGlobals class encapsulates non-POD globals to avoid making them static. It is available
+ * as a global static object via the OPL::GLOBAL makro and may be used as if it were a pointer, guaranteed to be initialized exactly once.
+ * For more information, see (Q_GLOBAL_STATIC)[https://doc.qt.io/qt-5/qglobalstatic.html#details]
+ */
+class OplGlobals : public QObject {
+public:
+    OplGlobals() = default;
+
+    void fillLanguageComboBox(QComboBox *combo_box) const;
+    void fillViewNamesComboBox(QComboBox *combo_box) const;
+    void loadPilotFunctios(QComboBox *combo_box) const;
+    void loadSimulatorTypes(QComboBox *combo_box) const;
+    void loadApproachTypes(QComboBox *combo_box) const;
+    void loadFlightRules(QComboBox *combo_box) const;
+
+    inline const QStringList &getApproachTypes() const {return APPROACH_TYPES;}
+    inline const QString getLanguageFilePath(Translation language) const {return L10N_FilePaths.value(language);}
+    inline const QString getViewIdentifier(LogbookView view_name) const {return DATABASE_VIEWS.value(view_name);}
+    inline const QString getDbTableName(DbTable table_name) const {return DB_TABLES.value(table_name);}
+
+private:
+    Q_OBJECT
+    const static inline QMap<Translation, QString> L10N_FilePaths {
+        {Translation::English, QStringLiteral("l10n/openpilotlog_en")},
+        {Translation::German,  QStringLiteral("l10n/openpilotlog_de")},
+        {Translation::Spanish, QStringLiteral("l10n/openpilotlog_es")},
+    };
+    const static inline QMap<Translation, QString> L10N_DisplayNames {
+        {Translation::English, QStringLiteral("English")},
+        {Translation::German,  QStringLiteral("Deutsch")},
+        {Translation::Spanish, QStringLiteral("Español")},
+    };
+    const static inline QMap<LogbookView, QString> DATABASE_VIEWS = {
+        {LogbookView::Default,        QStringLiteral("viewDefault")},
+        {LogbookView::DefaultWithSim, QStringLiteral("viewDefaultSim")},
+        {LogbookView::Easa,           QStringLiteral("viewEasa")},
+        {LogbookView::EasaWithSim,    QStringLiteral("viewEasaSim")},
+        {LogbookView::SimulatorOnly,  QStringLiteral("viewSimulators")},
+    };
+    const QMap<LogbookView, QString> DATABASE_VIEW_DISPLAY_NAMES = {
+        {LogbookView::Default,        tr("Default")},
+        {LogbookView::DefaultWithSim, tr("Default with Simulator")},
+        {LogbookView::Easa,           tr("EASA-FCL")},
+        {LogbookView::EasaWithSim,    tr("EASA-FCL with Simulator")},
+        {LogbookView::SimulatorOnly,  tr("Simulator Sessions Only")},
+    };
+    const static inline QMap<PilotFunction, QString> PILOT_FUNCTIONS = {
+        {PilotFunction::PIC,   QStringLiteral("PIC")},
+        {PilotFunction::PICUS, QStringLiteral("PICUS")},
+        {PilotFunction::SIC,   QStringLiteral("SIC")},
+        {PilotFunction::DUAL,  QStringLiteral("DUAL")},
+        {PilotFunction::FI,    QStringLiteral("FI")},
+    };
+    const static inline QMap<SimulatorType, QString> SIMULATOR_TYPES = {
+        {SimulatorType::FNPTI,  QStringLiteral("FNPT I")},
+        {SimulatorType::FNPTII, QStringLiteral("FNPT II")},
+        {SimulatorType::FSTD,   QStringLiteral("FSTD")},
+    };
+    const static inline QMap<DbTable, QString> DB_TABLES = {
+        {DbTable::Flights,      QStringLiteral("flights")},
+        {DbTable::Simulators,   QStringLiteral("simulators")},
+        {DbTable::Pilots,       QStringLiteral("pilots")},
+        {DbTable::Tails,        QStringLiteral("tails")},
+        {DbTable::Aircraft,     QStringLiteral("aircraft")},
+        {DbTable::Airports,     QStringLiteral("airports")},
+        {DbTable::Currencies,   QStringLiteral("currencies")},
+        {DbTable::PreviousExperience,    QStringLiteral("previousExperience")},
+    };
+
+    const static inline QStringList APPROACH_TYPES = {
+            QStringLiteral("VISUAL"),
+            QStringLiteral("ILS CAT I"),
+            QStringLiteral("ILS CAT II"),
+            QStringLiteral("ILS CAT III"),
+            QStringLiteral("GLS"),
+            QStringLiteral("MLS"),
+            QStringLiteral("LOC"),
+            QStringLiteral("LOC/DME"),
+            QStringLiteral("RNAV"),
+            QStringLiteral("RNAV (LNAV)"),
+            QStringLiteral("RNAV (LNAV/VNAV)"),
+            QStringLiteral("RNAV (LPV)"),
+            QStringLiteral("RNAV (RNP)"),
+            QStringLiteral("RNAV (RNP-AR)"),
+            QStringLiteral("VOR"),
+            QStringLiteral("VOR/DME"),
+            QStringLiteral("NDB"),
+            QStringLiteral("NDB/DME"),
+            QStringLiteral("TACAN"),
+            QStringLiteral("SRA"),
+            QStringLiteral("PAR"),
+            QStringLiteral("OTHER")
+    };
+};
+
+//Make available as a global static
+Q_GLOBAL_STATIC(OplGlobals, GLOBALS)
+
+
+namespace Assets {
+
+const inline auto  DATABASE_SCHEMA               = QStringLiteral(":/database/database_schema.sql");
+const inline auto  DATABASE_TEMPLATE_AIRCRAFT    = QStringLiteral(":/database/templates/aircraft.json");
+const inline auto  DATABASE_TEMPLATE_AIRPORT     = QStringLiteral(":/database/templates/airports.json");
+
+const inline auto  LOGO                          = QStringLiteral(":/icons/opl-icons/logos/logo_text.png");
+const inline auto  ICON_MAIN                     = QStringLiteral(":/icons/opl-icons/app/icon_main.png");
+const inline auto  ICON_APPICON_LINUX            = QStringLiteral(":/icons/opl-icons/app/icon_linux.svg");
+const inline auto  ICON_APPICON_IOS              = QStringLiteral(":/icons/opl-icons/app/icon_ios.icns");
+const inline auto  ICON_APPICON_WIN              = QStringLiteral(":/icons/opl-icons/app/icon_windows.ico");
+
+const inline auto  ICON_TOOLBAR_HOME             = QStringLiteral(":/icons/opl-icons/toolbar/thick/light/icon_home.svg");
+const inline auto  ICON_TOOLBAR_NEW_FLIGHT       = QStringLiteral(":/icons/opl-icons/toolbar/thick/light/icon_new_flight.svg");
+const inline auto  ICON_TOOLBAR_LOGBOOK          = QStringLiteral(":/icons/opl-icons/toolbar/thick/light/icon_logbook.svg");
+const inline auto  ICON_TOOLBAR_AIRCRAFT         = QStringLiteral(":/icons/opl-icons/toolbar/thick/light/icon_airplane.svg");
+const inline auto  ICON_TOOLBAR_PILOT            = QStringLiteral(":/icons/opl-icons/toolbar/thick/light/icon_pilot.svg");
+const inline auto  ICON_TOOLBAR_SETTINGS         = QStringLiteral(":/icons/opl-icons/toolbar/thick/light/icon_settings.svg");
+const inline auto  ICON_TOOLBAR_QUIT             = QStringLiteral(":/icons/opl-icons/toolbar/thick/light/icon_exit.svg");
+
+const inline auto  ICON_TOOLBAR_BACKUP           = QStringLiteral(":/icons/opl-icons/toolbar/thick/light/icon_backup.svg");
+
+const inline auto  ICON_TOOLBAR_HOME_DARK        = QStringLiteral(":/icons/opl-icons/toolbar/thick/dark/icon_home_dm.svg");
+const inline auto  ICON_TOOLBAR_NEW_FLIGHT_DARK  = QStringLiteral(":/icons/opl-icons/toolbar/thick/dark/icon_new_flight_dm.svg");
+const inline auto  ICON_TOOLBAR_LOGBOOK_DARK     = QStringLiteral(":/icons/opl-icons/toolbar/thick/dark/icon_logbook_dm.svg");
+const inline auto  ICON_TOOLBAR_AIRCRAFT_DARK    = QStringLiteral(":/icons/opl-icons/toolbar/thick/dark/icon_airplane_dm.svg");
+const inline auto  ICON_TOOLBAR_PILOT_DARK       = QStringLiteral(":/icons/opl-icons/toolbar/thick/dark/icon_pilot_dm.svg");
+const inline auto  ICON_TOOLBAR_SETTINGS_DARK    = QStringLiteral(":/icons/opl-icons/toolbar/thick/dark/icon_settings_dm.svg");
+const inline auto  ICON_TOOLBAR_QUIT_DARK        = QStringLiteral(":/icons/opl-icons/toolbar/thick/dark/icon_exit_dm.svg");
+
+const inline auto  ICON_TOOLBAR_BACKUP_DARK      = QStringLiteral(":/icons/opl-icons/toolbar/thick/dark/icon_backup_dm.svg");
+
+}
+
+namespace CssStyles {
+
+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 RegEx {
+
+const inline auto RX_PHONE_NUMBER  = QRegularExpression(QStringLiteral("^[+]{0,1}[0-9\\-\\s]+"));
+const inline auto RX_TIME_ENTRY    = QRegularExpression(QStringLiteral("^(?:(?:([01]?\\d|2[0-3])(?::?)([0-5]\\d))|(?:([01]?\\d|2[0-3])([0-5]\\d))|(?:([1-9]|[1-9]\\d)\\:([0-5]\\d)?)|(?:([01]?\\d|2[0-3])\\.([0-5]?\\d)))$"));
+const inline auto RX_AIRPORT_CODE  = QRegularExpression(QStringLiteral("[a-zA-Z0-9]{1,4}"));
+
+} // namespace RegEx
+
+} // namespace opl
+
+#endif // OPLCONSTANTS_H

+ 70 - 70
src/testing/importCrewlounge/processaircraft.cpp

@@ -1,70 +1,70 @@
-#include "processaircraft.h"
-#include "src/database/tailentry.h"
-
-void ProcessAircraft::parseRawData()
-{
-    QStringList tail_details;
-    // relevant colums: {reg,company,make,model,variant,multipilot,multiengine,engineType,weightClass}
-    int relevant_cols[9] = {79, 36, 76, 77, 78, 83, 84, 92, 96};
-
-    for (const auto &row : qAsConst(rawData)) {
-        for (const auto &col : relevant_cols) {
-            tail_details.append(row[col]);
-        }
-        if (!(unique_tails.contains(tail_details)))
-            unique_tails.append(tail_details);
-        tail_details.clear();
-    }
-}
-
-void ProcessAircraft::processParsedData()
-{
-    // init counter
-    int unique_tail_id = 1;
-
-    QHash<QString, QVariant> new_tail_data;
-    for (const auto &list : qAsConst(unique_tails)) {
-        new_tail_data.insert(OPL::TailEntry::REGISTRATION, list[0]);
-        new_tail_data.insert(OPL::TailEntry::COMPANY, list[1]);
-        new_tail_data.insert(OPL::TailEntry::MAKE, list[2]);
-        new_tail_data.insert(OPL::TailEntry::MODEL, list[3]);
-        new_tail_data.insert(OPL::TailEntry::VARIANT, list[4]);
-
-        if (list[5] == "TRUE")
-            new_tail_data.insert(OPL::TailEntry::MULTI_PILOT, 1);
-        else
-            new_tail_data.insert(OPL::TailEntry::MULTI_PILOT, 0);
-
-        if (list[6] == "TRUE")
-            new_tail_data.insert(OPL::TailEntry::MULTI_ENGINE, 1);
-        else
-            new_tail_data.insert(OPL::TailEntry::MULTI_ENGINE, 0);
-
-        if (list[7] == "Piston") // other values need to be added as needed, do later
-            new_tail_data.insert(OPL::TailEntry::ENGINE_TYPE, 1);
-        else if (list[7] == "Turbine (jet-fan)")
-            new_tail_data.insert(OPL::TailEntry::ENGINE_TYPE, 3);
-
-        if (list[8] == "TRUE") // this is a above 7.5t switch in MCC, so default to medium for now
-            new_tail_data.insert(OPL::TailEntry::WEIGHT_CLASS, 1);
-        else
-            new_tail_data.insert(OPL::TailEntry::WEIGHT_CLASS, 0);
-
-        new_tail_data.insert(QStringLiteral("tail_id"), unique_tail_id);
-
-        processedTailIds.insert(list[0], unique_tail_id);
-        processedTailMaps.insert(list[0], new_tail_data);
-        unique_tail_id ++;
-        new_tail_data.clear();
-    }
-}
-
-QHash<QString, int> ProcessAircraft::getProcessedTailIds() const
-{
-    return processedTailIds;
-}
-
-QHash<QString, QHash<QString, QVariant> > ProcessAircraft::getProcessedTailMaps() const
-{
-    return processedTailMaps;
-}
+#include "processaircraft.h"
+#include "src/database/tailentry.h"
+
+void ProcessAircraft::parseRawData()
+{
+    QStringList tail_details;
+    // relevant colums: {reg,company,make,model,variant,multipilot,multiengine,engineType,weightClass}
+    int relevant_cols[9] = {79, 36, 76, 77, 78, 83, 84, 92, 96};
+
+    for (const auto &row : std::as_const(rawData)) {
+        for (const auto &col : relevant_cols) {
+            tail_details.append(row[col]);
+        }
+        if (!(unique_tails.contains(tail_details)))
+            unique_tails.append(tail_details);
+        tail_details.clear();
+    }
+}
+
+void ProcessAircraft::processParsedData()
+{
+    // init counter
+    int unique_tail_id = 1;
+
+    QHash<QString, QVariant> new_tail_data;
+    for (const auto &list : std::as_const(unique_tails)) {
+        new_tail_data.insert(OPL::TailEntry::REGISTRATION, list[0]);
+        new_tail_data.insert(OPL::TailEntry::COMPANY, list[1]);
+        new_tail_data.insert(OPL::TailEntry::MAKE, list[2]);
+        new_tail_data.insert(OPL::TailEntry::MODEL, list[3]);
+        new_tail_data.insert(OPL::TailEntry::VARIANT, list[4]);
+
+        if (list[5] == "TRUE")
+            new_tail_data.insert(OPL::TailEntry::MULTI_PILOT, 1);
+        else
+            new_tail_data.insert(OPL::TailEntry::MULTI_PILOT, 0);
+
+        if (list[6] == "TRUE")
+            new_tail_data.insert(OPL::TailEntry::MULTI_ENGINE, 1);
+        else
+            new_tail_data.insert(OPL::TailEntry::MULTI_ENGINE, 0);
+
+        if (list[7] == "Piston") // other values need to be added as needed, do later
+            new_tail_data.insert(OPL::TailEntry::ENGINE_TYPE, 1);
+        else if (list[7] == "Turbine (jet-fan)")
+            new_tail_data.insert(OPL::TailEntry::ENGINE_TYPE, 3);
+
+        if (list[8] == "TRUE") // this is a above 7.5t switch in MCC, so default to medium for now
+            new_tail_data.insert(OPL::TailEntry::WEIGHT_CLASS, 1);
+        else
+            new_tail_data.insert(OPL::TailEntry::WEIGHT_CLASS, 0);
+
+        new_tail_data.insert(QStringLiteral("tail_id"), unique_tail_id);
+
+        processedTailIds.insert(list[0], unique_tail_id);
+        processedTailMaps.insert(list[0], new_tail_data);
+        unique_tail_id ++;
+        new_tail_data.clear();
+    }
+}
+
+QHash<QString, int> ProcessAircraft::getProcessedTailIds() const
+{
+    return processedTailIds;
+}
+
+QHash<QString, QHash<QString, QVariant> > ProcessAircraft::getProcessedTailMaps() const
+{
+    return processedTailMaps;
+}

+ 90 - 90
src/testing/importCrewlounge/processflights.cpp

@@ -1,90 +1,90 @@
-#include "processflights.h"
-#include "src/classes/time.h"
-#include "src/database/flightentry.h"
-
-void ProcessFlights::parseRawData()
-{
-    // doft, flightNumber, dept, dest, tofb, tonb, tblk, tPIC, tSIC, tDUAL, tPICUS, tFI, tNight, pic, secondPilot, thirdPilot   toDN,ldDN   pilotFlying, appType, remarks,  acftReg
-    int relevant_cols[24] = {0,3,5,7,9,11,17,19,20,21,22,23,25,38,42,46,53,54,55,56,58,60,64,79};
-    QStringList row_data;
-    for (const auto &row : qAsConst(rawData)) {
-        for (const auto &col : relevant_cols) {
-            row_data.append(row[col]);
-        }
-        rawFlightData.append(row_data);
-        row_data.clear();
-    }
-    DEB << "Flight Info #1742:" << rawFlightData[1742];
-}
-
-void ProcessFlights::processParsedData()
-{
-    QHash<QString, QVariant> new_flight_data;
-    int flight_id = 1;
-
-    for (const auto &row : qAsConst(rawFlightData)) {
-        // insert values that don't require editing
-        new_flight_data.insert(OPL::FlightEntry::FLIGHTNUMBER, row[1]);
-        new_flight_data.insert(OPL::FlightEntry::DEPT, row[2]);
-        new_flight_data.insert(OPL::FlightEntry::DEST, row[3]);
-        new_flight_data.insert(OPL::FlightEntry::TBLK, row[6]);
-        new_flight_data.insert(OPL::FlightEntry::TPIC, row[7]);
-        new_flight_data.insert(OPL::FlightEntry::TSIC, row[8]);
-        new_flight_data.insert(OPL::FlightEntry::TDUAL, row[9]);
-        new_flight_data.insert(OPL::FlightEntry::TPICUS, row[10]);
-        new_flight_data.insert(OPL::FlightEntry::TFI, row[11]);
-        new_flight_data.insert(OPL::FlightEntry::TNIGHT, row[12]);
-        new_flight_data.insert(OPL::FlightEntry::TODAY, row[16]);
-        new_flight_data.insert(OPL::FlightEntry::TONIGHT, row[17]);
-        new_flight_data.insert(OPL::FlightEntry::LDGDAY, row[18]);
-        new_flight_data.insert(OPL::FlightEntry::LDGNIGHT, row[19]);
-        new_flight_data.insert(OPL::FlightEntry::APPROACHTYPE, row[21]);
-        new_flight_data.insert(OPL::FlightEntry::REMARKS, row[22]);
-
-        // PF
-        if (row[20] == QLatin1String("TRUE"))
-            new_flight_data.insert(OPL::FlightEntry::PILOTFLYING, 1);
-        else
-            new_flight_data.insert(OPL::FlightEntry::PILOTFLYING, 0);
-
-        // Convert Date and Time
-        const QDate doft = QDate::fromString(row[0],QStringLiteral("dd/MM/yyyy"));
-        new_flight_data.insert(OPL::FlightEntry::DOFT, doft.toString(Qt::ISODate));
-
-        auto time_off = QTime::fromString(row[4], QStringLiteral("hh:mm"));
-        if (!time_off.isValid())
-            time_off = QTime::fromString(row[4], QStringLiteral("h:mm"));
-        int tofb = OPL::Time::fromString(time_off.toString(), OPL::DateTimeFormat()).toMinutes();
-        new_flight_data.insert(OPL::FlightEntry::TOFB, tofb);
-
-        auto time_on = QTime::fromString(row[5], QStringLiteral("hh:mm"));
-        if (!time_on.isValid())
-            time_on = QTime::fromString(row[5], QStringLiteral("h:mm"));
-
-        int tonb = OPL::Time::fromString(time_on.toString(), OPL::DateTimeFormat()).toMinutes();
-        new_flight_data.insert(OPL::FlightEntry::TONB, tonb);
-
-        // map pilots
-        int pic = processedPilotsIds.value(row[13]);
-        new_flight_data.insert(OPL::FlightEntry::PIC, pic);
-        int second_pilot = processedPilotsIds.value(row[14]);
-        new_flight_data.insert(OPL::FlightEntry::SECONDPILOT, second_pilot);
-        int third_pilot = processedPilotsIds.value(row[15]);
-        new_flight_data.insert(OPL::FlightEntry::THIRDPILOT, third_pilot);
-
-        // map tail
-        int acft = processedTailsIds.value(row[23]);
-        new_flight_data.insert(OPL::FlightEntry::ACFT, acft);
-
-        // set id, fix opl to include alias
-        new_flight_data.insert(QStringLiteral("flight_id"), flight_id);
-        processedFlights.append(new_flight_data);
-        new_flight_data.clear();
-        flight_id ++;
-    }
-}
-
-QVector<QHash<QString, QVariant> > ProcessFlights::getProcessedFlights() const
-{
-    return processedFlights;
-}
+#include "processflights.h"
+#include "src/classes/time.h"
+#include "src/database/flightentry.h"
+
+void ProcessFlights::parseRawData()
+{
+    // doft, flightNumber, dept, dest, tofb, tonb, tblk, tPIC, tSIC, tDUAL, tPICUS, tFI, tNight, pic, secondPilot, thirdPilot   toDN,ldDN   pilotFlying, appType, remarks,  acftReg
+    int relevant_cols[24] = {0,3,5,7,9,11,17,19,20,21,22,23,25,38,42,46,53,54,55,56,58,60,64,79};
+    QStringList row_data;
+    for (const auto &row : std::as_const(rawData)) {
+        for (const auto &col : relevant_cols) {
+            row_data.append(row[col]);
+        }
+        rawFlightData.append(row_data);
+        row_data.clear();
+    }
+    DEB << "Flight Info #1742:" << rawFlightData[1742];
+}
+
+void ProcessFlights::processParsedData()
+{
+    QHash<QString, QVariant> new_flight_data;
+    int flight_id = 1;
+
+    for (const auto &row : std::as_const(rawFlightData)) {
+        // insert values that don't require editing
+        new_flight_data.insert(OPL::FlightEntry::FLIGHTNUMBER, row[1]);
+        new_flight_data.insert(OPL::FlightEntry::DEPT, row[2]);
+        new_flight_data.insert(OPL::FlightEntry::DEST, row[3]);
+        new_flight_data.insert(OPL::FlightEntry::TBLK, row[6]);
+        new_flight_data.insert(OPL::FlightEntry::TPIC, row[7]);
+        new_flight_data.insert(OPL::FlightEntry::TSIC, row[8]);
+        new_flight_data.insert(OPL::FlightEntry::TDUAL, row[9]);
+        new_flight_data.insert(OPL::FlightEntry::TPICUS, row[10]);
+        new_flight_data.insert(OPL::FlightEntry::TFI, row[11]);
+        new_flight_data.insert(OPL::FlightEntry::TNIGHT, row[12]);
+        new_flight_data.insert(OPL::FlightEntry::TODAY, row[16]);
+        new_flight_data.insert(OPL::FlightEntry::TONIGHT, row[17]);
+        new_flight_data.insert(OPL::FlightEntry::LDGDAY, row[18]);
+        new_flight_data.insert(OPL::FlightEntry::LDGNIGHT, row[19]);
+        new_flight_data.insert(OPL::FlightEntry::APPROACHTYPE, row[21]);
+        new_flight_data.insert(OPL::FlightEntry::REMARKS, row[22]);
+
+        // PF
+        if (row[20] == QLatin1String("TRUE"))
+            new_flight_data.insert(OPL::FlightEntry::PILOTFLYING, 1);
+        else
+            new_flight_data.insert(OPL::FlightEntry::PILOTFLYING, 0);
+
+        // Convert Date and Time
+        const QDate doft = QDate::fromString(row[0],QStringLiteral("dd/MM/yyyy"));
+        new_flight_data.insert(OPL::FlightEntry::DOFT, doft.toString(Qt::ISODate));
+
+        auto time_off = QTime::fromString(row[4], QStringLiteral("hh:mm"));
+        if (!time_off.isValid())
+            time_off = QTime::fromString(row[4], QStringLiteral("h:mm"));
+        int tofb = OPL::Time::fromString(time_off.toString(), OPL::DateTimeFormat()).toMinutes();
+        new_flight_data.insert(OPL::FlightEntry::TOFB, tofb);
+
+        auto time_on = QTime::fromString(row[5], QStringLiteral("hh:mm"));
+        if (!time_on.isValid())
+            time_on = QTime::fromString(row[5], QStringLiteral("h:mm"));
+
+        int tonb = OPL::Time::fromString(time_on.toString(), OPL::DateTimeFormat()).toMinutes();
+        new_flight_data.insert(OPL::FlightEntry::TONB, tonb);
+
+        // map pilots
+        int pic = processedPilotsIds.value(row[13]);
+        new_flight_data.insert(OPL::FlightEntry::PIC, pic);
+        int second_pilot = processedPilotsIds.value(row[14]);
+        new_flight_data.insert(OPL::FlightEntry::SECONDPILOT, second_pilot);
+        int third_pilot = processedPilotsIds.value(row[15]);
+        new_flight_data.insert(OPL::FlightEntry::THIRDPILOT, third_pilot);
+
+        // map tail
+        int acft = processedTailsIds.value(row[23]);
+        new_flight_data.insert(OPL::FlightEntry::ACFT, acft);
+
+        // set id, fix opl to include alias
+        new_flight_data.insert(QStringLiteral("flight_id"), flight_id);
+        processedFlights.append(new_flight_data);
+        new_flight_data.clear();
+        flight_id ++;
+    }
+}
+
+QVector<QHash<QString, QVariant> > ProcessFlights::getProcessedFlights() const
+{
+    return processedFlights;
+}

+ 85 - 85
src/testing/importCrewlounge/processpilots.cpp

@@ -1,85 +1,85 @@
-#include "processpilots.h"
-#include "src/database/pilotentry.h"
-
-void ProcessPilots::parseRawData()
-{
-    const QVector<int> cols_pilot1 = {37, 38, 39, 40}; //empId, name, phone, email
-    const QVector<int> cols_pilot2 = {41, 42, 43, 44};
-    const QVector<int> cols_pilot3 = {45, 46, 47, 48};
-    const QVector<QVector<int>> pilot_cols = {
-        cols_pilot1,
-        cols_pilot2,
-        cols_pilot3
-    };
-
-    QVector<QStringList> unique_pilots;
-    QStringList pilot_data = {
-        QString(),
-        QStringLiteral("SELF"),
-        QString(),
-        QString()
-    };
-    unique_pilots.append(pilot_data);
-    int unique_pilot_id = 1;
-    processedPilotsIds.insert(pilot_data[1], unique_pilot_id);
-    rawPilotsAndIds.append({pilot_data, unique_pilot_id});
-    pilot_data.clear();
-    unique_pilot_id ++;
-
-
-    for (const auto &row : qAsConst(rawData)) {
-        for (const auto &col_array : pilot_cols) {
-            for (const auto &col : col_array) {
-                pilot_data.append(row[col]);
-            }
-            if (!unique_pilots.contains(pilot_data) && !pilot_data.contains(QLatin1String("SELF"))) {
-                unique_pilots.append(pilot_data);
-                processedPilotsIds.insert(pilot_data[1], unique_pilot_id);
-                rawPilotsAndIds.append({pilot_data, unique_pilot_id});
-                unique_pilot_id ++;
-            }
-            pilot_data.clear();
-        }
-    }
-}
-
-void ProcessPilots::processParsedData()
-{
-    for (const auto &pair : qAsConst(rawPilotsAndIds)) {
-        //DEB << "ID:" << pair.second << "Details:" << pair.first;
-        QHash<QString, QVariant> new_pilot_data;
-
-        // process name [1]
-        auto temp_list = pair.first[1].split(QLatin1Char(' '));
-        if (!temp_list.isEmpty()) {
-            new_pilot_data.insert(OPL::PilotEntry::LASTNAME, temp_list.first());
-            temp_list.pop_front();
-
-            if (!temp_list.isEmpty())
-                new_pilot_data.insert(OPL::PilotEntry::FIRSTNAME, temp_list.join(QLatin1Char(' ')));
-        } else {
-            new_pilot_data.insert(OPL::PilotEntry::LASTNAME, QStringLiteral("UNKNOWN"));
-        }
-
-        // add additional data
-        new_pilot_data.insert(OPL::PilotEntry::EMPLOYEEID, pair.first[0]);
-        new_pilot_data.insert(OPL::PilotEntry::PHONE, pair.first[2]);
-        new_pilot_data.insert(OPL::PilotEntry::EMAIL, pair.first[3]);
-
-        // add pilot_id (workaround with literal until OPL::Db is updated)
-        new_pilot_data.insert(QStringLiteral("pilot_id"), pair.second);
-
-        processedPilotHashes.insert(pair.first[1], new_pilot_data);
-        processedPilotsIds.insert(pair.first[1], pair.second);
-    }
-}
-
-QHash<QString, QHash<QString, QVariant>> ProcessPilots::getProcessedPilotMaps() const
-{
-    return processedPilotHashes;
-}
-
-QHash<QString, int> ProcessPilots::getProcessedPilotsIds() const
-{
-    return processedPilotsIds;
-}
+#include "processpilots.h"
+#include "src/database/pilotentry.h"
+
+void ProcessPilots::parseRawData()
+{
+    const QVector<int> cols_pilot1 = {37, 38, 39, 40}; //empId, name, phone, email
+    const QVector<int> cols_pilot2 = {41, 42, 43, 44};
+    const QVector<int> cols_pilot3 = {45, 46, 47, 48};
+    const QVector<QVector<int>> pilot_cols = {
+        cols_pilot1,
+        cols_pilot2,
+        cols_pilot3
+    };
+
+    QVector<QStringList> unique_pilots;
+    QStringList pilot_data = {
+        QString(),
+        QStringLiteral("SELF"),
+        QString(),
+        QString()
+    };
+    unique_pilots.append(pilot_data);
+    int unique_pilot_id = 1;
+    processedPilotsIds.insert(pilot_data[1], unique_pilot_id);
+    rawPilotsAndIds.append({pilot_data, unique_pilot_id});
+    pilot_data.clear();
+    unique_pilot_id ++;
+
+
+    for (const auto &row : std::as_const(rawData)) {
+        for (const auto &col_array : pilot_cols) {
+            for (const auto &col : col_array) {
+                pilot_data.append(row[col]);
+            }
+            if (!unique_pilots.contains(pilot_data) && !pilot_data.contains(QLatin1String("SELF"))) {
+                unique_pilots.append(pilot_data);
+                processedPilotsIds.insert(pilot_data[1], unique_pilot_id);
+                rawPilotsAndIds.append({pilot_data, unique_pilot_id});
+                unique_pilot_id ++;
+            }
+            pilot_data.clear();
+        }
+    }
+}
+
+void ProcessPilots::processParsedData()
+{
+    for (const auto &pair : std::as_const(rawPilotsAndIds)) {
+        //DEB << "ID:" << pair.second << "Details:" << pair.first;
+        QHash<QString, QVariant> new_pilot_data;
+
+        // process name [1]
+        auto temp_list = pair.first[1].split(QLatin1Char(' '));
+        if (!temp_list.isEmpty()) {
+            new_pilot_data.insert(OPL::PilotEntry::LASTNAME, temp_list.first());
+            temp_list.pop_front();
+
+            if (!temp_list.isEmpty())
+                new_pilot_data.insert(OPL::PilotEntry::FIRSTNAME, temp_list.join(QLatin1Char(' ')));
+        } else {
+            new_pilot_data.insert(OPL::PilotEntry::LASTNAME, QStringLiteral("UNKNOWN"));
+        }
+
+        // add additional data
+        new_pilot_data.insert(OPL::PilotEntry::EMPLOYEEID, pair.first[0]);
+        new_pilot_data.insert(OPL::PilotEntry::PHONE, pair.first[2]);
+        new_pilot_data.insert(OPL::PilotEntry::EMAIL, pair.first[3]);
+
+        // add pilot_id (workaround with literal until OPL::Db is updated)
+        new_pilot_data.insert(QStringLiteral("pilot_id"), pair.second);
+
+        processedPilotHashes.insert(pair.first[1], new_pilot_data);
+        processedPilotsIds.insert(pair.first[1], pair.second);
+    }
+}
+
+QHash<QString, QHash<QString, QVariant>> ProcessPilots::getProcessedPilotMaps() const
+{
+    return processedPilotHashes;
+}
+
+QHash<QString, int> ProcessPilots::getProcessedPilotsIds() const
+{
+    return processedPilotsIds;
+}