Added Mash profiles table and the first part of the Mash profile editor. Edit and write must be written.

Mon, 07 Mar 2022 17:33:22 +0100

author
Michiel Broek <mbroek@mbse.eu>
date
Mon, 07 Mar 2022 17:33:22 +0100
changeset 49
29cf6e350063
parent 48
ddd1171ecda5
child 50
571a13a4860b

Added Mash profiles table and the first part of the Mash profile editor. Edit and write must be written.

CMakeLists.txt file | annotate | diff | comparison | revisions
src/EditProfileMash.cpp file | annotate | diff | comparison | revisions
src/EditProfileMash.h file | annotate | diff | comparison | revisions
src/MainWindow.cpp file | annotate | diff | comparison | revisions
src/MainWindow.h file | annotate | diff | comparison | revisions
src/ProfileMashs.cpp file | annotate | diff | comparison | revisions
src/ProfileMashs.h file | annotate | diff | comparison | revisions
src/bmsapp.h file | annotate | diff | comparison | revisions
ui/EditProfileMash.ui file | annotate | diff | comparison | revisions
ui/MainWindow.ui file | annotate | diff | comparison | revisions
ui/ProfileMashs.ui file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Sun Mar 06 15:31:40 2022 +0100
+++ b/CMakeLists.txt	Mon Mar 07 17:33:22 2022 +0100
@@ -117,6 +117,8 @@
     ${SRCDIR}/EditEquipment.cpp
     ${SRCDIR}/ProfileWaters.cpp
     ${SRCDIR}/EditProfileWater.cpp
+    ${SRCDIR}/ProfileMashs.cpp
+    ${SRCDIR}/EditProfileMash.cpp
     ${SRCDIR}/Setup.cpp
     ${SRCDIR}/Utils.cpp
     ${SRCDIR}/MainWindow.cpp
@@ -143,6 +145,8 @@
     ${SRCDIR}/EditEquipment.h
     ${SRCDIR}/ProfileWaters.h
     ${SRCDIR}/EditProfileWater.h
+    ${SRCDIR}/ProfileMashs.h
+    ${SRCDIR}/EditProfileMash.h
     ${SRCDIR}/Setup.h
     ${SRCDIR}/Utils.h
     ${SRCDIR}/MainWindow.h
@@ -168,6 +172,8 @@
     ${UIDIR}/EditEquipment.ui
     ${UIDIR}/ProfileWaters.ui
     ${UIDIR}/EditProfileWater.ui
+    ${UIDIR}/ProfileMashs.ui
+    ${UIDIR}/EditProfileMash.ui
     ${UIDIR}/Setup.ui
     ${UIDIR}/MainWindow.ui
 )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/EditProfileMash.cpp	Mon Mar 07 17:33:22 2022 +0100
