src/EditProductTab3.cpp

changeset 175
f1ed3a2a94e9
child 177
62b8d701cd88
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/EditProductTab3.cpp	Thu Apr 28 22:49:13 2022 +0200
@@ -0,0 +1,967 @@
+/**
+ * EditProduct.cpp is part of bmsapp.
+ *
+ * Tab 3, fermentables
+ *
+ * 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/>.
+ */
+
+
+
+bool EditProduct::ferment_sort_test(const Fermentables &D1, const Fermentables &D2)
+{
+    if (D1.f_added > D2.f_added)
+	return false;
+    if (D1.f_added < D2.f_added)
+	return true;
+    return (D1.f_amount >= D2.f_amount) && (D1.f_color < D2.f_color);
+}
+
+
+void EditProduct::to100Fermentables(int row)
+{
+    if (product->fermentables.at(row).f_adjust_to_total_100) {
+	QWidget *pWidget = new QWidget();
+	QLabel *label = new QLabel;
+	label->setPixmap(QPixmap(":icons/silk/tick.png"));
+	QHBoxLayout *pLayout = new QHBoxLayout(pWidget);
+	pLayout->addWidget(label);
+	pLayout->setAlignment(Qt::AlignCenter);
+	pLayout->setContentsMargins(0, 0, 0, 0);
+	pWidget->setLayout(pLayout);
+	ui->fermentablesTable->setCellWidget(row, 9, pWidget);
+    } else {
+	ui->fermentablesTable->removeCellWidget(row, 9);
+    }
+}
+
+
+void EditProduct::refreshFermentables()
+{
+    QString w;
+    QWidget* pWidget;
+    QHBoxLayout* pLayout;
+    QTableWidgetItem *item;
+
+    qDebug() << "refreshFermentables" << product->fermentables.size();
+    std::sort(product->fermentables.begin(), product->fermentables.end(), ferment_sort_test);
+
+    const QStringList labels({tr("Supplier"), tr("Fermentable"), tr("EBC"), tr("Type"), tr("Graintype"), tr("When"), tr("Yield"),
+		    	      tr("Amount"), tr("Procent"), tr("100%"), tr("Delete"), tr("Edit") });
+    ui->fermentablesTable->setColumnCount(12);
+    ui->fermentablesTable->setColumnWidth(0, 150);     /* Supplier	*/
+    ui->fermentablesTable->setColumnWidth(1, 225);     /* Fermentable	*/
+    ui->fermentablesTable->setColumnWidth(2,  50);     /* Color		*/
+    ui->fermentablesTable->setColumnWidth(3,  75);     /* Type		*/
+    ui->fermentablesTable->setColumnWidth(4,  75);     /* Graintype	*/
+    ui->fermentablesTable->setColumnWidth(5,  82);     /* Added		*/
+    ui->fermentablesTable->setColumnWidth(6,  60);     /* Yield		*/
+    ui->fermentablesTable->setColumnWidth(7,  90);     /* Amount	*/
+    ui->fermentablesTable->setColumnWidth(8,  60);     /* Procent	*/
+    ui->fermentablesTable->setColumnWidth(9,  50);     /* 100%		*/
+    ui->fermentablesTable->setColumnWidth(10, 80);     /* Delete	*/
+    ui->fermentablesTable->setColumnWidth(11, 80);     /* Edit		*/
+    ui->fermentablesTable->setHorizontalHeaderLabels(labels);
+    ui->fermentablesTable->verticalHeader()->hide();
+    ui->fermentablesTable->setRowCount(product->fermentables.size());
+
+    for (int i = 0; i < product->fermentables.size(); i++) {
+
+	ui->fermentablesTable->setItem(i, 0, new QTableWidgetItem(product->fermentables.at(i).f_supplier));
+	ui->fermentablesTable->setItem(i, 1, new QTableWidgetItem(product->fermentables.at(i).f_name));
+
+        w = QString("%1").arg(product->fermentables.at(i).f_color, 1, 'f', 0, '0');
+        item = new QTableWidgetItem(w);
+        item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+        ui->fermentablesTable->setItem(i, 2, item);
+
+	item = new QTableWidgetItem(fermentable_types[product->fermentables.at(i).f_type]);
+	item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter);
+	ui->fermentablesTable->setItem(i, 3, item);
+
+        item = new QTableWidgetItem(fermentable_graintypes[product->fermentables.at(i).f_graintype]);
+        item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter);
+        ui->fermentablesTable->setItem(i, 4, item);
+
+        item = new QTableWidgetItem(fermentable_added[product->fermentables.at(i).f_added]);
+        item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter);
+        ui->fermentablesTable->setItem(i, 5, item);
+
+        item = new QTableWidgetItem(QString("%1%").arg(product->fermentables.at(i).f_yield, 2, 'f', 1, '0'));
+        item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+        ui->fermentablesTable->setItem(i, 6, item);
+
+        item = new QTableWidgetItem(QString("%1 Kg").arg(product->fermentables.at(i).f_amount, 4, 'f', 3, '0'));
+        item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+        ui->fermentablesTable->setItem(i, 7, item);
+
+	if (product->fermentables.at(i).f_added < 4) {
+            item = new QTableWidgetItem(QString("%1%").arg(product->fermentables.at(i).f_percentage, 2, 'f', 1, '0'));
+	} else {
+	    item = new QTableWidgetItem(QString(""));	// Blank for bottling and kegging.
+	}
+        item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+        ui->fermentablesTable->setItem(i, 8, item);
+
+	to100Fermentables(i);
+
+	/* Add the Delete row button */
+        pWidget = new QWidget();
+        QPushButton* btn_dele = new QPushButton();
+        btn_dele->setObjectName(QString("%1").arg(i));  /* Send row with the button */
+        btn_dele->setText(tr("Delete"));
+        connect(btn_dele, SIGNAL(clicked()), this, SLOT(deleteFermentRow_clicked()));
+        pLayout = new QHBoxLayout(pWidget);
+        pLayout->addWidget(btn_dele);
+        pLayout->setContentsMargins(5, 0, 5, 0);
+        pWidget->setLayout(pLayout);
+        ui->fermentablesTable->setCellWidget(i, 10, pWidget);
+
+	pWidget = new QWidget();
+	QPushButton* btn_edit = new QPushButton();
+	btn_edit->setObjectName(QString("%1").arg(i));  /* Send row with the button */
+	btn_edit->setText(tr("Edit"));
+	connect(btn_edit, SIGNAL(clicked()), this, SLOT(editFermentRow_clicked()));
+	pLayout = new QHBoxLayout(pWidget);
+        pLayout->addWidget(btn_edit);
+	pLayout->setContentsMargins(5, 0, 5, 0);
+        pWidget->setLayout(pLayout);
+        ui->fermentablesTable->setCellWidget(i, 11, pWidget);
+    }
+}
+
+
+void EditProduct::calcFermentables()
+{
+    int		i;
+    double	psugar = 0, pcara = 0, d, s = 0, x, color;
+    double	vol = 0;		// Volume sugars after boil
+    double	addedS = 0;		// Added sugars after boil
+    double	addedmass = 0;		// Added mass after boil
+    double	mvol = 0;		// Mash volume
+    double	lintner = 0;		// Total product lintner
+    double	sugarsf = 0;		// fermentable sugars mash + boil
+    double	sugarsm = 0;		// fermentable sugars in mash
+    double	sugardensity = 1.611;	// kg/l in solution
+    double	mashtime = 0;		// Total mash time
+    double	mashtemp = 0;		// Average mash temperature
+    double	mashinfuse = 0;		// Mash infuse amount
+    double	colort = 0;		// Colors srm * vol totals
+    double	colorh = 0;		// Colors ebc * vol * kt
+    double	colorn = 0;		// Colors ebc * pt * pct
+
+    qDebug() << "calcFermentables()";
+
+    /*
+     * Get average mashtemp and mashtime from the Mash schedule.
+     * It is possible that the schedule is not (yet) present.
+     */
+    if (product->mashs.size() > 0) {
+	for (i = 0; i < product->mashs.size(); i++) {
+	    if (product->mashs.at(i).step_type == 0)		// Infusion
+		mashinfuse += product->mashs.at(i).step_infuse_amount;
+	    if (product->mashs.at(i).step_temp < 75) {		// Ignore mashout
+		mashtime += product->mashs.at(i).step_time;
+		mashtemp += product->mashs.at(i).step_time * product->mashs.at(i).step_temp;
+	    }
+	}
+	mashtemp = mashtemp / mashtime;
+	mvol = mashinfuse;
+	qDebug() << "  mash time" << mashtime << "temp" << mashtemp << "infuse" << mashinfuse;
+    } else {
+	qDebug() << "  no mash schedule";
+    }
+
+    const QSignalBlocker blocker1(ui->est_ogEdit);
+    const QSignalBlocker blocker2(ui->est_og2Edit);
+
+    if (product->fermentables.size() < 1) {
+	qDebug() << "  no fermentables, return.";
+	product->est_og = 0.980;
+    	ui->est_ogEdit->setValue(0.980);
+    	ui->est_og2Edit->setValue(0.980);
+    	ui->est_og3Edit->setValue(0.980);
+    	ui->est_ogShow->setValue(0.980);
+    	product->est_color = 0;
+    	ui->est_colorEdit->setValue(0);
+    	ui->est_colorEdit->setStyleSheet(Utils::ebc_to_style(0));
+    	ui->est_color2Edit->setValue(0);
+    	ui->est_color2Edit->setStyleSheet(Utils::ebc_to_style(0));
+    	ui->est_colorShow->setValue(0);
+	ui->perc_mashShow->setValue(0);
+	ui->perc_sugarsShow->setValue(0);
+	ui->perc_caraShow->setValue(0);
+	ui->lintnerShow->setValue(0);
+    	product->est_fg = 0.980;
+    	ui->est_fgEdit->setValue(0.980);
+    	ui->est_fg3Edit->setValue(0.980);
+    	ui->est_fgShow->setValue(0.980);
+    	ui->est_abvEdit->setValue(0);
+    	ui->est_abv2Edit->setValue(0);
+    	ui->est_abvShow->setValue(0);
+    	product->est_abv = 0;
+    	ui->calEdit->setValue(0);
+	product->mashs_kg = 0;
+	return;
+    }
+    qDebug() << "  adjust to 100" << product->fermentables_use100;
+
+    product->mashs_kg = 0;
+    for (i = 0; i < product->fermentables.size(); i++) {
+	if (product->fermentables.at(i).f_type == 1 && product->fermentables.at(i).f_added < 4)		// Sugars
+	    psugar += product->fermentables.at(i).f_percentage;
+	if (product->fermentables.at(i).f_graintype == 2 && product->fermentables.at(i).f_added < 4)	// Crystal/Cara
+	    pcara += product->fermentables.at(i).f_percentage;
+	d = product->fermentables.at(i).f_amount * (product->fermentables.at(i).f_yield / 100) * (1 - product->fermentables.at(i).f_moisture / 100);
+	if (product->fermentables.at(i).f_added == 0) {					// Mash
+	    if (mvol > 0) {							// If mash volume is known
+		mvol += product->fermentables.at(i).f_amount * product->fermentables.at(i).f_moisture / 100;
+		s += d;
+	    }
+	    d = ui->efficiencyEdit->value() / 100 * d;
+	    sugarsm += d;
+	    product->mashs_kg += product->fermentables.at(i).f_amount;
+	}
+	if (product->fermentables.at(i).f_added == 0 || product->fermentables.at(i).f_added == 1)		// Mash or boil
+	    sugarsf += d;
+	if (product->fermentables.at(i).f_added == 2 || product->fermentables.at(i).f_added == 3) {		// Fermentation or lagering
+	    x = (product->fermentables.at(i).f_yield / 100) * (1 - product->fermentables.at(i).f_moisture / 100);
+	    addedS += product->fermentables.at(i).f_amount * x;
+	    addedmass += product->fermentables.at(i).f_amount;
+	    vol += (x * sugardensity + (1 - x) * 1) * product->fermentables.at(i).f_amount;
+	}
+	if (product->fermentables.at(i).f_added == 0 &&
+	    (product->fermentables.at(i).f_type == 0 || product->fermentables.at(i).f_type == 4) &&
+	    product->fermentables.at(i).f_color < 50) {
+	    lintner += product->fermentables.at(i).f_diastatic_power * product->fermentables.at(i).f_amount;
+	}
+	if (product->fermentables.at(i).f_added < 4) {
+	    colort += product->fermentables.at(i).f_amount * Utils::ebc_to_srm(product->fermentables.at(i).f_color);
+	    colorh += product->fermentables.at(i).f_amount * product->fermentables.at(i).f_color * Utils::get_kt(product->fermentables.at(i).f_color);
+	    colorn += (product->fermentables.at(i).f_percentage / 100) * product->fermentables.at(i).f_color;	// For 8.6 Pt wort.
+	}
+    }
+    qDebug() << "  colort" << colort << "colorh" << colorh << "colorn" << colorn;
+    qDebug() << "  psugar" << psugar << "pcara" << pcara << "mvol" << mvol;
+    qDebug() << "  sugarsf" << sugarsf << "sugarsm" << sugarsm;
+
+    double og = Utils::estimate_sg(sugarsf + addedS, product->batch_size);
+    qDebug() << "  OG" << ui->est_ogEdit->value() << og;
+    product->est_og = og;
+    ui->est_ogEdit->setValue(og);
+    ui->est_og2Edit->setValue(og);
+    ui->est_og3Edit->setValue(og);
+    ui->est_ogShow->setValue(og);
+
+    product->preboil_sg = Utils::estimate_sg(sugarsm, product->boil_size);
+    qDebug() << "  preboil SG" << product->preboil_sg;
+
+    /*
+     * Color of the wort
+     */
+    if (product->color_method == 4) {				// Naudts
+	color = round(((Utils::sg_to_plato(og) / 8.6) * colorn) + (product->boil_time / 60));
+    } else if (product->color_method == 3) {			// Hans Halberstadt
+	double bv = 0.925;					// Beer loss efficiency
+	double sr = 0.95;					// Mash and sparge efficiency
+	color = round((4.46 * bv * sr) / product->batch_size * colorh);
+    } else {
+	double cw = colort / product->batch_size * 8.34436;
+	color = Utils::kw_to_ebc(product->color_method, cw);
+	//qDebug() << "  oud EBC" << color << "new EBC" << Utils::kw_to_newebc(product->color_method, cw) << "SRM" << Utils::kw_to_srm(product->color_method, cw);
+    }
+    qDebug() << "  color" << ui->est_colorEdit->value() << color << product->est_color;
+    product->est_color = color;
+    ui->est_colorEdit->setValue(color);
+    ui->est_colorEdit->setStyleSheet(Utils::ebc_to_style(color));
+    ui->est_color2Edit->setValue(color);
+    ui->est_color2Edit->setStyleSheet(Utils::ebc_to_style(color));
+    ui->est_colorShow->setValue(color);
+
+    /*
+     * We don't have a equipment profile in products,
+     * so we assume a certain guessed mashtun size.
+     */
+    ui->perc_mashShow->setValue(round(product->mashs_kg / (product->boil_size / 3) * 100));
+    ui->perc_sugarsShow->setValue(round(psugar));
+    ui->perc_caraShow->setValue(round(pcara));
+    if (product->mashs_kg > 0) {
+	qDebug() << "  lintner" << lintner << "  mashkg" << product->mashs_kg << "final" << round(lintner / product->mashs_kg);
+	ui->lintnerShow->setValue(round(lintner / product->mashs_kg));
+    } else {
+	qDebug() << "  lintner N/A";
+	ui->lintnerShow->setValue(0);
+    }
+
+    /*
+     * Calculate the apparant attenuation.
+     */
+    double svg = 0;
+    if (product->yeasts.size() > 0) {
+        for (i = 0; i < product->yeasts.size(); i++) {
+	    if (product->yeasts.at(i).y_use == 0) {		// Used in primary
+		if (product->yeasts.at(i).y_attenuation > svg)
+		    svg = product->yeasts.at(i).y_attenuation;	// Take the highest if multiple yeasts.
+	    }
+	    // TODO: brett or others in secondary.
+	}
+	qDebug() << "  SVG" << svg;
+    }
+    if (svg == 0)
+	svg = 77.0;
+    ui->est_svgEdit->setValue(svg);
+
+    double fg;
+    if (product->mashs_kg > 0 && mashinfuse > 0 && mashtime > 0 && mashtemp > 0)
+	fg = Utils::estimate_fg(psugar, pcara, mashinfuse / product->mashs_kg, mashtime, mashtemp, svg, og);
+    else
+	fg = Utils::estimate_fg(psugar, pcara, 0, 0, 0, svg, og);
+    qDebug() << "  FG" << ui->est_fgEdit->value() << fg;
+    product->est_fg = fg;
+    ui->est_fgEdit->setValue(fg);
+    ui->est_fg3Edit->setValue(fg);
+    ui->est_fgShow->setValue(fg);
+
+    double abv = Utils::abvol(og, fg);
+    qDebug() << "  ABV" << ui->est_abvEdit->value() << abv;
+    ui->est_abvEdit->setValue(abv);
+    ui->est_abv2Edit->setValue(abv);
+    ui->est_abvShow->setValue(abv);
+    product->est_abv = abv;
+
+    /*
+     * Calculate kilocalories/liter. Formula from brouwhulp.
+     * Take the alcohol and sugar parts and then combine.
+     */
+    double alc = 1881.22 * fg * (og - fg) / (1.775 - og);
+    double sug = 3550 * fg * (0.1808 * og + 0.8192 * fg - 1.0004);
+    ui->calEdit->setValue(round((alc + sug) / (12 * 0.0295735296)));
+
+    // Bottle priming
+    double priming_total = 0;
+    for (i = 0; i < product->fermentables.size(); i++) {
+	if (product->fermentables.at(i).f_added == 4) {
+	    priming_total += ((product->fermentables.at(i).f_yield / 100) * (1 - product->fermentables.at(i).f_moisture / 100)) *
+		    		product->fermentables.at(i).f_amount;
+	    qDebug() << "  priming" << product->fermentables.at(i).f_amount << "total" << priming_total;
+	}
+    }
+    double grl = priming_total * 1000.0 * (1 / product->batch_size);
+    double volco2 = grl * 0.510;
+    qDebug() << "  priming gr/l" << grl << "volco2" << volco2;
+
+    if (volco2 > 0) {
+	product->est_carb = volco2;
+	ui->est_carbEdit->setValue(product->est_carb);
+	ui->est_carbShow->setValue(product->est_carb);
+    }
+}
+
+
+void EditProduct::calcFermentablesFromOG(double og)
+{
+    qDebug() << "calcFermentablesFromOG" << og;
+
+    int    i;
+    double totmass = 0;
+    double tot = 0;
+    double d, amount;
+    double efficiency = product->efficiency;
+    double sug = Utils::sg_to_plato(og) * product->batch_size * og / 100.0;	// total amount of sugars in kg.
+
+    for (i = 0; i < product->fermentables.size(); i++) {
+	if (product->fermentables.at(i).f_added < 4) {
+	    d = product->fermentables.at(i).f_percentage / 100.0 *
+		    (product->fermentables.at(i).f_yield / 100.0) *
+		    (1 - product->fermentables.at(i).f_moisture / 100.0);
+	    if (product->fermentables.at(i).f_added == 0)  // Mash
+		d = efficiency / 100.0 * d;
+	    tot += d;
+	}
+    }
+    if (tot)
+	totmass = round((sug / tot) * 1000.0) / 1000.0;
+
+    if (totmass) {
+	for (i = 0; i < product->fermentables.size(); i++) {
+	    amount = round(product->fermentables.at(i).f_percentage * 10.0 * totmass) / 1000.0;
+	    product->fermentables[i].f_amount = amount;
+	}
+    }
+}
+
+
+void EditProduct::ferment_perc_mash_valueChanged(int value)
+{
+    if (value < 90)
+	ui->perc_mashShow->setStyleSheet(bar_green);
+    else if (value < 100)
+	ui->perc_mashShow->setStyleSheet(bar_orange);
+    else
+	ui->perc_mashShow->setStyleSheet(bar_red);
+}
+
+
+void EditProduct::ferment_perc_sugars_valueChanged(int value)
+{
+    if (value < 20)
+	ui->perc_sugarsShow->setStyleSheet(bar_green);
+    else
+	ui->perc_sugarsShow->setStyleSheet(bar_red);
+}
+
+
+void EditProduct::ferment_perc_cara_valueChanged(int value)
+{
+    if (value < 25)
+	ui->perc_caraShow->setStyleSheet(bar_green);
+    else
+	ui->perc_caraShow->setStyleSheet(bar_red);
+}
+
+
+void EditProduct::ferment_lintner_valueChanged(int value)
+{
+    if (value < 30)
+	ui->lintnerShow->setStyleSheet(bar_red);
+    else if (value < 40)
+	ui->lintnerShow->setStyleSheet(bar_orange);
+    else
+	ui->lintnerShow->setStyleSheet(bar_green);
+}
+
+
+void EditProduct::addFermentRow_clicked()
+{
+    Fermentables newf;
+
+    qDebug() << "Add fermentable row";
+
+    for (int i = 0; i < product->fermentables.size(); i++) {
+	if (product->fermentables.at(i).f_amount == 0 && product->fermentables.at(i).f_color == 0)
+	    return;	// Add only one at a time.
+    }
+
+    newf.f_name = "Select one";
+    newf.f_origin = "";
+    newf.f_supplier = "";
+    newf.f_amount = 0;
+    newf.f_cost = 0;
+    newf.f_type = 0;
+    newf.f_yield = 0;
+    newf.f_color = 0;
+    newf.f_coarse_fine_diff = 0;
+    newf.f_moisture = 0;
+    newf.f_diastatic_power = 0;
+    newf.f_protein = 0;
+    newf.f_dissolved_protein = 0;
+    newf.f_max_in_batch = 100;
+    newf.f_graintype = 0;
+    newf.f_added = 0;
+    newf.f_recommend_mash = true;
+    newf.f_add_after_boil = false;
+    newf.f_adjust_to_total_100 = false;
+    newf.f_percentage = 0;
+    newf.f_di_ph = 0;
+    newf.f_acid_to_ph_57 = 0;
+
+    product->fermentables.append(newf);
+    emit refreshAll();
+}
+
+
+void EditProduct::deleteFermentRow_clicked()
+{
+    if (product->locked)
+	return;
+
+    QPushButton *pb = qobject_cast<QPushButton *>(QObject::sender());
+    int row = pb->objectName().toInt();
+    qDebug() << "Delete fermentable row" << row << product->fermentables.size();
+
+    if (product->fermentables.size() < 1)
+	return;
+
+    int rc = QMessageBox::warning(this, tr("Delete fermentable"), tr("Delete %1").arg(product->fermentables.at(row).f_name),
+		    QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
+    if (rc == QMessageBox::No)
+	return;
+
+    product->fermentables.removeAt(row);
+
+    /*
+     * Recalculate the percentages on the rows left.
+     */
+    double total = 0;
+    for (int i = 0; i < product->fermentables.size(); i++)
+        if (product->fermentables.at(i).f_added < 4)             // Only before bottle/kegging
+            total += product->fermentables.at(i).f_amount;
+    for (int i = 0; i < product->fermentables.size(); i++)
+        if (product->fermentables.at(i).f_added < 4)
+            product->fermentables[i].f_percentage = product->fermentables.at(i).f_amount / total * 100;
+
+    is_changed();
+    emit refreshAll();
+}
+
+
+void EditProduct::ferment_amount_changed(double val)
+{
+    QTableWidgetItem *item;
+    double	total = 0, perc;
+
+    if (product->fermentables_use100 && product->fermentables.at(product->fermentables_row).f_added < 4)
+	return;
+
+    qDebug() << "ferment_amount_changed()" << product->fermentables_row << val;
+
+    product->fermentables[product->fermentables_row].f_amount = val;
+    item = new QTableWidgetItem(QString("%1 Kg").arg(val, 4, 'f', 3, '0'));
+    item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+    ui->fermentablesTable->setItem(product->fermentables_row, 7, item);
+
+    for (int i = 0; i < product->fermentables.size(); i++)
+	if (product->fermentables.at(i).f_added < 4)		// Only before bottle/kegging
+	    total += product->fermentables.at(i).f_amount;
+    /*
+     * Recalculate the percentages
+     */
+    for (int i = 0; i < product->fermentables.size(); i++) {
+	if (product->fermentables.at(i).f_added < 4) {
+	    perc = product->fermentables.at(i).f_amount / total * 100;
+	    product->fermentables[i].f_percentage = perc;
+	    item = new QTableWidgetItem(QString("%1%").arg(perc, 2, 'f', 1, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->fermentablesTable->setItem(i, 8, item);
+	    if (i == product->fermentables_row)
+	    	this->pctEdit->setValue(perc);
+	}
+    }
+    is_changed();
+}
+
+
+void EditProduct::ferment_pct_changed(double val)
+{
+    QTableWidgetItem *item;
+    double	total = 0, row100 = -1;
+
+    if (! product->fermentables_use100)
+        return;
+
+    qDebug() << "ferment_pct_changed()" << product->fermentables_row << val;
+    /*
+     * Since we have arrived here, adjust_to_100 is active and
+     * this is not the entry to be adjusted to 100.
+     */
+    for (int i = 0; i < product->fermentables.size(); i++) {
+        if (product->fermentables.at(i).f_added < 4)             // Only before bottle/kegging
+            total += product->fermentables.at(i).f_amount;
+	if (product->fermentables.at(i).f_adjust_to_total_100)
+	    row100 = i;
+    }
+    double oldperc = product->fermentables.at(product->fermentables_row).f_percentage;
+    double diffp = val - oldperc;
+    double diffw = (diffp / 100) * total;
+    qDebug() << "row100" << row100 << "total" << total << "diff kg" << diffw << "diff %" << diffp;
+
+    product->fermentables[product->fermentables_row].f_percentage += diffp;
+    product->fermentables[product->fermentables_row].f_amount += diffw;
+    product->fermentables[row100].f_percentage -= diffp;
+    product->fermentables[row100].f_amount -= diffw;
+
+    item = new QTableWidgetItem(QString("%1 Kg").arg(product->fermentables[product->fermentables_row].f_amount, 4, 'f', 3, '0'));
+    item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+    ui->fermentablesTable->setItem(product->fermentables_row, 7, item);
+    this->famountEdit->setValue(product->fermentables[product->fermentables_row].f_amount);
+
+    item = new QTableWidgetItem(QString("%1%").arg(product->fermentables[product->fermentables_row].f_percentage, 2, 'f', 1, '0'));
+    item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+    ui->fermentablesTable->setItem(product->fermentables_row, 8, item);
+
+    item = new QTableWidgetItem(QString("%1 Kg").arg(product->fermentables[row100].f_amount, 4, 'f', 3, '0'));
+    item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+    ui->fermentablesTable->setItem(row100, 7, item);
+
+    item = new QTableWidgetItem(QString("%1%").arg(product->fermentables[row100].f_percentage, 2, 'f', 1, '0'));
+    item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+    ui->fermentablesTable->setItem(row100, 8, item);
+
+    is_changed();
+}
+
+
+void EditProduct::ferment_to100_changed(bool val)
+{
+    qDebug() << "ferment_to100_changed()" << product->fermentables_row << val << product->fermentables_use100;
+
+    if (product->fermentables.at(product->fermentables_row).f_added >= 4) {
+	const QSignalBlocker blocker1(to100Edit);
+	product->fermentables[product->fermentables_row].f_adjust_to_total_100 = false;
+	to100Edit->setChecked(false);
+	return;
+    }
+
+    /*
+     * Three scenario's.
+     * 1. There is no fermentable selected yet, just mark it.
+     * 2. There is current one is selected, deselect it.
+     * 3. There is another one selected, deselect and select this one.
+     */
+    if (! product->fermentables_use100 && val) {
+	/* Scenario 1. */
+        product->fermentables_use100 = true;
+        product->fermentables[product->fermentables_row].f_adjust_to_total_100 = true;
+	pctEdit->setReadOnly(false);
+	famountEdit->setReadOnly(true);
+    } else if (product->fermentables_use100 && product->fermentables[product->fermentables_row].f_adjust_to_total_100 && ! val) {
+	/* Scenario 2. */
+	product->fermentables[product->fermentables_row].f_adjust_to_total_100 = false;
+	product->fermentables_use100 = false;
+	pctEdit->setReadOnly(true);
+	famountEdit->setReadOnly(false);
+    } else if (product->fermentables_use100 && ! product->fermentables[product->fermentables_row].f_adjust_to_total_100 && val) {
+	/* Scenario 3. */
+	for (int i = 0; i < product->fermentables.size(); i++) {
+	    product->fermentables[i].f_adjust_to_total_100 = false;
+	}
+	product->fermentables[product->fermentables_row].f_adjust_to_total_100 = true;
+    } else {
+	qDebug() << "bug";
+	return;
+    }
+
+    for (int i = 0; i < product->fermentables.size(); i++) {
+	to100Fermentables(i);
+    }
+    is_changed();
+}
+
+
+void EditProduct::ferment_select_changed(int val)
+{
+    QSqlQuery query;
+    bool instock = finstockEdit->isChecked();
+    QString w;
+    QTableWidgetItem *item;
+
+    if (val < 1)
+	return;
+
+    qDebug() << "ferment_select_changed()" << product->fermentables_row << val << instock;
+
+    /*
+     * Search the fermentable pointed by the index and instock flag.
+     */
+    QString sql = "SELECT name,origin,supplier,cost,type,yield,color,coarse_fine_diff,moisture,diastatic_power,protein,dissolved_protein,max_in_batch,"
+	  	  "graintype,recommend_mash,add_after_boil,di_ph,acid_to_ph_57 FROM inventory_fermentables ";
+    if (instock)
+	sql.append("WHERE inventory > 0 ");
+    sql.append("ORDER BY supplier,name");
+    query.prepare(sql);
+    query.exec();
+    query.first();
+    for (int i = 0; i < (val - 1); i++) {
+	query.next();
+    }
+    qDebug() << "found" << query.value(2).toString() << query.value(0).toString();
+
+    /*
+     * Replace the fermentable record contents
+     */
+    product->fermentables[product->fermentables_row].f_name = query.value(0).toString();
+    product->fermentables[product->fermentables_row].f_origin = query.value(1).toString();
+    product->fermentables[product->fermentables_row].f_supplier = query.value(2).toString();
+    product->fermentables[product->fermentables_row].f_cost = query.value(3).toDouble();
+    product->fermentables[product->fermentables_row].f_type = query.value(4).toInt();
+    product->fermentables[product->fermentables_row].f_yield = query.value(5).toDouble();
+    product->fermentables[product->fermentables_row].f_color = query.value(6).toDouble();
+    product->fermentables[product->fermentables_row].f_coarse_fine_diff = query.value(7).toDouble();
+    product->fermentables[product->fermentables_row].f_moisture = query.value(8).toDouble();
+    product->fermentables[product->fermentables_row].f_diastatic_power = query.value(9).toDouble();
+    product->fermentables[product->fermentables_row].f_protein = query.value(10).toDouble();
+    product->fermentables[product->fermentables_row].f_dissolved_protein = query.value(11).toDouble();
+    product->fermentables[product->fermentables_row].f_max_in_batch = query.value(12).toDouble();
+    product->fermentables[product->fermentables_row].f_graintype = query.value(13).toInt();
+    product->fermentables[product->fermentables_row].f_recommend_mash = query.value(14).toInt() ? true:false;
+    product->fermentables[product->fermentables_row].f_add_after_boil = query.value(15).toInt() ? true:false;
+    product->fermentables[product->fermentables_row].f_di_ph = query.value(16).toDouble();
+    product->fermentables[product->fermentables_row].f_acid_to_ph_57 = query.value(17).toDouble();
+
+    /*
+     * Update the visible fields
+     */
+    fnameEdit->setText(product->fermentables.at(product->fermentables_row).f_name);
+    fsupplierEdit->setText(product->fermentables.at(product->fermentables_row).f_supplier);
+    fmaxEdit->setValue(product->fermentables.at(product->fermentables_row).f_max_in_batch);
+
+    ui->fermentablesTable->setItem(product->fermentables_row, 0, new QTableWidgetItem(product->fermentables.at(product->fermentables_row).f_supplier));
+    ui->fermentablesTable->setItem(product->fermentables_row, 1, new QTableWidgetItem(product->fermentables.at(product->fermentables_row).f_name));
+
+    w = QString("%1").arg(product->fermentables.at(product->fermentables_row).f_color, 1, 'f', 0, '0');
+    item = new QTableWidgetItem(w);
+    item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+    ui->fermentablesTable->setItem(product->fermentables_row, 2, item);
+
+    item = new QTableWidgetItem(fermentable_types[product->fermentables.at(product->fermentables_row).f_type]);
+    item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter);
+    ui->fermentablesTable->setItem(product->fermentables_row, 3, item);
+
+    item = new QTableWidgetItem(fermentable_graintypes[product->fermentables.at(product->fermentables_row).f_graintype]);
+    item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter);
+    ui->fermentablesTable->setItem(product->fermentables_row, 4, item);
+
+    item = new QTableWidgetItem(QString("%1%").arg(product->fermentables.at(product->fermentables_row).f_yield, 2, 'f', 1, '0'));
+    item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+    ui->fermentablesTable->setItem(product->fermentables_row, 6, item);
+
+    calcFermentables();
+    is_changed();
+}
+
+
+void EditProduct::ferment_instock_changed(bool val)
+{
+    QSqlQuery query;
+
+    qDebug() << "ferment_instock_changed()" << product->fermentables_row << val;
+
+    this->fselectEdit->setCurrentIndex(-1);
+    this->fselectEdit->clear();
+    QString sql = "SELECT supplier,name,color,inventory FROM inventory_fermentables ";
+    if (val)
+	sql.append("WHERE inventory > 0 ");
+    sql.append("ORDER BY supplier,name");
+    query.prepare(sql);
+    query.exec();
+    query.first();
+    this->fselectEdit->addItem("");	// Start with empty value
+    for (int i = 0; i < query.size(); i++) {
+        this->fselectEdit->addItem(query.value(0).toString()+" - "+query.value(1).toString()+" ("+query.value(2).toString()+" EBC) "+
+			QString("%1 kg").arg(query.value(3).toDouble(), 4, 'f', 3, '0'));
+        query.next();
+    }
+}
+
+
+void EditProduct::ferment_added_changed(int val)
+{
+    qDebug() << "ferment_added_changed()" << product->fermentables_row << val;
+
+    product->fermentables[product->fermentables_row].f_added = val;
+    QTableWidgetItem *item = new QTableWidgetItem(fermentable_added[val]);
+    item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter);
+    ui->fermentablesTable->setItem(product->fermentables_row, 5, item);
+
+    famountEdit->setReadOnly(product->fermentables_use100 && product->fermentables.at(product->fermentables_row).f_added < 4);
+    pctEdit->setReadOnly(! (product->fermentables_use100 && product->fermentables.at(product->fermentables_row).f_added < 4));
+
+    double total = 0;
+    for (int i = 0; i < product->fermentables.size(); i++)
+        if (product->fermentables.at(i).f_added < 4)             // Only before bottle/kegging
+            total += product->fermentables.at(i).f_amount;
+    for (int i = 0; i < product->fermentables.size(); i++)
+        if (product->fermentables.at(i).f_added < 4)
+            product->fermentables[i].f_percentage = product->fermentables.at(i).f_amount / total * 100;
+
+    is_changed();
+    emit refreshAll();
+}
+
+
+void EditProduct::editFermentRow_clicked()
+{
+    QSqlQuery query;
+
+    if (product->locked)
+	return;
+
+    QPushButton *pb = qobject_cast<QPushButton *>(QObject::sender());
+    product->fermentables_row = pb->objectName().toInt();
+    qDebug() << "Edit fermentable row" << product->fermentables_row;
+    Fermentables backup = product->fermentables.at(product->fermentables_row);
+
+    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(525, 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);
+
+    fselectEdit = new QComboBox(dialog);
+    fselectEdit->setObjectName(QString::fromUtf8("fselectEdit"));
+    fselectEdit->setGeometry(QRect(160, 70, 371, 23));
+
+    fnameEdit = new QLineEdit(dialog);
+    fnameEdit->setObjectName(QString::fromUtf8("fnameEdit"));
+    fnameEdit->setText(product->fermentables.at(product->fermentables_row).f_name);
+    fnameEdit->setGeometry(QRect(160, 10, 511, 23));
+    fnameEdit->setReadOnly(true);
+    fsupplierEdit = new QLineEdit(dialog);
+    fsupplierEdit->setObjectName(QString::fromUtf8("fsupplierEdit"));
+    fsupplierEdit->setText(product->fermentables.at(product->fermentables_row).f_supplier);
+    fsupplierEdit->setGeometry(QRect(160, 40, 511, 23));
+    fsupplierEdit->setReadOnly(true);
+    famountEdit = new QDoubleSpinBox(dialog);
+    famountEdit->setObjectName(QString::fromUtf8("famountEdit"));
+    famountEdit->setGeometry(QRect(160, 100, 121, 24));
+    famountEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
+    famountEdit->setAccelerated(true);
+    famountEdit->setDecimals(3);
+    famountEdit->setReadOnly(product->fermentables_use100 && product->fermentables.at(product->fermentables_row).f_added < 4);
+    famountEdit->setMaximum(100000.0);
+    famountEdit->setSingleStep(0.0010);
+    famountEdit->setValue(product->fermentables.at(product->fermentables_row).f_amount);
+
+    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 (product->fermentables_use100 && product->fermentables.at(product->fermentables_row).f_added < 4) {
+    	if (product->fermentables.at(product->fermentables_row).f_adjust_to_total_100)
+	    pctEdit->setReadOnly(true);
+    	else
+    	    pctEdit->setReadOnly(false);
+    } else {
+	pctEdit->setReadOnly(true);
+    }
+    pctEdit->setMaximum(100.0);
+    pctEdit->setSingleStep(0.1);
+    pctEdit->setValue(product->fermentables.at(product->fermentables_row).f_percentage);
+
+    faddedEdit = new QComboBox(dialog);
+    faddedEdit->setObjectName(QString::fromUtf8("faddedEdit"));
+    faddedEdit->setGeometry(QRect(160, 190, 161, 23));
+    faddedEdit->addItem(tr("Mash"));
+    faddedEdit->addItem(tr("Boil"));
+    faddedEdit->addItem(tr("Fermentation"));
+    faddedEdit->addItem(tr("Lagering"));
+    faddedEdit->addItem(tr("Bottle"));
+    faddedEdit->addItem(tr("Kegs"));
+    faddedEdit->setCurrentIndex(product->fermentables.at(product->fermentables_row).f_added);
+
+    to100Edit = new QCheckBox(dialog);
+    to100Edit->setObjectName(QString::fromUtf8("to100Edit"));
+    to100Edit->setGeometry(QRect(160, 160, 85, 21));
+    to100Edit->setChecked(product->fermentables.at(product->fermentables_row).f_adjust_to_total_100);
+
+    finstockEdit = new QCheckBox(dialog);
+    finstockEdit->setObjectName(QString::fromUtf8("instockEdit"));
+    finstockEdit->setGeometry(QRect(655, 70, 85, 21));
+    finstockEdit->setChecked(true);
+
+    fmaxEdit = new QDoubleSpinBox(dialog);
+    fmaxEdit->setObjectName(QString::fromUtf8("fmaxEdit"));
+    fmaxEdit->setGeometry(QRect(550, 130, 121, 24));
+    fmaxEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
+    fmaxEdit->setReadOnly(true);
+    fmaxEdit->setButtonSymbols(QAbstractSpinBox::NoButtons);
+    fmaxEdit->setDecimals(1);
+    fmaxEdit->setValue(product->fermentables.at(product->fermentables_row).f_max_in_batch);
+
+    ferment_instock_changed(true);
+
+    connect(fselectEdit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &EditProduct::ferment_select_changed);
+    connect(famountEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::ferment_amount_changed);
+    connect(pctEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::ferment_pct_changed);
+    connect(faddedEdit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &EditProduct::ferment_added_changed);
+    connect(to100Edit, &QCheckBox::stateChanged, this, &EditProduct::ferment_to100_changed);
+    connect(finstockEdit, &QCheckBox::stateChanged, this, &EditProduct::ferment_instock_changed);
+    connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject()));
+    connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept()));
+
+    dialog->setModal(true);
+    dialog->exec();
+    if (dialog->result() == QDialog::Rejected) {
+	qDebug() << "reject and rollback";
+	product->fermentables[product->fermentables_row] = backup;
+	/*
+	 * Recalculate the percentages
+	 */
+	double total = 0;
+	for (int i = 0; i < product->fermentables.size(); i++)
+	    if (product->fermentables.at(i).f_added < 4)             // Only before bottle/kegging
+		total += product->fermentables.at(i).f_amount;
+	product->fermentables_use100 = false;
+	for (int i = 0; i < product->fermentables.size(); i++) {
+	    if (product->fermentables.at(i).f_adjust_to_total_100)
+	    	product->fermentables_use100 = true;
+	    if (product->fermentables.at(i).f_added < 4) {
+		product->fermentables[i].f_percentage = product->fermentables.at(i).f_amount / total * 100;
+	    }
+	}
+    }
+
+    disconnect(fselectEdit, nullptr, nullptr, nullptr);
+    disconnect(famountEdit, nullptr, nullptr, nullptr);
+    disconnect(pctEdit, nullptr, nullptr, nullptr);
+    disconnect(faddedEdit, nullptr, nullptr, nullptr);
+    disconnect(to100Edit, nullptr, nullptr, nullptr);
+    disconnect(finstockEdit, nullptr, nullptr, nullptr);
+    disconnect(buttonBox, nullptr, nullptr, nullptr);
+
+    emit refreshAll();
+}
+
+

mercurial