Redesign the database interface for the recipe editor.

Tue, 05 Apr 2022 22:28:35 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Tue, 05 Apr 2022 22:28:35 +0200
changeset 108
ecfcbee4a9b2
parent 107
bb4607e23065
child 109
1ce50e72a6b1

Redesign the database interface for the recipe editor.

src/EditRecipe.cpp file | annotate | diff | comparison | revisions
src/EditRecipe.h file | annotate | diff | comparison | revisions
--- a/src/EditRecipe.cpp	Mon Apr 04 20:45:04 2022 +0200
+++ b/src/EditRecipe.cpp	Tue Apr 05 22:28:35 2022 +0200
@@ -14,9 +14,9 @@
  * 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 "MainWindow.h"
 #include "EditRecipe.h"
 #include "../ui/ui_EditRecipe.h"
-#include "MainWindow.h"
 #include "Utils.h"
 
 
@@ -25,6 +25,7 @@
     QSqlQuery query;
 
     qDebug() << "EditRecipe record:" << id;
+    recipe = new Recipe;
     ui->setupUi(this);
     this->recno = id;
 
@@ -59,14 +60,51 @@
 	query.exec();
 	query.next();
 
-	ui->lockedEdit->setChecked(query.value(2).toInt() ? true:false);
-	ui->st_nameEdit->setText(query.value(3).toString());
-	ui->st_groupEdit->setText(query.value(4).toString());
-	ui->st_guideEdit->setText(query.value(5).toString());
-	ui->st_catEdit->setText(query.value(6).toString());
-	ui->st_catnrEdit->setText(query.value(7).toString());
-	st_type = query.value(8).toInt();
-	ui->st_typeEdit->setText(s_types[st_type]);
+	recipe->record = query.value(0).toInt();
+	recipe->uuid = query.value(1).toString();
+	recipe->locked = query.value(2).toInt() ? true:false;
+	recipe->st_name = query.value(3).toString();
+	recipe->st_letter = query.value(4).toString();
+	recipe->st_guide = query.value(5).toString();
+	recipe->st_category = query.value(6).toString();
+	recipe->st_category_number = query.value(7).toInt();
+	recipe->st_type = query.value(8).toInt();
+	recipe->st_og_min = query.value(9).toDouble();
+	recipe->st_og_max = query.value(10).toDouble();
+	recipe->st_fg_min = query.value(11).toDouble();
+	recipe->st_fg_max = query.value(12).toDouble();
+	recipe->st_ibu_min = query.value(13).toDouble();
+	recipe->st_ibu_max = query.value(14).toDouble();
+	recipe->st_color_min = query.value(15).toDouble();
+	recipe->st_color_max = query.value(16).toDouble();
+	recipe->st_carb_min = query.value(17).toDouble();
+	recipe->st_carb_max = query.value(18).toDouble();
+	recipe->st_abv_min = query.value(19).toDouble();
+	recipe->st_abv_max = query.value(20).toDouble();
+
+	recipe->name = query.value(21).toString();
+	recipe->notes = query.value(22).toString();
+	recipe->type = query.value(23).toInt();
+	recipe->batch_size = query.value(24).toDouble();
+	recipe->boil_size = query.value(25).toDouble();
+	recipe->boil_time = query.value(26).toDouble();
+	recipe->efficiency = query.value(27).toDouble();
+	recipe->est_og = query.value(28).toDouble();
+	recipe->est_fg = query.value(29).toDouble();
+	recipe->est_abv = query.value(30).toDouble();
+	recipe->est_color = query.value(31).toDouble();
+	recipe->color_method = query.value(32).toInt();
+	recipe->est_ibu = query.value(33).toDouble();
+	recipe->ibu_method = query.value(34).toInt();
+	recipe->est_carb = query.value(35).toDouble();
+
+	ui->lockedEdit->setChecked(recipe->locked);
+	ui->st_nameEdit->setText(recipe->st_name);
+	ui->st_groupEdit->setText(recipe->st_letter);
+	ui->st_guideEdit->setText(recipe->st_guide);
+	ui->st_catEdit->setText(recipe->st_category);
+	ui->st_catnrEdit->setText(QString("%1").arg(recipe->st_category_number));
+	ui->st_typeEdit->setText(s_types[recipe->st_type]);
 
 	ui->nameEdit->setText(query.value(21).toString());
 	ui->notesEdit->setPlainText(query.value(22).toString());