@@ -0,0 +1,314 @@
+/**
+ * EditProfileMash.cpp is part of bmsapp.
+ *
+ * bmsapp 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.
+ *
+ * bmsapp 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 <http://www.gnu.org/licenses/>.
+ */
+#include "EditProfileMash.h"
+#include "../ui/ui_EditProfileMash.h"
+#include "bmsapp.h"
+
+
+EditProfileMash::EditProfileMash(int id, QWidget *parent) : QDialog(parent), ui(new Ui::EditProfileMash)
+{
+    QSqlQuery query;
+
+    qDebug() << "EditProfileMash record:" << id;
+    ui->setupUi(this);
+    this->recno = id;
+
+    if (id >= 0) {
+        query.prepare("SELECT * FROM profile_mash WHERE record = :recno");
+        query.bindValue(":recno", id);
+        query.exec();
+        query.next();
+
+        ui->nameEdit->setText(query.value(1).toString());
+        ui->notesEdit->setPlainText(query.value(2).toString());
+	QJsonParseError parseError;
+        const auto& json = query.value(3).toString();
+
+        if (!json.trimmed().isEmpty()) {
+            const auto& formattedJson = QString("%1").arg(json);
+            this->steps = QJsonDocument::fromJson(formattedJson.toUtf8(),  &parseError);
+
+            if (parseError.error != QJsonParseError::NoError)
+                qDebug() << "Parse error: " << parseError.errorString() << "at" << parseError.offset ;
+	}
+
+    } else {
+        /* Set some defaults */
+	const auto& formattedJson = QString("[]");
+	this->steps = QJsonDocument::fromJson(formattedJson.toUtf8());
+    }
+
+    connect(ui->nameEdit, &QLineEdit::textChanged, this, &EditProfileMash::is_changed);
+    connect(ui->notesEdit, SIGNAL(textChanged()), this, SLOT(is_changed()));
+    connect(ui->stepsTable, SIGNAL(cellPressed(int, int)), this, SLOT(cell_Changed(int, int)));
+
+    ui->saveButton->setEnabled(false);
+    ui->deleteButton->setEnabled((id >= 0) ? true:false);
+
+    emit refreshTable();
+
+    WindowTitle();
+}
+
+
+void EditProfileMash::refreshTable()
+{
+    QString w;
+    QWidget* pWidget;
+    QHBoxLayout* pLayout;
+    double  d;
+    int	total;
+
+    qDebug() << "Steps reload";
+
+    const QStringList labels({tr("Step name"), tr("Type"), tr("Start °C"), tr("End °C"), tr("Rest time"), tr("Ramp time"), tr("Button")});
+    ui->stepsTable->setColumnCount(7);
+    ui->stepsTable->setColumnWidth(0, 250);	/* Step name	*/
+    ui->stepsTable->setColumnWidth(1, 150);	/* Step type	*/
+    ui->stepsTable->setColumnWidth(2,  75);	/* Start temp	*/
+    ui->stepsTable->setColumnWidth(3,  75);	/* End temp	*/
+    ui->stepsTable->setColumnWidth(4,  75);	/* Step time	*/
+    ui->stepsTable->setColumnWidth(5,  75);	/* Ramp time	*/
+    ui->stepsTable->setColumnWidth(6,  80);	/* Button	*/
+    ui->stepsTable->setHorizontalHeaderLabels(labels);
+    ui->stepsTable->verticalHeader()->hide();
+
+    qDebug() << " ** " << this->steps << this->steps.isArray() << this->steps.array().size() ;
+
+    total = 0;
+    ui->stepsTable->setRowCount(this->steps.array().size());
+
+    if (this->steps.isArray()) {
+	for (int i = 0; i < this->steps.array().size(); i++) {
+	    QJsonObject obj = this->steps.array().at(i).toObject();
+	    qDebug() << i << obj;
+
+	    ui->stepsTable->setItem(i, 0, new QTableWidgetItem(obj["step_name"].toString()));
+
+	    QComboBox* myComboBox = new QComboBox();
+	    myComboBox->addItem(tr("Infusion"));
+	    myComboBox->addItem(tr("Temperature"));
+	    myComboBox->addItem(tr("Decoction"));
+	    ui->stepsTable->setCellWidget(i, 1, myComboBox);
+	    if (obj["step_type"].isString())
+		d = QString(obj["step_type"].toString()).toDouble();
+	    else
+		d = obj["step_type"].toDouble();
+	    myComboBox->setCurrentIndex((int)d);
+
+	    if (obj["step_temp"].isString())
+		d = QString(obj["step_temp"].toString()).toDouble();
+	    else
+		d = obj["step_temp"].toDouble();
+	    w = QString("%1").arg(d, 2, 'f', 1, '0');
+	    QTableWidgetItem *item = new QTableWidgetItem(w);
+            item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter);
+	    ui->stepsTable->setItem(i, 2, item);
+
+	    if (obj["end_temp"].isString())
+                d = QString(obj["end_temp"].toString()).toDouble();
+            else
+                d = obj["end_temp"].toDouble();
+            w = QString("%1").arg(d, 2, 'f', 1, '0');
+            item = new QTableWidgetItem(w);
+            item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter);
+            ui->stepsTable->setItem(i, 3, item);
+
+	    if (obj["step_time"].isString())
+                d = QString(obj["step_time"].toString()).toDouble();
+            else
+                d = obj["step_time"].toDouble();
+            w = QString("%1").arg(d, 1, 'f', 0, '0');
+            item = new QTableWidgetItem(w);
+            item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter);
+            ui->stepsTable->setItem(i, 4, item);
+	    total += (int)d;
+
+	    if (obj["ramp_time"].isString())
+                d = QString(obj["ramp_time"].toString()).toDouble();
+            else
+                d = obj["ramp_time"].toDouble();
+            w = QString("%1").arg(d, 1, 'f', 0, '0');
+            item = new QTableWidgetItem(w);
+            item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter);
+            ui->stepsTable->setItem(i, 5, item);
+	    if (i > 0)
+		total += (int)d;
+
+	    /* Add the Delete row button */
+            pWidget = new QWidget();
+            QPushButton* btn_edit = new QPushButton();
+            btn_edit->setObjectName(QString("%1").arg(i));  /* Send row with the button */
+            btn_edit->setText(tr("Delete"));
+            connect(btn_edit, SIGNAL(clicked()), this, SLOT(on_deleteRow_clicked()));
+            pLayout = new QHBoxLayout(pWidget);
+            pLayout->addWidget(btn_edit);
+            pLayout->setContentsMargins(5, 0, 5, 0);
+            pWidget->setLayout(pLayout);
+            ui->stepsTable->setCellWidget(i, 6, pWidget);
+	}
+    }
+
+    /* Show the calculated total mash time. */
+    ui->totalEdit->setText(QString("%1:%2").arg(total / 60).arg(total % 60, 2, 'f', 0, '0'));
+}
+
+
+EditProfileMash::~EditProfileMash()
+{
+    qDebug() << "EditProfileMash done";
+    delete ui;
+    emit entry_changed();
+}
+
+
+/*
+ * Window header, mark any change with '**'
+ */
+void EditProfileMash::WindowTitle()
+{
+    QString txt;
+
+    if (this->recno < 0) {
+	txt = QString(tr("BMSapp - Add new mash profile"));
+    } else {
+	txt = QString(tr("BMSapp - Edit mash profile %1").arg(this->recno));
+    }
+
+    if (this->textIsChanged) {
+	txt.append((QString(" **")));
+    }
+    setWindowTitle(txt);
+}
+
+
+void EditProfileMash::on_saveButton_clicked()
+{
+    QSqlQuery query;
+
+    /* If there are errors in the form, show a message and do "return;" */
+    if (ui->nameEdit->text().length() < 2) {
+	QMessageBox::warning(this, tr("Edit Mash"), tr("Name empty or too short."));
+	return;
+    }
+
+    if (this->textIsChanged) {
+    	if (this->recno == -1) {
+    	    query.prepare("INSERT INTO profile_mash SET name=:name, notes=:notes, "
+		"uuid = :uuid");
+    	} else {
+	    query.prepare("UPDATE profile_mash SET name=:name, notes=:notes "
+                "WHERE record = :recno");
+    	}
+	query.bindValue(":name", ui->nameEdit->text());
+	query.bindValue(":notes", ui->notesEdit->toPlainText());
+
+	if (this->recno == -1) {
+	    query.bindValue(":uuid", QUuid::createUuid().toString().mid(1, 36));
+	} else {
+	    query.bindValue(":recno", this->recno);
+	}
+	query.exec();
+	if (query.lastError().isValid()) {
+	    qDebug() << "EditProfileMash" << query.lastError();
+	    QMessageBox::warning(this, tr("Database error"),
+                        tr("MySQL error: %1\n%2\n%3")
+                        .arg(query.lastError().nativeErrorCode())
+                        .arg(query.lastError().driverText())
+                        .arg(query.lastError().databaseText()));
+	} else {
+	    qDebug() << "EditProfileMash Saved";
+	}
+    }
+
+    ui->saveButton->setEnabled(false);
+    this->textIsChanged = false;
+    WindowTitle();
+}
+
+
+void EditProfileMash::on_deleteButton_clicked()
+{
+    QSqlQuery query;
+
+    query.prepare("DELETE FROM profile_water WHERE record = :recno");
+    query.bindValue(":recno", this->recno);
+    query.exec();
+    if (query.lastError().isValid()) {
+	qDebug() << "EditProfileMash" << query.lastError();
+	QMessageBox::warning(this, tr("Database error"),
+                        tr("MySQL error: %1\n%2\n%3")
+                        .arg(query.lastError().nativeErrorCode())
+                        .arg(query.lastError().driverText())
+                        .arg(query.lastError().databaseText()));
+    } else {
+	qDebug() << "EditProfileMash Deleted" << this->recno;
+    }
+
+    this->close();
+    this->setResult(1);
+}
+
+
+void EditProfileMash::is_changed()
+{
+    ui->saveButton->setEnabled(true);
+    ui->deleteButton->setEnabled((this->recno >= 0) ? true:false);
+    this->textIsChanged = true;
+    WindowTitle();
+}
+
+
+void EditProfileMash::cell_Changed(int nRow, int nCol)
+{
+    qDebug() << "Cell at row " + QString::number(nRow) + " column " + QString::number(nCol) + " was double clicked.";
+
+    //ui->stepsTable->sortItems(2, Qt::AscendingOrder);	// Sort on temp
+}
+
+
+void EditProfileMash::on_addButton_clicked()
+{
+    qDebug() << "Add cell";
+}
+
+
+void EditProfileMash::on_deleteRow_clicked()
+{
+    qDebug() << "Delete row";
+}
+
+
+void EditProfileMash::on_quitButton_clicked()
+{
+    if (this->textIsChanged) {
+	int rc = QMessageBox::warning(this, tr("Mash changed"), tr("The water has been modified\n Save changes?"),
+                                QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Save);
+        switch (rc) {
+            case QMessageBox::Save:
+                        on_saveButton_clicked();
+                        break;  /* Saved and then Quit */
+            case QMessageBox::Discard:
+                        break;  /* Quit without Save */
+            case QMessageBox::Cancel:
+                        return; /* Return to the editor page */
+        }
+    }
+
+    this->close();
+    this->setResult(1);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/EditProfileMash.h	Mon Mar 07 17:33:22 2022 +0100
@@ -0,0 +1,41 @@
+#ifndef _EDITPROFILEMASH_H
+#define _EDITPROFILEMASH_H
+
+#include <QDialog>
+#include <QJsonDocument>
+
+namespace Ui {
+class EditProfileMash;
+}
+
+class EditProfileMash : public QDialog
+{
+    Q_OBJECT
+
+signals:
+    void entry_changed();
+
+public:
+    explicit EditProfileMash(int id, QWidget *parent = 0);
+    ~EditProfileMash();
+
+private slots:
+    void on_saveButton_clicked();
+    void on_quitButton_clicked();
+    void on_deleteButton_clicked();
+    void is_changed();
+    void refreshTable(void);
+    void cell_Changed(int nRow, int nCol);
+    void on_addButton_clicked();
+    void on_deleteRow_clicked();
+
+private:
+    Ui::EditProfileMash *ui;
+    int recno;
+    bool textIsChanged = false;
+    QJsonDocument steps;
+
+    void WindowTitle();
+};
+
+#endif
--- a/src/MainWindow.cpp	Sun Mar 06 15:31:40 2022 +0100
+++ b/src/MainWindow.cpp	Mon Mar 07 17:33:22 2022 +0100
@@ -24,6 +24,7 @@
 #include "InventoryWaters.h"
 #include "InventoryEquipments.h"
 #include "ProfileWaters.h"
+#include "ProfileMashs.h"
 #include "Setup.h"
 #include "../ui/ui_MainWindow.h"
 #include "config.h"
@@ -202,6 +203,24 @@
 }
 
 
