浏览代码

Created a prototype for Table Editing

The TableEditWidget provides an interface for editing the simpler tables in the database (the ones that don't require more complex input verification). Creating a base class that handles most of the common tasks seems more manageable in the long term even though it's a lot of refactoring now
Felix Turowsky 1 年之前
父节点
当前提交
08b1cbdade

+ 8 - 1
CMakeLists.txt

@@ -53,6 +53,9 @@ set(PROJECT_SOURCES
     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
+
     # Widgets
     src/gui/widgets/tailswidget.h
     src/gui/widgets/tailswidget.cpp
@@ -81,6 +84,11 @@ set(PROJECT_SOURCES
     src/gui/widgets/totalswidget.h
     src/gui/widgets/totalswidget.cpp
     src/gui/widgets/totalswidget.ui
+    src/gui/widgets/tableeditwidget.h
+    src/gui/widgets/tableeditwidget.cpp
+    src/gui/widgets/pilottableeditwidget.h
+    src/gui/widgets/pilottableeditwidget.cpp
+
     # Verification
     src/gui/verification/validationstate.h
     src/gui/verification/userinput.h
@@ -98,7 +106,6 @@ set(PROJECT_SOURCES
     src/gui/widgets/currencywidget.h
     src/gui/widgets/currencywidget.cpp
 
-
     # Classes
     src/classes/style.h
     src/classes/style.cpp

+ 15 - 8
mainwindow.cpp

@@ -17,6 +17,7 @@
  */
 #include <QToolBar>
 #include "mainwindow.h"
+#include "src/gui/widgets/pilottableeditwidget.h"
 #include "ui_mainwindow.h"
 #include "src/database/database.h"
 #include "src/classes/style.h"
@@ -29,7 +30,10 @@
 // Quick and dirty Debug area
 void MainWindow::doDebugStuff()
 {
-
+    PilotTableEditWidget *widget = new PilotTableEditWidget(this);
+    widget->init();
+    widget->setWindowFlags(Qt::Dialog);
+    widget->show();
 }
 
 MainWindow::MainWindow(QWidget *parent)
@@ -88,7 +92,10 @@ void MainWindow::initialiseWidgets()
     aircraftWidget = new TailsWidget(this);
     ui->stackedWidget->addWidget(aircraftWidget);
 
-    pilotsWidget = new PilotsWidget(this);
+//    pilotsWidget = new PilotsWidget(this);
+//    ui->stackedWidget->addWidget(pilotsWidget);
+    pilotsWidget = new PilotTableEditWidget(this);
+    pilotsWidget->init();
     ui->stackedWidget->addWidget(pilotsWidget);
 
     airportWidget = new AirportWidget(this);
@@ -174,17 +181,17 @@ void MainWindow::connectWidgets()
     QObject::connect(settingsWidget, &SettingsWidget::settingChanged,
                      aircraftWidget, &TailsWidget::onAircraftWidget_settingChanged);
 
-    QObject::connect(DB,             &OPL::Database::dataBaseUpdated,
-                     pilotsWidget,   &PilotsWidget::onPilotsWidget_databaseUpdated);
-    QObject::connect(settingsWidget, &SettingsWidget::settingChanged,
-                     pilotsWidget,   &PilotsWidget::onPilotsWidget_settingChanged);
+//    QObject::connect(DB,             &OPL::Database::dataBaseUpdated,
+//                     pilotsWidget,   &PilotsWidget::onPilotsWidget_databaseUpdated);
+//    QObject::connect(settingsWidget, &SettingsWidget::settingChanged,
+//                     pilotsWidget,   &PilotsWidget::onPilotsWidget_settingChanged);
     QObject::connect(settingsWidget, &SettingsWidget::settingChanged,
                      this,           &MainWindow::onStyleChanged);
 
     QObject::connect(DB,              &OPL::Database::connectionReset,
                      logbookWidget,   &LogbookWidget::repopulateModel);
-    QObject::connect(DB,              &OPL::Database::connectionReset,
-                     pilotsWidget,    &PilotsWidget::repopulateModel);
+//    QObject::connect(DB,              &OPL::Database::connectionReset,
+//                     pilotsWidget,    &PilotsWidget::repopulateModel);
     QObject::connect(DB,              &OPL::Database::connectionReset,
                      aircraftWidget,  &TailsWidget::repopulateModel);
 }

+ 2 - 1
mainwindow.h

@@ -32,6 +32,7 @@
 #include "src/gui/widgets/homewidget.h"
 #include "src/gui/widgets/settingswidget.h"
 #include "src/gui/widgets/logbookwidget.h"
+#include "src/gui/widgets/tableeditwidget.h"
 #include "src/gui/widgets/tailswidget.h"
 #include "src/gui/widgets/airportwidget.h"
 #include "src/gui/widgets/airportwidget.h"
@@ -125,7 +126,7 @@ private:
 
     TailsWidget* aircraftWidget;
 
-    PilotsWidget* pilotsWidget;
+    TableEditWidget* pilotsWidget;
 
     AirportWidget* airportWidget;
 

+ 4 - 4
src/classes/date.h

@@ -29,6 +29,10 @@ public:
 
     Date(int julianDay);
 
+    const QString toString(Format format = Format::Default) const;
+
+    const bool isValid() { return date.isValid(); }
+
     const static inline Date fromString(const QString &dateString, Format format = Default) {
         switch (format) {
         case Default:
@@ -49,10 +53,6 @@ public:
 
     const static inline QString julianDayToString(int julianDay) { return Date(julianDay).toString(); }
 
-    const QString toString(Format format = Format::Default) const;
-
-    const bool isValid() { return date.isValid(); }
-
 private:
     QDate date;
 

+ 23 - 0
src/database/flightentry.cpp

@@ -16,6 +16,8 @@
  *along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 #include "flightentry.h"
+#include "src/classes/date.h"
+#include "src/classes/time.h"
 
 namespace OPL {
 
@@ -36,4 +38,25 @@ const QString FlightEntry::getTableName() const
     return TABLE_NAME;
 }
 
+const QString FlightEntry::getFlightSummary() const
+{
+    if(!isValid())
+        return QString();
+
+    auto tableData = getData();
+    QString flight_summary;
+    auto space = QLatin1Char(' ');
+    flight_summary.append(OPL::Date(tableData.value(OPL::FlightEntry::DOFT).toInt()).toString() + space);
+    flight_summary.append(tableData.value(OPL::FlightEntry::DEPT).toString() + space);
+    flight_summary.append(OPL::Time(tableData.value(OPL::FlightEntry::TOFB).toInt()).toString()
+                          + space);
+    flight_summary.append(OPL::Time(tableData.value(OPL::FlightEntry::TONB).toInt()).toString()
+                          + space);
+    flight_summary.append(tableData.value(OPL::FlightEntry::DEST).toString());
+
+    return flight_summary;
+}
+
+
+
 } // namespace OPL

+ 5 - 0
src/database/flightentry.h

@@ -34,6 +34,11 @@ public:
 
     const QString getTableName() const override;
 
+    /*!
+     * \brief returns a String representation of the key data of this flight
+     */
+    const QString getFlightSummary() const;
+
     const static inline QString ROWID          = QStringLiteral("flight_id");
     const static inline QString DOFT           = QStringLiteral("doft");
     const static inline QString DEPT           = QStringLiteral("dept");

+ 13 - 0
src/gui/dialogues/entryeditdialog.cpp

@@ -0,0 +1,13 @@
+#include "entryeditdialog.h"
+
+EntryEditDialog::EntryEditDialog(QWidget *parent)
+    : QDialog{parent}
+{
+    rowID = 0;
+}
+
+EntryEditDialog::EntryEditDialog(int rowID, QWidget *parent)
+    : QDialog{parent}, rowID(rowID)
+{
+
+}

+ 41 - 0
src/gui/dialogues/entryeditdialog.h

@@ -0,0 +1,41 @@
+#ifndef ENTRYEDITDIALOG_H
+#define ENTRYEDITDIALOG_H
+
+#include <QDialog>
+#include <QObject>
+
+/*!
+ * \brief The EntryEditDialog class is a base class for Dialogs that enable editing of individual database entries
+ */
+class EntryEditDialog : public QDialog
+{
+    Q_OBJECT
+public:
+    EntryEditDialog() = delete;
+    EntryEditDialog(QWidget *parent = nullptr);
+    EntryEditDialog(int rowID, QWidget *parent = nullptr);
+
+    /*!
+     * \brief reset the Entry Dialog to accept a new entry
+     */
+    virtual void reset() = 0;
+
+    /*!
+     * \brief load an entry from the database for editing
+     * \param rowID - The row ID of the entry
+     */
+    virtual void loadEntry(int rowID) = 0;
+
+
+    /*!
+     * \brief delete an entry from the database
+     * \param rowID - the row ID to be deleted
+     * \return true on success
+     */
+    virtual bool deleteEntry(int rowID) = 0;
+
+protected:
+    int rowID;
+};
+
+#endif // ENTRYEDITDIALOG_H

+ 4 - 3
src/gui/dialogues/newflightdialog.cpp

@@ -201,7 +201,10 @@ void NewFlightDialog::fillWithEntryData()
     const auto &flight_data = flightEntry.getData();
 
     // Date of Flight
-    ui->doftLineEdit->setText(OPL::Date::julianDayToString(flight_data.value(OPL::FlightEntry::DOFT).toInt()));
+    QDate date = QDate::fromJulianDay(flight_data.value(OPL::FlightEntry::DOFT).toInt());
+    calendar->setSelectedDate(date);
+    ui->doftLineEdit->setText(OPL::Date::fromJulianDay(date.toJulianDay()).toString(dateFormat));
+
     // Location
     ui->deptLocationLineEdit->setText(flight_data.value(OPL::FlightEntry::DEPT).toString());
     ui->destLocationLineEdit->setText(flight_data.value(OPL::FlightEntry::DEST).toString());
@@ -559,8 +562,6 @@ void NewFlightDialog::on_acftLineEdit_editingFinished()
 void NewFlightDialog::on_doftLineEdit_editingFinished()
 {
     const auto line_edit = ui->doftLineEdit;
-    auto text = ui->doftLineEdit->text();
-
     OPL::Date date = OPL::Date::fromString(ui->doftLineEdit->text(), dateFormat);
 
 

+ 28 - 3
src/gui/dialogues/newpilotdialog.cpp

@@ -26,8 +26,8 @@
 /*!
  * \brief NewPilotDialog::NewPilotDialog - creates a new pilot dialog which can be used to add a new entry to the database
  */
-NewPilotDialog::NewPilotDialog(QWidget *parent) :
-    QDialog(parent),
+NewPilotDialog::NewPilotDialog(QWidget *parent)
+    : EntryEditDialog{parent},
     ui(new Ui::NewPilot)
 {
     setup();
@@ -39,7 +39,7 @@ NewPilotDialog::NewPilotDialog(QWidget *parent) :
  * \param rowId - the rowid of the entry to be edited in the database
  */
 NewPilotDialog::NewPilotDialog(int rowId, QWidget *parent) :
-    QDialog(parent),
+    EntryEditDialog{rowId, parent},
     ui(new Ui::NewPilot)
 {
     setup();
@@ -107,3 +107,28 @@ void NewPilotDialog::submitForm()
         QDialog::accept();
     }
 }
+
+void NewPilotDialog::reset()
+{
+    pilotEntry = OPL::PilotEntry();
+    auto line_edits = this->findChildren<QLineEdit *>();
+
+    for (const auto &le : line_edits) {
+        le->setText(QString());
+    }
+    ui->lastnameLineEdit->setFocus();
+}
+
+void NewPilotDialog::loadEntry(int rowID)
+{
+    pilotEntry = DB->getPilotEntry(rowID);
+    formFiller();
+    ui->lastnameLineEdit->setFocus();
+}
+
+bool NewPilotDialog::deleteEntry(int rowID)
+{
+    auto entry = DB->getPilotEntry(rowID);
+    return DB->remove(entry);
+
+}

+ 11 - 1
src/gui/dialogues/newpilotdialog.h

@@ -24,6 +24,7 @@
 #include <QRegularExpressionValidator>
 #include <QCompleter>
 #include "src/database/pilotentry.h"
+#include "src/gui/dialogues/entryeditdialog.h"
 
 namespace Ui {
 class NewPilot;
@@ -41,7 +42,7 @@ class NewPilot;
  * come in all different forms and shapes around the world. In order to maintain a maximum
  * amount of flexibility, any unicode input is allowed.
  */
-class NewPilotDialog : public QDialog
+class NewPilotDialog : public EntryEditDialog
 {
     Q_OBJECT
 public:
@@ -49,6 +50,9 @@ public:
     explicit NewPilotDialog(int rowId, QWidget *parent = nullptr);
     ~NewPilotDialog();
 
+
+
+
 private slots:
     void on_buttonBox_accepted();
 private:
@@ -67,6 +71,12 @@ private:
      * \brief submitForm - retreives the input from the line edits and commits to the database.
      */
     void submitForm();
+
+    // EntryEditDialog interface
+public:
+    virtual void reset() override;
+    virtual void loadEntry(int rowID) override;
+    virtual bool deleteEntry(int rowID) override;
 };
 
 

+ 17 - 43
src/gui/widgets/pilotswidget.cpp

@@ -70,10 +70,12 @@ void PilotsWidget::setupModelAndView()
 
 void PilotsWidget::connectSignalsAndSlots()
 {
-    QObject::connect(ui->tableView->selectionModel(),   &QItemSelectionModel::selectionChanged,
-                     this,                              &PilotsWidget::tableView_selectionChanged);
-    QObject::connect(ui->tableView->horizontalHeader(), &QHeaderView::sectionClicked,
-                     this,                              &PilotsWidget::tableView_headerClicked);
+    QObject::connect(ui->pilotSearchLineEdit,  &QLineEdit::textChanged,
+                     this,                     &PilotsWidget::filterLineEdit_textChanged);
+    QObject::connect(view->horizontalHeader(), &QHeaderView::sectionClicked,
+                     this,                     &PilotsWidget::newSortColumnSelected);
+    QObject::connect(view,					   &QTableView::clicked,
+                     this, 			    	   &PilotsWidget::editRequested);
 }
 
 void PilotsWidget::setUiEnabled(bool enabled)
@@ -103,42 +105,28 @@ void PilotsWidget::onPilotsWidget_databaseUpdated()
     refreshView();
 }
 
-void PilotsWidget::on_pilotSearchLineEdit_textChanged(const QString &arg1)
+void PilotsWidget::filterLineEdit_textChanged(const QString &arg1)
 {
     model->setFilter(QLatin1Char('\"') + ui->pilotsSearchComboBox->currentText()
                      + QLatin1String("\" LIKE '%") + arg1
                      + QLatin1String("%' AND ID > 1"));
 }
 
-void PilotsWidget::tableView_selectionChanged()
+void PilotsWidget::editRequested(const QModelIndex &index)
 {
-    if (this->findChild<NewPilotDialog*>() != nullptr) {
-        delete this->findChild<NewPilotDialog*>();
-    }
-
-    auto selection = ui->tableView->selectionModel();
-    selectedPilots.clear();
+    int pilotID = model->index(index.row(), 0).data().toInt();
 
-    for (const auto& row : selection->selectedRows()) {
-        selectedPilots.append(row.data().toInt());
-        DEB << "Selected Tails(s) with ID: " << selectedPilots;
-    }
-    if(selectedPilots.length() == 1) {
-        NewPilotDialog np = NewPilotDialog(selectedPilots.first(), this);
-        np.setWindowFlag(Qt::Widget);
-        ui->stackedWidget->addWidget(&np);
-        ui->stackedWidget->setCurrentWidget(&np);
-
-        setUiEnabled(false);
-        np.exec();
-        refreshView();
-        setUiEnabled(true);
-    }
+    NewPilotDialog np = NewPilotDialog(pilotID, this);
+    np.setWindowFlag(Qt::Widget);
+    ui->stackedWidget->addWidget(&np);
+    ui->stackedWidget->setCurrentWidget(&np);
+    np.exec();
+    refreshView();
 }
 
-void PilotsWidget::tableView_headerClicked(int column)
+void PilotsWidget::newSortColumnSelected(int newSortColumn)
 {
-    Settings::setPilotSortColumn(column);
+    Settings::setPilotSortColumn(newSortColumn);
 }
 
 void PilotsWidget::on_newPilotButton_clicked()
@@ -248,20 +236,6 @@ const QString PilotsWidget::getPilotName(const OPL::PilotEntry &pilot) const
 const QString PilotsWidget::getFlightSummary(const OPL::FlightEntry &flight) const
 {
 
-    if(!flight.isValid())
-        return QString();
-
-    auto tableData = flight.getData();
-    QString flight_summary;
-    auto space = QLatin1Char(' ');
-    flight_summary.append(tableData.value(OPL::FlightEntry::DOFT).toString() + space);
-    flight_summary.append(tableData.value(OPL::FlightEntry::DEPT).toString() + space);
-    flight_summary.append(OPL::Time(tableData.value(OPL::FlightEntry::TOFB).toInt()).toString()
-                          + space);
-    flight_summary.append(OPL::Time(tableData.value(OPL::FlightEntry::TONB).toInt()).toString()
-                          + space);
-    flight_summary.append(tableData.value(OPL::FlightEntry::DEST).toString());
 
-    return flight_summary;
 
 }

+ 25 - 3
src/gui/widgets/pilotswidget.h

@@ -59,12 +59,34 @@ public:
     ~PilotsWidget();
 
 private slots:
-    void tableView_selectionChanged();
-    void tableView_headerClicked(int);
+    /*!
+     * \brief Creates a dialog to add a new Pilot to the database
+     */
     void on_newPilotButton_clicked();
+
+    /*!
+     * \brief Deletes the selected pilot from the database if the selection is valid.
+     */
     void on_deletePilotButton_clicked();
+
+    /*!
+     * \brief Informs the user about a error that ocurred when trying to delete an entry.
+     */
     void onDeleteUnsuccessful();
-    void on_pilotSearchLineEdit_textChanged(const QString &arg1);
+
+    /*!
+     * \brief Sets a filter on the model
+     */
+    void filterLineEdit_textChanged(const QString &arg1);
+
+    /*!
+     * \brief Creates a new PilotWidget to allow editing of the selected item
+     */
+    void editRequested(const QModelIndex &index);
+    /*!
+     * \brief Sorts the table on the selected column
+     */
+    void newSortColumnSelected(int newSortColumn);
 
 public slots:
     /*!

+ 90 - 0
src/gui/widgets/pilottableeditwidget.cpp

@@ -0,0 +1,90 @@
+#include "pilottableeditwidget.h"
+#include "src/database/database.h"
+#include "src/gui/dialogues/entryeditdialog.h"
+#include "src/gui/dialogues/newpilotdialog.h"
+#include "src/opl.h"
+#include <QGridLayout>
+
+PilotTableEditWidget::PilotTableEditWidget(QWidget *parent)
+    : TableEditWidget(parent)
+{}
+
+void PilotTableEditWidget::setupModelAndView()
+{
+    model = new QSqlTableModel(this, DB->database());
+    model->setTable(OPL::GLOBALS->getDbTableName(OPL::DbTable::Pilots));
+    model->select();
+    model->setHeaderData(COL_LASTNAME, Qt::Horizontal, tr("Last Name"));
+    model->setHeaderData(COL_FIRSTNAME, Qt::Horizontal, tr("First Name"));
+    model->setHeaderData(COL_COMPANY, Qt::Horizontal, tr("Company"));
+    model->setFilter(QStringLiteral("%1 > 1").arg(OPL::PilotEntry::ROWID)); // hide self
+
+    view->setModel(model);
+    view->setSelectionMode(QAbstractItemView::SingleSelection);
+    view->setSelectionBehavior(QAbstractItemView::SelectRows);
+    view->setEditTriggers(QAbstractItemView::NoEditTriggers);
+    view->resizeColumnsToContents();
+    view->horizontalHeader()->setStretchLastSection(QHeaderView::Stretch);
+    view->verticalHeader()->hide();
+    view->setAlternatingRowColors(true);
+    for(const int i : COLS_TO_HIDE)
+        view->hideColumn(i);
+
+}
+
+void PilotTableEditWidget::setupUI()
+{
+    TableEditWidget::setupUI();
+    addNewEntryPushButton->setText(tr("Add New Pilot"));
+    deleteEntryPushButton->setText(tr("Delete Selected Pilot"));
+    filterSelectionComboBox->addItems({""});
+}
+
+EntryEditDialog *PilotTableEditWidget::getEntryEditDialog(QWidget *parent)
+{
+    return new NewPilotDialog(parent);
+}
+
+QString PilotTableEditWidget::deleteErrorString(int pilotId)
+{
+    const QList<int> foreign_key_constraints = DB->getForeignKeyConstraints(pilotId,
+                                                                            OPL::DbTable::Pilots);
+    QList<OPL::FlightEntry> constrained_flights;
+    for (const auto &row_id : foreign_key_constraints) {
+        constrained_flights.append(DB->getFlightEntry(row_id));
+    }
+
+    // the error is a database error
+    if (constrained_flights.isEmpty()) {
+        return(tr("<br>Unable to delete.<br><br>The following error has ocurred:<br>%1"
+                ).arg(DB->lastError.text()));
+    } else {
+        // the error is a foreign key constraint
+        QString constrained_flights_string;
+        for (int i=0; i<constrained_flights.length(); i++) {
+            constrained_flights_string.append(constrained_flights[i].getFlightSummary() + QStringLiteral("&nbsp;&nbsp;&nbsp;&nbsp;<br>"));
+            if (i>10) {
+                constrained_flights_string.append("<br>[...]<br>");
+                break;
+            }
+        }
+        return(tr("Unable to delete.<br><br>"
+                "This is most likely the case because a flight exists with the Pilot "
+                "you are trying to delete as PIC.<br><br>"
+                "%1 flight(s) with this pilot 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 pilot from the database.<br><br>"
+                ).arg(QString::number(constrained_flights.length()),
+                      constrained_flights_string));
+    }
+}
+
+QString PilotTableEditWidget::confirmDeleteString(int rowId)
+{
+    const auto entry = DB->getPilotEntry(rowId);
+    return tr("You are deleting the following pilot:<br><br><b><tt>"
+           "%1, %2</b></tt><br><br>Are you sure?").arg(entry.getLastName(), entry.getFirstName());
+}
+
+

+ 41 - 0
src/gui/widgets/pilottableeditwidget.h

@@ -0,0 +1,41 @@
+#ifndef PILOTTABLEEDITWIDGET_H
+#define PILOTTABLEEDITWIDGET_H
+
+#include "tableeditwidget.h"
+
+class PilotTableEditWidget : public TableEditWidget
+{
+public:
+    PilotTableEditWidget(QWidget *parent = nullptr);
+
+    virtual void setupModelAndView() override;
+    virtual void setupUI() override;
+    virtual EntryEditDialog *getEntryEditDialog(QWidget *parent = nullptr) override;
+
+
+private:
+    const int COL_ROWID = 0;
+    const int COL_LASTNAME = 1;
+    const int COL_FIRSTNAME = 2;
+    const int COL_COMPANY = 4;
+    const int COLS_TO_HIDE[5] = {0, 3, 5, 6, 7};
+
+    /*!
+    * \brief Informs the user that deleting a Pilot has been unsuccessful
+    *
+    * \details Normally, when one of these entries can not be deleted, it is because of
+    * a [foreign key constraint](https://sqlite.org/foreignkeys.html), meaning that a flight
+    * is associated with the Pilot that was supposed to be deleted as Pilot-in-command.
+    *
+    * This function is used to inform the user and give hints on how to solve the problem.
+    */
+    virtual QString deleteErrorString(int pilotId) override;
+
+    virtual QString confirmDeleteString(int rowId) override;
+
+
+private slots:
+//    virtual void deleteEntryRequested() override;
+};
+
+#endif // PILOTTABLEEDITWIDGET_H

+ 167 - 0
src/gui/widgets/tableeditwidget.cpp

@@ -0,0 +1,167 @@
+#include "tableeditwidget.h"
+#include "src/database/database.h"
+#include "src/opl.h"
+#include "src/classes/settings.h"
+#include <QGridLayout>
+#include <QLabel>
+
+TableEditWidget::TableEditWidget(QWidget *parent)
+    : QWidget{parent}
+{}
+
+void TableEditWidget::init() {
+    setupUI();
+    setupSignalsAndSlots();
+}
+
+void TableEditWidget::setupUI()
+{
+    // Setting up the model and view is done in the derived class
+    setupModelAndView();
+
+    // Set up the editing widget
+    stackedWidget->hide();
+
+    // create a 2-column grid layout and fill the cells
+    int colL = 0; // left column
+    int colR = 1; // right column
+    int row = 0;
+    int allRowSpan = 4; // adjust as needed for stackedWidget to span all rows
+
+    auto gridLayout = new QGridLayout(this);
+
+    gridLayout->addWidget(view, row, colL);
+    gridLayout->addWidget(stackedWidget, row, colR, allRowSpan, 1);
+    row++;
+
+    gridLayout->addWidget(addNewEntryPushButton, row, colL);
+    row++;
+
+    gridLayout->addWidget(deleteEntryPushButton, row, colL);
+    row++;
+
+    gridLayout->addWidget(setupFilterWidget(), row, colL);
+}
+
+void TableEditWidget::setupSignalsAndSlots()
+{
+    QObject::connect(DB,             		   &OPL::Database::dataBaseUpdated,
+                     this,     		 		   &TableEditWidget::databaseContentChanged);
+    QObject::connect(filterLineEdit,  		   &QLineEdit::textChanged,
+                     this,                     &TableEditWidget::filterTextChanged);
+    QObject::connect(view->horizontalHeader(), &QHeaderView::sectionClicked,
+                     this,                     &TableEditWidget::sortColumnChanged);
+    QObject::connect(view,					   &QTableView::clicked,
+                     this, 			    	   &TableEditWidget::editEntryRequested);
+    QObject::connect(addNewEntryPushButton,    &QPushButton::clicked,
+                     this, 					   &TableEditWidget::addEntryRequested);
+    QObject::connect(deleteEntryPushButton,    &QPushButton::clicked,
+                     this, 					   &TableEditWidget::deleteEntryRequested);
+}
+
+void TableEditWidget::addEntryRequested()
+{
+    clearStackedWidget();
+
+    // create a Dialog for adding a new entry and put it on the stackedWidget
+    auto editDialog = getEntryEditDialog(this);
+    stackedWidget->addWidget(editDialog);
+    stackedWidget->show();
+    editDialog->exec();
+
+    stackedWidget->hide();
+
+}
+
+
+void TableEditWidget::editEntryRequested(const QModelIndex &selectedIndex)
+{
+    clearStackedWidget();
+
+    // create a Dialog for editing the selected entry and put it on the stackedWidget
+    int rowId = model->index(selectedIndex.row(), 0).data().toInt();
+    auto editEntryDialog = getEntryEditDialog(this);
+    editEntryDialog->loadEntry(rowId);
+
+    stackedWidget->addWidget(editEntryDialog);
+    stackedWidget->setCurrentWidget(editEntryDialog);
+    stackedWidget->show();
+    editEntryDialog->exec();
+
+    stackedWidget->hide();
+
+}
+
+void TableEditWidget::deleteEntryRequested()
+{
+    const QModelIndex selectedIndex = view->selectionModel()->currentIndex();
+    if(!selectedIndex.isValid()) {
+        WARN(tr("No entry selected."));
+        return;
+    }
+
+    stackedWidget->hide();
+    clearStackedWidget();
+    int rowId = model->index(selectedIndex.row(), 0).data().toInt();
+    view->selectionModel()->reset();
+
+    // get user confirmation
+    QMessageBox confirm(this);
+    confirm.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
+    confirm.setDefaultButton(QMessageBox::No);
+    confirm.setIcon(QMessageBox::Question);
+    confirm.setWindowTitle(tr("Confirm Deletion"));
+
+    confirm.setText(confirmDeleteString(rowId));
+    if (confirm.exec() == QMessageBox::Yes) {
+        auto editDialog = getEntryEditDialog(this);
+        if(!editDialog->deleteEntry(rowId))
+            WARN(deleteErrorString(rowId));
+    }
+}
+
+void TableEditWidget::filterTextChanged(const QString &filterString)
+{
+    TODO << "Create map <index, column Name> and implement func in derived";
+    model->setFilter(QLatin1Char('\"') + filterSelectionComboBox->currentText()
+                     + QLatin1String("\" LIKE '%") + filterString
+                     + QLatin1String("%' AND ID > 1"));
+}
+
+void TableEditWidget::sortColumnChanged(int newSortColumn)
+{
+    view->sortByColumn(newSortColumn, Qt::AscendingOrder);
+    Settings::setPilotSortColumn(newSortColumn);
+}
+
+QWidget *TableEditWidget::setupFilterWidget()
+{
+    QWidget *widget = new QWidget(this);
+    QGridLayout *layout = new QGridLayout(widget);
+
+    int row =  0;
+    int colL = 0;
+    int colM = 1;
+    int colR = 2;
+
+    layout->addWidget(new QLabel(tr("Filter"), this), row, colL);
+    layout->addWidget(filterLineEdit, row, colM);
+    layout->addWidget(filterSelectionComboBox, row, colR);
+
+    return widget;
+}
+
+void TableEditWidget::clearStackedWidget()
+{
+    while (stackedWidget->count() > 0) {
+        QWidget *orphan = stackedWidget->currentWidget();
+        stackedWidget->removeWidget(orphan);
+        delete orphan;
+    }
+}
+
+void TableEditWidget::databaseContentChanged()
+{
+    model->select();
+    view->resizeColumnsToContents();
+}

+ 108 - 0
src/gui/widgets/tableeditwidget.h

@@ -0,0 +1,108 @@
+#ifndef TABLEEDITWIDGET_H
+#define TABLEEDITWIDGET_H
+
+#include "src/gui/dialogues/entryeditdialog.h"
+#include <QWidget>
+#include <QSqlTableModel>
+#include <QHeaderView>
+#include <QTableView>
+#include <QLineEdit>
+#include <QPushButton>
+#include <QStackedWidget>
+#include <QComboBox>
+#include <QLabel>
+
+/*!
+ * \brief The TableEditWidget class is a base class for widgets which enable
+ * editing certain tables in the datbase.
+ */
+class TableEditWidget : public QWidget
+{
+    Q_OBJECT
+public:
+    /*!
+     * \brief Create a new TableEditWidget
+     */
+    explicit TableEditWidget(QWidget *parent = nullptr);
+
+    /*!
+     * \brief Initialises the dialog by calling its virtual setup functions.
+     * \attention Call this functian before showing the dialog.
+     */
+    void init();
+
+    /*!
+     * \brief Set up the model and view of the widget
+     * \details Implement this function to initialise the protected members of this class.
+     * This includes setting the QSqlTableModel and QTableView      */
+    virtual void setupModelAndView() = 0;
+
+    /*!
+     * \brief Set up the UI of the widget
+     * \details Implement this function to set appropriate labels to the protected members of this
+     * class. This includes setting appropriate labels on the Push Buttons as well as
+     * appropriate filter options in the filter Combo Box. Make sure to call the base class
+     * implementation first when overriding this method.
+     */
+    virtual void setupUI();
+
+    /*!
+     * \brief create an error String when deleting a database entry has been unsuccessful
+     * \param rowId - the row id of the entry to be deleted
+     * \details When deleting an entry from a database fails, this can have different reasons
+     * depending on the table. This function returns an implementation-specific error string
+     * to inform the user about the failure and give hints on how to fix it
+     */
+    virtual QString deleteErrorString(int rowId) = 0;
+
+    /*!
+     * \brief return a String asking the user to confirm deletion of a given entry
+     * \param rowId - the row id of the entry to be deleted
+     * \brief The message string is displayed in a QMessageBox
+     */
+    virtual QString confirmDeleteString(int rowId) = 0;
+
+    /*!
+     * \brief get an apropriate Edit Dialog for the implementation
+     * \details The Edit Dialogs for different tables differ in the data they display
+     * and how they verify the user inputs. This method returns an apropriate
+     * EntryEditDialog for the selected table.
+     */
+    virtual EntryEditDialog *getEntryEditDialog(QWidget *parent = nullptr) = 0;
+
+protected:
+    QSqlTableModel *model = nullptr;
+    QTableView *view = new QTableView(this);
+
+    QPushButton *addNewEntryPushButton = new QPushButton(this);
+    QPushButton *deleteEntryPushButton = new QPushButton(this);
+
+    QStackedWidget *stackedWidget = new QStackedWidget(this);
+    QLineEdit *filterLineEdit = new QLineEdit(this);
+    QComboBox *filterSelectionComboBox = new QComboBox(this);
+
+private:
+    void setupSignalsAndSlots();
+    QWidget *setupFilterWidget();
+
+    /*!
+     * \brief Remove orphaned editing widgets from the stacked widget.
+     * \details When the user selects another entry without finishing editing a previous one,
+     * the previously created entry edit never finished. To prevent a leak, this function is called
+     * to clean up stale dialogs
+     */
+    void clearStackedWidget();
+
+private slots:
+    void addEntryRequested();
+    void editEntryRequested(const QModelIndex &selectedIndex);
+    void deleteEntryRequested();
+    void filterTextChanged(const QString &filterString);
+    void sortColumnChanged(int newSortColumn);
+
+public slots:
+    void databaseContentChanged();
+
+};
+
+#endif // TABLEEDITWIDGET_H