@@ -456,7 +494,6 @@
 void EditRecipe::calcFermentables()
 {
     int		i;
-    bool	my_100 = false;
     double	psugar = 0, pcara = 0, d, s = 0, x, color;
     double	vol = 0;		// Volume sugars after boil
     double	addedS = 0;		// Added sugars after boil
@@ -476,6 +513,7 @@
     QJsonObject obj;
 
     qDebug() << "calcFermentables()";
+    use_to100 = false;
 
     /*
      * Get average mashtemp and mashtime from the Mash schedule.
@@ -505,9 +543,8 @@
 
     for (i = 0; i < this->fermentables.array().size(); i++) {
 	obj = this->fermentables.array().at(i).toObject();
-	if (obj["f_adjust_to_total_100"].toInt()) {
-	    my_100 = true;
-	}
+	if (obj["f_adjust_to_total_100"].toInt())
+	    use_to100 = true;
 	if (obj["f_type"].toInt() == 1 && obj["f_added"].toInt() < 4)		// Sugars
 	    psugar += obj["f_percentage"].toDouble();
 	if (obj["f_graintype"].toInt() == 2 && obj["f_added"].toInt() < 4)	// Crystal/Cara
@@ -725,7 +762,7 @@
 	query.bindValue(":st_letter", ui->st_groupEdit->text());
 	query.bindValue(":st_guide", ui->st_guideEdit->text());
 	query.bindValue(":st_category", ui->st_catEdit->text());
-	query.bindValue(":st_catnr", st_type);
+	query.bindValue(":st_catnr", recipe->st_type);
 	query.bindValue(":st_og_min", QString("%1").arg(ui->est_ogShow->minval(), 4, 'f', 3, '0'));
 	query.bindValue(":st_og_max", QString("%1").arg(ui->est_ogShow->maxval(), 4, 'f', 3, '0'));
 	query.bindValue(":st_fg_min", QString("%1").arg(ui->est_fgShow->minval(), 4, 'f', 3, '0'));
@@ -829,13 +866,32 @@
         query.next();
     }
     // Set relevant fields and update ranges.
-    ui->st_nameEdit->setText(query.value(1).toString());
-    ui->st_catEdit->setText(query.value(2).toString());
-    ui->st_catnrEdit->setText(query.value(3).toString());
-    ui->st_groupEdit->setText(query.value(4).toString());
-    ui->st_guideEdit->setText(query.value(5).toString());
-    st_type = query.value(6).toInt();
-    ui->st_typeEdit->setText(s_types[st_type]);
+    recipe->st_name = query.value(1).toString();
+    recipe->st_category = query.value(2).toString();
+    recipe->st_category_number = query.value(3).toInt();
+    recipe->st_letter = query.value(4).toString();
+    recipe->st_guide = query.value(5).toString();
+    recipe->st_type = query.value(6).toInt();
+    recipe->st_og_min = query.value(7).toDouble();
+    recipe->st_og_max = query.value(8).toDouble();
+    recipe->st_fg_min = query.value(9).toDouble();
+    recipe->st_fg_max = query.value(10).toDouble();
+    recipe->st_ibu_min = query.value(11).toDouble();
+    recipe->st_ibu_max = query.value(12).toDouble();
+    recipe->st_color_min = query.value(13).toDouble();
+    recipe->st_color_max = query.value(14).toDouble();
+    recipe->st_carb_min = query.value(15).toDouble();
+    recipe->st_carb_max = query.value(16).toDouble();
+    recipe->st_abv_min = query.value(17).toDouble();
+    recipe->st_abv_max = query.value(18).toDouble();
+
+    ui->st_nameEdit->setText(recipe->st_name);
+    ui->st_groupEdit->setText(recipe->st_letter);
+    ui->st_guideEdit->setText(recipe->st_guide);
+    ui->st_catEdit->setText(recipe->st_category);
+    ui->st_catnrEdit->setText(QString("%1").arg(recipe->st_category_number));
+    ui->st_typeEdit->setText(s_types[recipe->st_type]);
+
     ui->est_ogShow->setRange(query.value(7).toDouble(), query.value(8).toDouble());
     ui->est_fgShow->setRange(query.value(9).toDouble(), query.value(10).toDouble());
     ui->est_ibuShow->setRange(query.value(11).toDouble(), query.value(12).toDouble());
@@ -859,10 +915,13 @@
     QTableWidgetItem *item;
     QJsonArray array;
 
+    qDebug() << "fermentable_Json()";
     ui->fermentablesTable->sortItems(23, Qt::DescendingOrder);   // Sort on amount.
+    qDebug() << "fermentable_Json() 1";
 
     for (int i = 0; i < ui->fermentablesTable->rowCount(); i++) {
 
+	qDebug() << "fermentable_Json() 2" << i;
         QJsonObject obj;
 	item = ui->fermentablesTable->item(i, 12);
 	obj.insert("f_acid_to_ph_57", item->text().toDouble());
@@ -908,7 +967,7 @@
         obj.insert("f_type", item->text().toDouble());
 	item = ui->fermentablesTable->item(i, 25);
         obj.insert("f_yield", item->text().toDouble());
-//	qDebug() << "fermentable_Json" << i << obj;
+	qDebug() << "fermentable_Json" << i << obj;
 	array.append(obj);      /* Append this object */
     }
 