+void MainWindow::fromProfileMashs()
+{
+    qDebug() << Q_FUNC_INFO;
+    delete ProfileMashsWindow;
+    this->show();
+}
+
+
+void MainWindow::on_actionMash_profiles_triggered()
+{
+    qDebug() << Q_FUNC_INFO;
+    ProfileMashsWindow = new ProfileMashs(this);
+    QObject::connect(ProfileMashsWindow, SIGNAL(firstWindow()), this, SLOT(fromProfileMashs()));
+    this->hide();    // Close the main window
+    ProfileMashsWindow->show();  // Show a second window
+}
+
+
 void MainWindow::fromSetup()
 {
     qDebug() << Q_FUNC_INFO;
--- a/src/MainWindow.h	Sun Mar 06 15:31:40 2022 +0100
+++ b/src/MainWindow.h	Mon Mar 07 17:33:22 2022 +0100
@@ -9,6 +9,7 @@
 #include "InventoryWaters.h"
 #include "InventoryEquipments.h"
 #include "ProfileWaters.h"
+#include "ProfileMashs.h"
 #include "Setup.h"
 
 #include <QMainWindow>
@@ -38,6 +39,7 @@
     void on_actionWaters_triggered();
     void on_actionEquipments_triggered();
     void on_actionWater_profiles_triggered();
+    void on_actionMash_profiles_triggered();
     void on_actionSetup_triggered();
     void on_actionAbout_triggered();
 
@@ -50,6 +52,7 @@
     void fromInventoryWaters();
     void fromInventoryEquipments();
     void fromProfileWaters();
+    void fromProfileMashs();
     void fromSetup();
 
 private:
@@ -63,7 +66,8 @@
     InventoryMiscs *InventoryMiscsWindow;
     InventoryWaters *InventoryWatersWindow;
     InventoryEquipments *InventoryEquipmentsWindow;
-    ProfileWaters * ProfileWatersWindow;
+    ProfileWaters *ProfileWatersWindow;
+    ProfileMashs *ProfileMashsWindow;
     Setup *SetupWindow;
 };
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ProfileMashs.cpp	Mon Mar 07 17:33:22 2022 +0100
@@ -0,0 +1,140 @@
+/**
+ * ProfileMashs.cpp is part of bmsapp.
+ *
+ * bmsapp 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.
+ *
+ * bmsapp 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 <http://www.gnu.org/licenses/>.
+ */
+#include "ProfileMashs.h"
+#include "EditProfileMash.h"
+#include "../ui/ui_ProfileMashs.h"
+#include "config.h"
+#include "bmsapp.h"
+
+
+ProfileMashs::ProfileMashs(QWidget *parent) : QDialog(parent), ui(new Ui::ProfileMashs)
+{
+    qDebug() << "ProfileMashs start";
+
+    ui->setupUi(this);
+    emit refreshTable();
+
+    setWindowTitle( QString("BMSapp - %1 - Profile Mashs").arg(VERSIONSTRING) );
+}
+
+
+void ProfileMashs::refreshTable()
+{
+    QString w;
+    QWidget* pWidget;
+    QLabel *label;
+    QHBoxLayout* pLayout;
+
+    qDebug() << "ProfileMashs reload";
+
+    QSqlQuery query("SELECT * FROM profile_mash ORDER BY name");
+    const QStringList labels({tr("Name"), tr("Notes"), tr("Steps"), tr("Edit")});
+
+    ui->tableMashs->setColumnCount(4);
+    ui->tableMashs->setColumnWidth(0, 250);	/* Name		*/
+    ui->tableMashs->setColumnWidth(1, 675);	/* Notes	*/
+    ui->tableMashs->setColumnWidth(2,  75);	/* Steps	*/
+    ui->tableMashs->setColumnWidth(3,  80);	/* Edit button	*/
+    ui->tableMashs->setRowCount(query.size());
+    ui->tableMashs->setHorizontalHeaderLabels(labels);
+    ui->tableMashs->verticalHeader()->hide();
+    ui->tableMashs->setFixedSize(1080 + 24, 640);	/* Even if this is too large, it works */
+
+    QTableWidgetItem *rightitem = new QTableWidgetItem();
+    rightitem->setTextAlignment(Qt::AlignRight);
+
+    query.first();
+    for (int ridx = 0 ; ridx < query.size() ; ridx++ ) {
+	ui->tableMashs->setItem(ridx, 0, new QTableWidgetItem(query.value(1).toString()));	/* Name */
+	ui->tableMashs->setItem(ridx, 1, new QTableWidgetItem(query.value(2).toString()));	/* Notes */
+
+	QJsonParseError parseError;
+	const auto& json = query.value(3).toString();
+
+	if (!json.trimmed().isEmpty()) {
+	    const auto& formattedJson = QString("%1").arg(json);
+            const auto& doc = QJsonDocument::fromJson(formattedJson.toUtf8(),  &parseError);
+
+	    if (parseError.error != QJsonParseError::NoError)
+    		qDebug() << "Parse error: " << parseError.errorString() << "at" << parseError.offset ;
+
+//	    qDebug() << " ** " << doc << doc.isArray() << doc.array().size() ;
+
+	    w = QString("%1").arg(doc.array().size());
+	    QTableWidgetItem *item = new QTableWidgetItem(w);
+	    item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter);
+	    ui->tableMashs->setItem(ridx, 2, item);
+	}
+
+	/* Add the Edit button */
+	pWidget = new QWidget();
+	QPushButton* btn_edit = new QPushButton();
+	btn_edit->setObjectName(QString("%1").arg(query.value(0).toString()));	/* Send record with the button */
+	btn_edit->setText(tr("Edit"));
+	connect(btn_edit, SIGNAL(clicked()), this, SLOT(on_editButton_clicked()));
+	pLayout = new QHBoxLayout(pWidget);
+	pLayout->addWidget(btn_edit);
+	pLayout->setContentsMargins(5, 0, 5, 0);
+	pWidget->setLayout(pLayout);
+	ui->tableMashs->setCellWidget(ridx, 3, pWidget);
+	query.next();
+    }
+
+    setWindowTitle( QString("BMSapp - %1 - Profile Mashs").arg(VERSIONSTRING) );
+}
+
+
+ProfileMashs::~ProfileMashs()
+{
+    qDebug() << "ProfileMashs done";
+    delete ui;
+}
+
+
+void ProfileMashs::edit(int recno)
+{
+    qDebug() << "ProfileMashs edit:" << recno;
+
+    EditProfileMash dialog(recno, this);
+    /* Signal from editor if a refresh is needed */
+    connect(&dialog, SIGNAL(entry_changed()), this, SLOT(refreshTable()));
+    dialog.setModal(true);
+    dialog.exec();
+}
+
+
+void ProfileMashs::on_editButton_clicked()
+{
+    QPushButton *pb = qobject_cast<QPushButton *>(QObject::sender());
+    int recno = pb->objectName().toInt();
+    qDebug() << Q_FUNC_INFO << recno;
+    edit(recno);
+}
+
+
+void ProfileMashs::on_insertButton_clicked()
+{
+    qDebug() << Q_FUNC_INFO;
+    edit(-1);
+}
+
+
+void ProfileMashs::on_quitButton_clicked()
+{
+    emit firstWindow();
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ProfileMashs.h	Mon Mar 07 17:33:22 2022 +0100
@@ -0,0 +1,32 @@
+#ifndef _PROFILEMASHS_H
+#define _PROFILEMASHS_H
+
+#include <QDialog>
+
+namespace Ui {
+class ProfileMashs;
+}
+
+class ProfileMashs : public QDialog
+{
+    Q_OBJECT
+
+public:
+    explicit ProfileMashs(QWidget *parent = nullptr);
+    ~ProfileMashs();
+
+signals:
+    void firstWindow();
+
+private slots:
+    void on_quitButton_clicked();
+    void on_insertButton_clicked();
+    void on_editButton_clicked();
+    void refreshTable(void);
+
+private:
+    Ui::ProfileMashs *ui;
+    void edit(int recno);
+};
+
+#endif
--- a/src/bmsapp.h	Sun Mar 06 15:31:40 2022 +0100
+++ b/src/bmsapp.h	Mon Mar 07 17:33:22 2022 +0100
@@ -27,10 +27,13 @@
 #include <QColorSpace>
 #include <QGraphicsView>
 #include <QFileDialog>
-
 #include <QXmlStreamReader>
 #include <QXmlStreamWriter>
 
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+
 #include "Utils.h"
 #include "database/database.h"
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/EditProfileMash.ui	Mon Mar 07 17:33:22 2022 +0100
@@ -0,0 +1,227 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>EditProfileMash</class>
+ <widget class="QDialog" name="EditProfileMash">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>1024</width>
+    <height>560</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Dialog</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <widget class="QWidget" name="topWidget" native="true">
+     <widget class="QLabel" name="nameLabel">
+      <property name="geometry">
+       <rect>
+        <x>10</x>
+        <y>10</y>
+        <width>91</width>
+        <height>20</height>
+       </rect>
+      </property>
+      <property name="text">
+       <string>Name:</string>
+      </property>
+      <property name="alignment">
+       <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+      </property>
+     </widget>
+     <widget class="QLabel" name="notesLabel">
+      <property name="geometry">
+       <rect>
+        <x>10</x>
+        <y>40</y>
+        <width>91</width>
+        <height>20</height>
+       </rect>
+      </property>
+      <property name="text">
+       <string>Notes:</string>
+      </property>
+      <property name="alignment">
+       <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+      </property>
+     </widget>
+     <widget class="QLineEdit" name="nameEdit">
+      <property name="geometry">
+       <rect>
+        <x>110</x>
+        <y>10</y>
+        <width>701</width>
+        <height>23</height>
+       </rect>
+      </property>
+      <property name="maxLength">
+       <number>128</number>
+      </property>
+      <property name="placeholderText">
+       <string>Name of the mash profile</string>
+      </property>
+     </widget>
+     <widget class="QPlainTextEdit" name="notesEdit">
+      <property name="geometry">
+       <rect>
+        <x>110</x>
+        <y>40</y>
+        <width>791</width>
+        <height>81</height>
+       </rect>
+      </property>
+      <property name="placeholderText">
+       <string>Notes or this mash profile.</string>
+      </property>
+     </widget>
+     <widget class="QPushButton" name="quitButton">
+      <property name="geometry">
+       <rect>
+        <x>110</x>
+        <y>510</y>
+        <width>80</width>
+        <height>23</height>
+       </rect>
+      </property>
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <property name="text">
+       <string>Quit</string>
+      </property>
+      <property name="icon">
+       <iconset>
+        <normaloff>:icons/silk/door_out.png</normaloff>:icons/silk/door_out.png</iconset>
+      </property>
+     </widget>
+     <widget class="QPushButton" name="saveButton">
+      <property name="enabled">
+       <bool>false</bool>
+      </property>
+      <property name="geometry">
+       <rect>
+        <x>820</x>
+        <y>510</y>
+        <width>80</width>
+        <height>23</height>
+       </rect>
+      </property>
+      <property name="text">
+       <string>Save</string>
+      </property>
+      <property name="icon">
+       <iconset>
+        <normaloff>:icons/silk/disk.png</normaloff>:icons/silk/disk.png</iconset>
+      </property>
+     </widget>
+     <widget class="QPushButton" name="deleteButton">
+      <property name="enabled">
+       <bool>false</bool>
+      </property>
+      <property name="geometry">
+       <rect>
+        <x>463</x>
+        <y>510</y>
+        <width>80</width>
+        <height>23</height>
+       </rect>
+      </property>
+      <property name="text">
+       <string>Delete</string>
+      </property>
+      <property name="icon">
+       <iconset>
+        <normaloff>:icons/silk/delete.png</normaloff>:icons/silk/delete.png</iconset>
+      </property>
+     </widget>
+     <widget class="QTableWidget" name="stepsTable">
+      <property name="geometry">
+       <rect>
+        <x>110</x>
+        <y>160</y>
+        <width>791</width>
+        <height>321</height>
+       </rect>
+      </property>
+     </widget>
+     <widget class="QLabel" name="stepsLabel">
+      <property name="geometry">
+       <rect>
+        <x>10</x>
+        <y>160</y>
+        <width>91</width>
+        <height>20</height>
+       </rect>
+      </property>
+      <property name="text">
+       <string>Steps:</string>
+      </property>
+      <property name="alignment">
+       <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+      </property>
+     </widget>
+     <widget class="QLabel" name="totalLabel">
+      <property name="geometry">
+       <rect>
+        <x>10</x>
+        <y>130</y>
+        <width>91</width>
+        <height>20</height>
+       </rect>
+      </property>
+      <property name="text">
+       <string>Total time:</string>
+      </property>
+      <property name="alignment">
+       <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+      </property>
+     </widget>
+     <widget class="QLineEdit" name="totalEdit">
+      <property name="geometry">
+       <rect>
+        <x>110</x>
+        <y>130</y>
+        <width>91</width>
+        <height>23</height>
+       </rect>
+      </property>
+     </widget>
+     <widget class="QPushButton" name="addButton">
+      <property name="geometry">
+       <rect>
+        <x>820</x>
+        <y>130</y>
+        <width>80</width>
+        <height>23</height>
+       </rect>
+      </property>
+      <property name="text">
+       <string>Add step</string>
+      </property>
+      <property name="icon">
+       <iconset resource="../../../../../../home/mbroek/MyProjects/bmsapp/resources/icons.qrc">
+        <normaloff>:/icons/silk/add.png</normaloff>:/icons/silk/add.png</iconset>
+      </property>
+     </widget>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>nameEdit</tabstop>
+  <tabstop>notesEdit</tabstop>
+  <tabstop>quitButton</tabstop>
+  <tabstop>deleteButton</tabstop>
+  <tabstop>saveButton</tabstop>
+ </tabstops>
+ <resources>
+  <include location="../../../../../../home/mbroek/MyProjects/bmsapp/resources/icons.qrc"/>
+ </resources>
+ <connections/>
+</ui>
--- a/ui/MainWindow.ui	Sun Mar 06 15:31:40 2022 +0100
+++ b/ui/MainWindow.ui	Mon Mar 07 17:33:22 2022 +0100
@@ -78,9 +78,9 @@
      <string>Settings</string>
     </property>
     <addaction name="actionWater_profiles"/>
-    <addaction name="actionMash_schedules"/>
+    <addaction name="actionMash_profiles"/>
     <addaction name="actionBeer_styles"/>
-    <addaction name="actionFermentation_schedules"/>
+    <addaction name="actionFermentation_profiles"/>
     <addaction name="separator"/>
     <addaction name="actionSetup"/>
    </widget>
@@ -300,9 +300,9 @@
     <string>Water profiles</string>
    </property>
   </action>
-  <action name="actionMash_schedules">
+  <action name="actionMash_profiles">
    <property name="enabled">
-    <bool>false</bool>
+    <bool>true</bool>
    </property>
    <property name="icon">
     <iconset>
@@ -314,7 +314,7 @@
   </action>
   <action name="actionBeer_styles">
    <property name="enabled">
-    <bool>false</bool>
+    <bool>true</bool>
    </property>
    <property name="icon">
     <iconset>
@@ -324,9 +324,9 @@
     <string>Beer styles</string>
    </property>
   </action>
-  <action name="actionFermentation_schedules">
+  <action name="actionFermentation_profiles">
    <property name="enabled">
-    <bool>false</bool>
+    <bool>true</bool>
    </property>
    <property name="icon">
     <iconset>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ProfileMashs.ui	Mon Mar 07 17:33:22 2022 +0100
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ProfileMashs</class>
+ <widget class="QDialog" name="ProfileMashs">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>1280</width>
+    <height>640</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Dialog</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <widget class="QTableWidget" name="tableMashs">
+     <property name="enabled">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="0">
+    <widget class="QGroupBox" name="groupBox">
+     <property name="enabled">
+      <bool>true</bool>
+     </property>
+     <property name="flat">
+      <bool>false</bool>
+     </property>
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <property name="spacing">
+       <number>6</number>
+      </property>
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item alignment="Qt::AlignLeft">
+       <widget class="QPushButton" name="quitButton">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="minimumSize">
+         <size>
+          <width>80</width>
+          <height>24</height>
+         </size>
+        </property>
+        <property name="text">
+         <string>Quit</string>
+        </property>
+        <property name="icon">
+         <iconset>
+          <normaloff>:icons/silk/door_out.png</normaloff>:icons/silk/door_out.png</iconset>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="insertButton">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="minimumSize">
+         <size>
+          <width>80</width>
+          <height>24</height>
+         </size>
+        </property>
+        <property name="text">
+         <string>New</string>
+        </property>
+        <property name="icon">
+         <iconset>
+          <normaloff>:icons/silk/table_row_insert.png</normaloff>:icons/silk/table_row_insert.png</iconset>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources>
+  <include location="../../../../../../home/mbroek/MyProjects/bmsapp/resources/icons.qrc"/>
+ </resources>
+ <connections/>
+</ui>

mercurial