@@ -927,29 +986,6 @@
 
     qDebug() << "Cell at row " + QString::number(nRow) + " column " + QString::number(nCol) + " was changed.";
 
-    if (nCol == 9) {	// 100% checkbox
-	this->ignoreChanges = true;
-
-	if (ui->fermentablesTable->item(nRow, nCol)->checkState() == Qt::Checked) {
-	    /*
-	     * This row is checked. Remove any other checked item.
-	     */
-	    for (int i = 0; i < ui->fermentablesTable->rowCount(); i++) {
-		if (i != nRow) {
-		    QTableWidgetItem *checkBoxItem = ui->fermentablesTable->item(i, nCol);
-		    checkBoxItem->setCheckState(Qt::Unchecked);
-		    ui->fermentablesTable->setItem(i, nCol, checkBoxItem);
-		}
-	    }
-	} else {
-	    /*
-	     * Unchecked, start working with amounts instead of percentages.
-	     */
-	}
-	qDebug() << ui->fermentablesTable->item(nRow, nCol)->checkState();
-	this->ignoreChanges = false;
-    }
-
     // TODO: some checks and auto fixes.
 //    make_Json();
 }
@@ -975,16 +1011,195 @@
 }
 
 
+void EditRecipe::ferment_amount_changed(double val)
+{
+    QTableWidgetItem *item;
+    double	total = 0;
+
+    qDebug() << "ferment_amount_changed()" << editrow << val;
+
+    this->ignoreChanges = true;
+
+    item = new QTableWidgetItem(QString("%1 Kg").arg(val, 4, 'f', 3, '0'));
+    item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+    ui->fermentablesTable->setItem(editrow, 7, item);
+
+    item = new QTableWidgetItem(QString("%1").arg(val, 4, 'f', 3, '0'));
+    ui->fermentablesTable->setItem(editrow, 23, item);
+
+    for (int i = 0; i < ui->fermentablesTable->rowCount(); i++) {
+	item = ui->fermentablesTable->item(i, 23);
+	total += item->text().toDouble();
+    }
+    qDebug() << "total weight now" << total;
+
+//    for (int i = 0; i < ui->fermentablesTable->rowCount(); i++) {
+//        item = ui->fermentablesTable->item(i, 23);
+//        total += item->text().toDouble();
+//    }
+
+    this->ignoreChanges = false;
+
+//    fermentable_Json();
+}
+
+void EditRecipe::ferment_pct_changed(double val)
+{
+    qDebug() << "ferment_pct_changed()" << val;
+}
+
+
 void EditRecipe::on_editFermentRow_clicked()
 {
     QPushButton *pb = qobject_cast<QPushButton *>(QObject::sender());
-    int row = pb->objectName().toInt();
-    qDebug() << "Edit fermentable row" << row;
-    // init fermentrow and fermentbackup.
-    // connect popup to refreshfermentables and calcFermentables
-    // popup editor
-    // on abort, restore fermentbackup
-    // on accept, fermentrow to final
+    editrow = pb->objectName().toInt();
+    qDebug() << "Edit fermentable row" << editrow;
+    work = this->fermentables.array().at(editrow).toObject();
+    backup = this->fermentables.array().at(editrow).toObject();
+
+    qDebug() << work;
+
+    QDialog* dialog = new QDialog(this);
+    dialog->resize(738, 287);
+    QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog);
+    buttonBox->setObjectName(QString::fromUtf8("buttonBox"));
+    buttonBox->setGeometry(QRect(30, 240, 671, 32));
+    buttonBox->setLayoutDirection(Qt::LeftToRight);
+    buttonBox->setOrientation(Qt::Horizontal);
+    buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
+    buttonBox->setCenterButtons(true);
+    QLabel *nameLabel = new QLabel(dialog);
+    nameLabel->setObjectName(QString::fromUtf8("nameLabel"));
+    nameLabel->setText(tr("Current ingredient:"));
+    nameLabel->setGeometry(QRect(10, 10, 141, 20));
+    nameLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
+    QLabel *supplierLabel = new QLabel(dialog);
+    supplierLabel->setObjectName(QString::fromUtf8("supplierLabel"));
+    supplierLabel->setText(tr("Supplier:"));
+    supplierLabel->setGeometry(QRect(10, 40, 141, 20));
+    supplierLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
+    QLabel *amountLabel = new QLabel(dialog);
+    amountLabel->setObjectName(QString::fromUtf8("amountLabel"));
+    amountLabel->setText(tr("Amount in kg:"));
+    amountLabel->setGeometry(QRect(10, 100, 141, 20));
+    amountLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
+    QLabel *pctLabel = new QLabel(dialog);
+    pctLabel->setObjectName(QString::fromUtf8("pctLabel"));
+    pctLabel->setText(tr("Percentage in batch:"));
+    pctLabel->setGeometry(QRect(10, 130, 141, 20));
+    pctLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
+    QLabel *to100Label = new QLabel(dialog);
+    to100Label->setObjectName(QString::fromUtf8("to100Label"));
+    to100Label->setText(tr("Auto fill to 100%:"));
+    to100Label->setGeometry(QRect(10, 160, 141, 20));
+    to100Label->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
+    QLabel *addedLabel = new QLabel(dialog);
+    addedLabel->setObjectName(QString::fromUtf8("addedLabel"));
+    addedLabel->setText(tr("Use at:"));
+    addedLabel->setGeometry(QRect(10, 190, 141, 20));
+    addedLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
+    QLabel *selectLabel = new QLabel(dialog);
+    selectLabel->setObjectName(QString::fromUtf8("selectLabel"));
+    selectLabel->setText(tr("Select ingredient:"));
+    selectLabel->setGeometry(QRect(10, 70, 141, 20));
+    selectLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
+    QLabel *instockLabel = new QLabel(dialog);
+    instockLabel->setObjectName(QString::fromUtf8("instockLabel"));
+    instockLabel->setText(tr("In stock:"));
+    instockLabel->setGeometry(QRect(420, 70, 121, 20));
+    instockLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
+    QLabel *maxLabel = new QLabel(dialog);
+    maxLabel->setObjectName(QString::fromUtf8("maxLabel"));
+    maxLabel->setText(tr("Max in batch:"));
+    maxLabel->setGeometry(QRect(420, 130, 121, 20));
+    maxLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
+
+    selectEdit = new QComboBox(dialog);
+    selectEdit->setObjectName(QString::fromUtf8("selectEdit"));
+    selectEdit->setGeometry(QRect(160, 70, 251, 23));
+    nameEdit = new QLineEdit(dialog);
+    nameEdit->setObjectName(QString::fromUtf8("nameEdit"));
+    nameEdit->setText(work["f_name"].toString());
+    nameEdit->setGeometry(QRect(160, 10, 511, 23));
+    nameEdit->setReadOnly(true);
+    supplierEdit = new QLineEdit(dialog);
+    supplierEdit->setObjectName(QString::fromUtf8("supplierEdit"));
+    supplierEdit->setText(work["f_supplier"].toString());
+    supplierEdit->setGeometry(QRect(160, 40, 511, 23));
+    supplierEdit->setReadOnly(true);
+    amountEdit = new QDoubleSpinBox(dialog);
+    amountEdit->setObjectName(QString::fromUtf8("amountEdit"));
+    amountEdit->setGeometry(QRect(160, 100, 121, 24));
+    amountEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
+    amountEdit->setAccelerated(true);
+    amountEdit->setDecimals(3);
+    amountEdit->setReadOnly(use_to100);
+    amountEdit->setMaximum(100000.0);
+    amountEdit->setSingleStep(0.0010);
+    amountEdit->setValue(work["f_amount"].toDouble());
+    connect(amountEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditRecipe::ferment_amount_changed);
+
+    pctEdit = new QDoubleSpinBox(dialog);
+    pctEdit->setObjectName(QString::fromUtf8("pctEdit"));
+    pctEdit->setGeometry(QRect(160, 130, 121, 24));
+    pctEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
+    pctEdit->setAccelerated(true);
+    pctEdit->setDecimals(1);
+    if (use_to100) {
+    	if (work["f_adjust_to_total_100"].toInt())
+	    pctEdit->setReadOnly(true);
+    	else
+    	    pctEdit->setReadOnly(false);
+    } else {
+	pctEdit->setReadOnly(true);
+    }
+    pctEdit->setMaximum(100.0);
+    pctEdit->setSingleStep(0.1);
+    pctEdit->setValue(work["f_percentage"].toDouble());
+    connect(pctEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditRecipe::ferment_pct_changed);
+
+    addedEdit = new QComboBox(dialog);
+    addedEdit->setObjectName(QString::fromUtf8("addedEdit"));
+    addedEdit->setGeometry(QRect(160, 190, 161, 23));
+    addedEdit->addItem(tr("Mash"));
+    addedEdit->addItem(tr("Boil"));
+    addedEdit->addItem(tr("Fermentation"));
+    addedEdit->addItem(tr("Lagering"));
+    addedEdit->addItem(tr("Bottle"));
+    addedEdit->addItem(tr("Kegs"));
+    addedEdit->setCurrentIndex(work["f_added"].toInt());
+
+    to100Edit = new QCheckBox(dialog);
+    to100Edit->setObjectName(QString::fromUtf8("to100Edit"));
+    to100Edit->setGeometry(QRect(160, 160, 85, 21));
+    to100Edit->setChecked(work["f_adjust_to_total_100"].toInt() ? true:false);
+
+    instockEdit = new QCheckBox(dialog);
+    instockEdit->setObjectName(QString::fromUtf8("instockEdit"));
+    instockEdit->setGeometry(QRect(550, 70, 85, 21));
+
+    maxEdit = new QDoubleSpinBox(dialog);
+    maxEdit->setObjectName(QString::fromUtf8("maxEdit"));
+    maxEdit->setGeometry(QRect(550, 130, 121, 24));
+    maxEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
+    maxEdit->setReadOnly(true);
+    maxEdit->setButtonSymbols(QAbstractSpinBox::NoButtons);
+    maxEdit->setDecimals(1);
+    maxEdit->setValue(work["f_max_in_batch"].toDouble());
+
+    connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject()));
+    connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept()));
+    dialog->exec();
+    if (dialog->result() == QDialog::Rejected) {
+	qDebug() << "rejected";
+	// restore fermentbackup
+    } else {
+	qDebug() << "accepted";
+	// fermentrow to final
+
+	//fermentable_Json(); segfault !
+    }
+
     // disconnect
     // return
 }
--- a/src/EditRecipe.h	Mon Apr 04 20:45:04 2022 +0200
+++ b/src/EditRecipe.h	Tue Apr 05 22:28:35 2022 +0200
@@ -4,18 +4,224 @@
 #include <QDialog>
 #include <QStringList>
 #include <QJsonDocument>
+#include <QJsonObject>
+#include <QDoubleSpinBox>
+#include <QCheckBox>
+#include <QComboBox>
+#include <QLineEdit>
+#include <QDialogButtonBox>
+#include <QList>
+
+
+/*
+ * Fermentables, Hops, Miscs, Yeasts and Mashs are stored in the 
+ * database in json arrays. These are the QList structures.
+ */
+struct Fermentables
+{
+    QString	f_name;
+    QString	f_origin;
+    QString	f_supplier;
+    double	f_amount;
+    double	f_cost;
+    int		f_type;
+    double	f_yield;
+    double	f_color;
+    double	f_coarse_fine_diff;
+    double	f_moisture;
+    double	f_diastatic_power;
+    double	f_protein;
+    double	f_dissolved_protein;
+    double	f_max_in_batch;
+    int		f_graintype;
+    int		f_added;
+    bool	f_recommend_mash;
+    bool	f_add_after_boil;
+    bool	f_adjust_to_total_100;
+    double	f_percentage;
+    double	f_di_ph;
+    double	f_acid_to_ph_57;
+};
+
+
+struct Hops
+{
+    QString	h_name;
+    QString	h_origin;
+    double	h_amount;
+    double	h_cost;
+    int		h_type;
+    int		h_form;
+    int		h_useat;
+    double	h_time;
+    double	h_alpha;
+    double	h_beta;
+    double	h_hsi;
+    double	h_humulene;
+    double	h_caryophyllene;
+    double	h_cohumulone;
+    double	h_myrcene;
+    double	h_total_oil;
+};
+
+
+struct Miscs
+{
+    QString	m_name;
+    double	m_amount;
+    int		m_type;
+    int		m_use_use;
+    int		m_time;
+    bool	m_amount_is_weight;
+    double	m_cost;
+};
+
+
+struct Yeasts
+{
+    QString	y_name;
+    QString	y_laboratory;
+    QString	y_product_id;
+    double	y_amount;
+    int		y_type;
+    int		y_form;
+    double	y_min_temperature;
+    double	y_max_temperature;
+    int		y_flocculation;
+    double	y_attenuation;
+    double	y_cells;
+    double	y_tolerance;
+    double	y_inventory;
+    int		y_use;
+    bool	y_sta1;
+    bool	y_bacteria;
+    bool	y_harvest_top;
+    double	y_harvest_time;
+    double	y_pitch_temperature;
+    bool	y_pofpos;
+    int		y_zymocide;
+    double	y_gr_hl_lo;
+    double	y_sg_lo;
+    double	y_gr_hl_hi;
+    double	y_sg_hi;
+    double	y_cost;
+};
 
 
-typedef struct f_edit {
-    int 	f_row;				///< Row to edit, -1 for insert.
-    QString	f_name;				///< Fermentable name
-    QString	f_supplier;			///< Fermentable supplier
-    double	f_max_in_batch;			///< Max percentage in batch
-    double	f_percentage;			///< Current percentage in batch
-    double	f_amount;			///< Amount in kg
-    bool	f_adjust_to_total_100;		///< Adjust amount/percentage to 100%
-    int		f_added;			///< When to add fermentable
-} fedit;
+struct Mashs
+{
+    QString	step_name;
+    int		step_type;
+    double	step_volume;		// May not be present
+    double	step_infuse_amount;	// May not be present
+    double	step_infuse_temp;	// May not be present
+    double	step_temp;
+    double	step_time;
+    double	ramp_time;
+    double	end_time;
+    double	step_wg_ratio;		// May not be present
+};
+
+
+/*
+ * The main recipe record stored in the database.
+ */
+struct Recipe
+{
+    int		record;
+    QString	uuid;
+    bool	locked;
+    QString	st_name;
+    QString	st_letter;
+    QString	st_guide;
+    QString	st_category;
+    int		st_category_number;
+    int		st_type;
+    double	st_og_min;
+    double	st_og_max;
+    double	st_fg_min;
+    double	st_fg_max;
+    double	st_ibu_min;
+    double	st_ibu_max;
+    double	st_color_min;
+    double	st_color_max;
+    double	st_carb_min;
+    double	st_carb_max;
+    double	st_abv_min;
+    double	st_abv_max;
+
+    QString	name;
+    QString	notes;
+    int		type;
+    double	batch_size;
+    double	boil_size;
+    double	boil_time;
+    double	efficiency;
+    double	est_og;
+    double	est_fg;
+    double	est_abv;
+    double	est_color;
+    int		color_method;
+    double	est_ibu;
+    int		ibu_method;
+    double	est_carb;
+
+    double	sparge_temp;
+    double	sparge_ph;
+    double	sparge_volume;
+    int		sparge_source;
+    int		sparge_acid_type;
+    double	sparge_acid_perc;
+    double	sparge_acid_amount;
+    double	mash_ph;
+    double	mash_name;
+    bool	calc_acid;
+
+    QString	w1_name;			///< Water source 1
+    double	w1_amount;
+    double	w1_calcium;
+    double	w1_sulfate;
+    double	w1_chloride;
+    double	w1_sodium;
+    double	w1_magnesium;
+    double	w1_total_alkalinity;
+    double	w1_ph;
+    double	w1_cost;
+    QString     w2_name;			///< Water source 2
+    double      w2_amount;
+    double      w2_calcium;
+    double      w2_sulfate;
+    double      w2_chloride;
+    double      w2_sodium;
+    double      w2_magnesium;
+    double      w2_total_alkalinity;
+    double      w2_ph;
+    double      w2_cost;
+    double      wg_amount;			///< Mixed water
+    double      wg_calcium;
+    double      wg_sulfate;
+    double      wg_chloride;
+    double      wg_sodium;
+    double      wg_magnesium;
+    double      wg_total_alkalinity;
+    double      wg_ph;
+    double      wb_calcium;			///< Treated water
+    double      wb_sulfate;
+    double      wb_chloride;
+    double      wb_sodium;
+    double      wb_magnesium;
+    double      wb_total_alkalinity;
+    double      wb_ph;
+    double	wa_acid_name;
+    double	wa_acid_perc;
+    double	wa_base_name;
+
+    QList<Fermentables>	fermentables;
+    QList<Hops>		hops;
+    QList<Miscs>	miscs;
+    QList<Yeasts>	yeasts;
+    QList<Mashs>	mashs;
+};
 
 
 namespace Ui {
@@ -47,6 +253,8 @@
     void refreshMashs();
     void refreshAll();
     void cell_Fermentable_changed(int nRow, int nCol);
+    void ferment_amount_changed(double val);
+    void ferment_pct_changed(double val);
     void on_deleteFermentRow_clicked();
     void on_editFermentRow_clicked();
 
@@ -64,11 +272,20 @@
     QString bar_red = "QProgressBar::chunk {background: #FF0000;}";
     QString bar_orange = "QProgressBar::chunk {background: #EB7331;}";
     QString bar_green = "QProgressBar::chunk {background: #008C00;}";
-    int recno, st_type = 0;
+    int recno, editrow;
     bool textIsChanged = false;
     bool ignoreChanges = false;
+    bool use_to100 = false;
+    Recipe *recipe;
+    /*
+     * Variables for popup ingredients editing.
+     */
     QJsonDocument fermentables, hops, miscs, yeasts, mashs;
-    fedit fermentrow, fermentbackup;
+    QJsonObject work, backup;
+    QComboBox *selectEdit, *addedEdit;
+    QLineEdit *nameEdit, *supplierEdit;
+    QDoubleSpinBox *amountEdit, *pctEdit, *maxEdit;
+    QCheckBox *to100Edit, *instockEdit;
 
     void WindowTitle();
     void fermentable_Json();

mercurial