src/EditProductTab8.cpp

changeset 175
f1ed3a2a94e9
child 185
405bb68c1ea4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/EditProductTab8.cpp	Thu Apr 28 22:49:13 2022 +0200
@@ -0,0 +1,797 @@
+/**
+ * EditProduct.cpp is part of bmsapp.
+ *
+ * tab 8, water.
+ *
+ * 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/>.
+ */
+
+
+void EditProduct::refreshWaters()
+{
+
+    // calc_acid
+    ui->mw_phEdit->setValue(product->mash_ph);
+    // mash_name
+    //ui->mw_acidpercEdit->setValue(product->
+
+}
+
+
+/*
+ * Z alkalinity is the amount of acid (in mEq/l) needed to bring water to the target pH (Z pH)
+ */
+double EditProduct::ZAlkalinity(double pHZ)
+{
+    double C43 = Utils::Charge(4.3);
+    double Cw = Utils::Charge(product->wg_ph);
+    double Cz = Utils::Charge(pHZ);
+    double DeltaCNaught = -C43 + Cw;
+    double CT = product->wg_total_alkalinity / 50 / DeltaCNaught;
+    double DeltaCZ = -Cz + Cw;
+    return CT * DeltaCZ;
+}
+
+
+/*
+ * Z Residual alkalinity is the amount of acid (in mEq/l) needed
+ * to bring the water in the mash to the target pH (Z pH).
+ */
+double EditProduct::ZRA(double pHZ)
+{
+    double Calc = product->wg_calcium / (MMCa / 2);
+    double Magn = product->wg_magnesium / (MMMg / 2);
+    double Z = ZAlkalinity(pHZ);
+    return Z - (Calc / 3.5 + Magn / 7);
+}
+
+
+double EditProduct::BufferCapacity(Fermentables F)
+{
+    double C1 = 0;
+
+    if ((F.f_di_ph != 5.7) && ((F.f_acid_to_ph_57 < - 0.1) || (F.f_acid_to_ph_57 > 0.1))) {
+	C1 = F.f_acid_to_ph_57 / (F.f_di_ph - 5.7);
+     } else {
+	/*
+	 * If the acid_to_ph_5.7 is unknown from the maltster, guess the required acid.
+	 */
+	switch (F.f_graintype) {
+	   case 0:					// Base, Special, Kilned
+	   case 3:
+	   case 5:	C1 = 0.014 * F.f_color - 34.192;
+			break;
+	   case 2:	C1 = -0.0597 * F.f_color - 32.457;	// Crystal
+			break;
+	   case 1:	C1 = 0.0107 * F.f_color - 54.768;	// Roast
+			break;
+	   case 4:	C1 = -149;                      // Sour malt
+			break;
+	}
+    }
+    return C1;
+}
+
+
+double EditProduct::AcidRequired(double ZpH, Fermentables F)
+{
+    double C1 = BufferCapacity(F);
+    double x = F.f_di_ph;
+    return C1 * (ZpH - x);
+}
+
+
+double EditProduct::ProtonDeficit(double pHZ)
+{
+    double C1, x;
+    int i, error_count = 0;
+    double Result = ZRA(pHZ) * product->wg_amount;
+    Fermentables F;
+
+    /*
+     * proton deficit for the grist
+     */
+    if (product->fermentables.size()) {
+	for (i = 0; i < product->fermentables.size(); i++) {
+	    F = product->fermentables.at(i);
+	    if (F.f_added == 0 && F.f_graintype != 6) { // Added == Mash && graintype != No Malt
+		x = AcidRequired(pHZ, F) * F.f_amount;
+		Result += x;
+	    }
+	}
+    } else {
+	error_count++;
+	if (error_count < 5)
+	   qDebug() << "  ProtonDeficit" <<  pHZ << "invalid grist, return" << Result;
+    }
+    return Result;
+}
+
+
+double EditProduct::MashpH()
+{
+    int n = 0;
+    double pH = 5.4;
+    double deltapH = 0.001;
+    double deltapd = 0.1;
+    double pd = ProtonDeficit(pH);
+
+    while (((pd < -deltapd) || (pd > deltapd)) && (n < 2000)) {
+	n++;
+	if (pd < -deltapd)
+	    pH -= deltapH;
+	else if (pd > deltapd)
+	    pH += deltapH;
+	pd = ProtonDeficit(pH);
+    }
+    pH = round(pH * 1000000) / 1000000.0;
+    qDebug() << "  MashpH() n:" << n << "pH:" << pH;
+    return pH;
+}
+
+
+void EditProduct::calcWater()
+{
+    double liters = 0;
+    double calcium = 0;
+    double magnesium = 0;
+    double sodium = 0;
+    double total_alkalinity = 0;
+    double bicarbonate = 0;
+    double chloride = 0;
+    double sulfate = 0;
+    double ph = 0;
+    double TpH = 0;
+    double frac, RA;
+    double protonDeficit = 0;
+    double Acid = 0, Acidmg = 0;
+    int AT;
+
+    qDebug() << "calcWater()";
+
+    /*
+     * If there is a dilute water source, mix the waters.
+     */
+    if (product->w2_name != "") {
+	liters = product->w1_amount + product->w2_amount;
+	calcium = Utils::mix(product->w1_amount, product->w2_amount, product->w1_calcium, product->w2_calcium);
+	magnesium = Utils::mix(product->w1_amount, product->w2_amount, product->w1_magnesium, product->w2_magnesium);
+	sodium = Utils::mix(product->w1_amount, product->w2_amount, product->w1_sodium, product->w2_sodium);
+	chloride = Utils::mix(product->w1_amount, product->w2_amount, product->w1_chloride, product->w2_chloride);
+	sulfate = Utils::mix(product->w1_amount, product->w2_amount, product->w1_sulfate, product->w2_sulfate);
+	total_alkalinity = Utils::mix(product->w1_amount, product->w2_amount, product->w1_total_alkalinity, product->w2_total_alkalinity);
+	ph = -log10(((pow(10, -product->w1_ph) * product->w1_amount) + (pow(10, -product->w2_ph) * product->w2_amount)) / liters);
+    } else {
+	liters = product->w1_amount;
+	calcium = product->w1_calcium;
+	magnesium = product->w1_magnesium;
+	sodium = product->w1_sodium;
+	chloride = product->w1_chloride;
+	sulfate = product->w1_sulfate;
+	total_alkalinity = product->w1_total_alkalinity;
+	ph = product->w1_ph;
+    }
+
+    product->wg_amount = liters;
+    product->wg_calcium = round(calcium * 10.0) / 10.0;
+    product->wg_magnesium = round(magnesium * 10.0) / 10.0;
+    product->wg_sodium = round(sodium * 10.0) / 10.0;
+    product->wg_chloride = round(chloride * 10.0) / 10.0;
+    product->wg_sulfate = round(sulfate * 10.0) / 10.0;
+    product->wg_total_alkalinity = round(total_alkalinity * 10.0) / 10.0;
+    product->wg_ph = ph;
+
+    ui->wg_volEdit->setValue(liters);
+    ui->wg_caEdit->setValue(calcium);
+    ui->wg_mgEdit->setValue(magnesium);
+    ui->wg_hco3Edit->setValue(total_alkalinity * 1.22);
+    ui->wg_caco3Edit->setValue(total_alkalinity);
+    ui->wg_naEdit->setValue(sodium);
+    ui->wg_clEdit->setValue(chloride);
+    ui->wg_so4Edit->setValue(sulfate);
+    ui->wg_phEdit->setValue(ph);
+    bicarbonate = total_alkalinity * 1.22;
+
+    /* Save mixed water ions for later */
+    double wg_calcium = calcium;
+    double wg_sodium = sodium;
+    double wg_total_alkalinity = total_alkalinity;
+    double wg_chloride = chloride;
+    double wg_sulfate = sulfate;
+    double wg_bicarbonate = bicarbonate;
+
+    double mash_ph = MashpH();
+    qDebug() << "  Distilled water mash pH:" << mash_ph;
+
+    /* Calculate Salt additions */
+   if (liters > 0) {
+	calcium += ( ui->bs_cacl2Edit->value() * MMCa / MMCaCl2 * 1000 + ui->bs_caso4Edit->value() * MMCa / MMCaSO4 * 1000 +
+		     ui->bs_caco3Edit->value() * MMCa / MMCaCO3 * 1000) / liters;
+	magnesium += (ui->bs_mgso4Edit->value() * MMMg / MMMgSO4 * 1000 + ui->bs_mgcl2Edit->value() * MMMg / MMMgCl2 * 1000) / liters;
+	sodium += (ui->bs_naclEdit->value() * MMNa / MMNaCl * 1000 + ui->bs_nahco3Edit->value() * MMNa / MMNaHCO3 * 1000) / liters;
+	sulfate += (ui->bs_caso4Edit->value() * MMSO4 / MMCaSO4 * 1000 + ui->bs_mgso4Edit->value() * MMSO4 / MMMgSO4 * 1000) / liters;
+	chloride += (2 * ui->bs_cacl2Edit->value() * MMCl / MMCaCl2 * 1000 + ui->bs_naclEdit->value() * MMCl / MMNaCl * 1000 +
+		     ui->bs_mgcl2Edit->value() * MMCl / MMMgCl2 * 1000) / liters;
+	bicarbonate += (ui->bs_nahco3Edit->value() * MMHCO3 / MMNaHCO3 * 1000 + ui->bs_caco3Edit->value() / 3 * MMHCO3 / MMCaCO3 * 1000) / liters;
+    }
+
+    const QSignalBlocker blocker1(ui->mw_acidPick);
+    const QSignalBlocker blocker2(ui->mw_acidpercEdit);
+    const QSignalBlocker blocker3(ui->mw_acidvolEdit);
+    const QSignalBlocker blocker4(ui->wb_phEdit);
+    const QSignalBlocker blocker5(ui->mw_phEdit);
+
+    if (product->wa_acid_name < 0 || product->wa_acid_name >= my_acids.size()) {
+	product->wa_acid_name = 0;
+	product->wa_acid_perc = my_acids.at(0).AcidPrc;
+	ui->mw_acidPick->setCurrentIndex(0);
+	ui->mw_acidpercEdit->setValue(my_acids.at(0).AcidPrc);
+    }
+    AT = product->wa_acid_name;
+
+    /*
+     * Note that the next calculations do not correct the pH change by the added salts.
+     * This pH change is at most 0.1 pH and is a minor difference in Acid amount.
+     */
+    if (product->calc_acid) {
+	/*
+	 * Auto calculate the needed acid.
+	 */
+	TpH = product->mash_ph;
+	protonDeficit = ProtonDeficit(TpH);
+	qDebug() << "  calc_acid tgt:" << TpH << "protonDeficit:" << protonDeficit;
+	if (protonDeficit > 0) {
+	    frac = Utils::CalcFrac(TpH, my_acids[AT].pK1, my_acids[AT].pK2, my_acids[AT].pK3);
+	    Acid = protonDeficit / frac;
+	    Acid *= my_acids[AT].MolWt;	// mg.
+	    Acidmg = Acid;
+	    Acid = Acid / my_acids[AT].AcidSG;
+	    Acid = round((Acid / (product->wa_acid_perc / 100.0)) * 100.0) / 100.0;
+	    qDebug() << "  Mash auto Acid final ml:" << Acid;
+
+	    QString w = my_acids[AT].name_en + ' ' + my_acids[AT].name_nl;
+	    brewing_salt_sub(w, Acid);
+	    ui->mw_acidvolEdit->setValue(Acid);
+
+	    bicarbonate = bicarbonate - protonDeficit * frac / liters;
+	    total_alkalinity = bicarbonate * 50 / 61;
+	}
+	ph = TpH;
+	ui->wb_phEdit->setValue(ph);
+	product->mash_ph = ph;
+    } else { // Manual
+	/*
+	 * Manual adjust acid, calculate resulting pH.
+	 */
+	double pHa = ph;	// Mixed water pH.
+	// Then calculate the new pH with added acids and malts
+	qDebug() << "  Mash pH:" << pHa;
+	Acid = my_acids[AT].AcidSG * (product->wa_acid_perc / 100.0);	// ml
+	Acid *= ui->mw_acidvolEdit->value();
+	Acid /= my_acids[AT].MolWt;	// mg;
+	Acidmg = Acid;
+
+	//find the pH where the protondeficit = protondeficit by the acid
+	frac = Utils::CalcFrac(pHa, my_acids[AT].pK1, my_acids[AT].pK2, my_acids[AT].pK3);
+	protonDeficit = Acid * frac;
+	//qDebug() << "  protonDeficit Acid:" << protonDeficit << "frac:" << frac << "pH:" << pHa;
+
+	double deltapH = 0.001;
+   	double deltapd = 0.1;
+   	double pd = round(ProtonDeficit(pHa) * 1000000.0) / 1000000.0;
+	int n = 0;
+	while (((pd < (protonDeficit - deltapd)) || (pd > (protonDeficit + deltapd))) && (n < 4000)) {
+	    n++;
+	    if (pd < (protonDeficit - deltapd))
+		pHa -= deltapH;
+	    else if (pd > (protonDeficit + deltapd))
+		pHa += deltapH;
+	    frac = Utils::CalcFrac(pHa, my_acids[AT].pK1, my_acids[AT].pK2, my_acids[AT].pK3);
+	    protonDeficit = Acid * frac;
+	    pd = ProtonDeficit(pHa);
+	}
+	//qDebug() << "  n:" << n << "pd:" << pd << "protonDeficit:" << protonDeficit << "frac:" << frac << "pHa:" << pHa;
+
+	bicarbonate = wg_bicarbonate - protonDeficit * frac / liters;
+	total_alkalinity = bicarbonate * 50 / 61;
+	ph = pHa;
+	ui->wb_phEdit->setValue(ph);
+	ui->mw_phEdit->setValue(ph);
+        product->mash_ph = ph;
+    }
+
+    if ((AT == 3) && (liters > 0)) {        // Sulfuctic / Zwavelzuur
+	RA = ui->bs_caso4Edit->value() * MMSO4 / MMCaSO4 + ui->bs_mgso4Edit->value() * MMSO4 / MMMgSO4 + Acidmg / 1000 * MMSO4 / (MMSO4 + 2);
+	RA = 1000 * RA / liters;
+	sulfate = wg_sulfate + RA;      // Not add to sulfate??
+    } else if ((AT == 1) && (liters > 0)) { // Hydrochloric, Zoutzuur
+	RA = ui->bs_cacl2Edit->value() * MMCl / MMCaCl2 + ui->bs_naclEdit->value() * MMCl / MMNaCl + Acidmg / 1000 * MMCl / (MMCl + 1);
+	RA = 1000 * RA / liters;
+	chloride = wg_chloride + RA;
+    }
+
+    double BUGU = GetBUGU();
+    ui->buguEdit->setValue(BUGU);
+    if (BUGU < 0.32)
+	ui->buguResult->setText(tr("Very malty and sweet"));
+    else if (BUGU < 0.43)
+	ui->buguResult->setText(tr("Malty, sweet"));
+    else if (BUGU < 0.52)
+	ui->buguResult->setText(tr("Balanced"));
+    else if (BUGU < 0.63)
+	ui->buguResult->setText(tr("Hoppy, bitter"));
+    else
+	ui->buguResult->setText(tr("Very hoppy, very bitter"));
+
+    double OptSO4Clratio = GetOptSO4Clratio();
+    ui->so4clEdit->setValue(OptSO4Clratio);
+    ui->cur_so4clResult->setRange(0.7 * OptSO4Clratio, 1.3 * OptSO4Clratio);
+    if (OptSO4Clratio < 0.4)
+	ui->so4clResult->setText(tr("Too malty"));
+    else if (OptSO4Clratio < 0.6)
+	ui->so4clResult->setText(tr("Very malty"));
+    else if (OptSO4Clratio < 0.8)
+	ui->so4clResult->setText(tr("Malty"));
+    else if (OptSO4Clratio < 1.5)
+	ui->so4clResult->setText(tr("Balanced"));
+    else if (OptSO4Clratio < 2.0)
+	ui->so4clResult->setText(tr("Little bitter"));
+    else if (OptSO4Clratio < 4.0)
+	ui->so4clResult->setText(tr("Bitter"));
+    else if (OptSO4Clratio < 9.0)
+	ui->so4clResult->setText(tr("Very bitter"));
+    else
+	ui->so4clResult->setText(tr("Too bitter"));
+    if (chloride > 0)
+	RA = sulfate / chloride;
+    else
+	RA = 10;
+    ui->cur_so4clEdit->setValue(RA);
+    ui->cur_so4clResult->setValue(RA);
+
+    product->wb_calcium = calcium;
+    product->wb_magnesium = magnesium;
+    product->wb_total_alkalinity = total_alkalinity;
+    product->wb_sodium = sodium;
+    product->wb_chloride = chloride;
+    product->wb_sulfate = sulfate;
+    product->wb_ph = ph;
+
+    ui->wb_caEdit->setValue(calcium);
+    ui->wb_mgEdit->setValue(magnesium);
+    ui->wb_hco3Edit->setValue(bicarbonate);
+    ui->wb_caco3Edit->setValue(total_alkalinity);
+    ui->wb_naEdit->setValue(sodium);
+    ui->wb_clEdit->setValue(chloride);
+    ui->wb_so4Edit->setValue(sulfate);
+
+    ui->wb_caEdit->setStyleSheet((calcium < 40 || calcium > 150) ? "background-color: red":"background-color: green");
+    ui->wb_mgEdit->setStyleSheet((magnesium < 5 || magnesium > 40) ? "background-color: red":"background-color: green");
+    ui->wb_naEdit->setStyleSheet((sodium > 150) ? "background-color: red":"background-color: green");
+    /*
+     * Both chloride and sulfate should be above 50 according to
+     * John Palmer. So the Cl/SO4 ratio calculation will work.
+     */
+    ui->wb_clEdit->setStyleSheet((chloride <= 50 || chloride > 150) ? "background-color: red":"background-color: green");
+    ui->wb_so4Edit->setStyleSheet((sulfate <= 50 || sulfate > 400) ? "background-color: red":"background-color: green");
+    /*
+     * (cloride + sulfate) > 500 is too high
+     */
+    if ((chloride + sulfate) > 500) {
+	ui->wb_clEdit->setStyleSheet("background-color: red");
+	ui->wb_so4Edit->setStyleSheet("background-color: red");
+    }
+    ui->wb_phEdit->setStyleSheet((ph < 5.2 || ph > 5.6) ? "background-color: red":"background-color: green");
+    ui->wb_hco3Edit->setStyleSheet((bicarbonate > 250) ? "background-color: red":"background-color: green");
+    ui->wb_caco3Edit->setStyleSheet((bicarbonate > 250) ? "background-color: red":"background-color: green");
+
+    calcSparge();
+}
+
+
+/*
+ * Based on the work of ajDeLange.
+ */
+void EditProduct::calcSparge()
+{
+    double TargetpH = product->sparge_ph;
+    double Source_pH = product->w1_ph;
+    double Source_alkalinity = product->w1_total_alkalinity;
+
+    qDebug() << "calcSparge()";
+
+    const QSignalBlocker blocker1(ui->sp_sourceEdit);
+
+    // Select watersource or fallback to the first source.
+    if (product->sparge_source == 1) { // Source 2
+	if (product->w2_ph > 0.0 && product->w2_amount > 0) {
+	    Source_pH = product->w2_ph;
+	    Source_alkalinity = product->w2_total_alkalinity;
+	} else {
+	    product->sparge_source = 0; // Source 1
+	    ui->sp_sourceEdit->setCurrentIndex(0);
+	}
+    } else if (product->sparge_source == 2) { // Mixed
+	if (product->w2_ph > 0.0 && product->w2_amount > 0) {
+	    Source_pH = product->wg_ph;
+	    Source_alkalinity = product->wg_total_alkalinity;
+	} else {
+	    product->sparge_source = 0; // Source 1
+            ui->sp_sourceEdit->setCurrentIndex(0);
+	}
+    }
+
+    // Step 1: Compute the mole fractions of carbonic (f1o), bicarbonate (f2o) and carbonate(f3o) at the water pH
+    double r1 = pow(10, Source_pH - 6.35);
+    double r2 = pow(10, Source_pH - 10.33);
+    double d = 1 + r1 + r1 * r2;
+    double f1 = 1 / d;
+    double f3 = r1 * r2 / d;
+
+    // Step 2. Compute the mole fractions at pH = 4.3 (the pH which defines alkalinity)
+    double r143 = pow(10, 4.3 - 6.35);
+    double r243 = pow(10, 4.3 - 10.33);
+    double d43 = 1 + r143 + r143 * r243;
+    double f143 = 1 / d43;
+    double f343 = r143 * r243 / d43;
+
+    // Step 4. Solve
+    //double Ct = (Source_alkalinity - 1000 * (pow(10, -4.3) - pow(10, -Source_pH))) / ((f143 - f1) + (f3 - f343));
+    double Ct = Source_alkalinity / 50 / ((f143 - f1) + (f3 - f343));
+
+    // Step 5. Compute mole fractions at desired pH
+    double r1g = pow(10, TargetpH - 6.35);
+    double r2g = pow(10, TargetpH - 10.33);
+    double dg = 1 + r1g + r1g * r2g;
+    double f1g = 1 / dg;
+    double f3g = r1g * r2g / dg;
+
+    // Step 6. Use these to compute the milliequivalents acid required per liter (mEq/L)
+    double Acid = Ct * ((f1g - f1) + (f3 - f3g)) + pow(10, -TargetpH) - pow(10, -Source_pH);  //mEq/l
+    Acid += 0.01;   // Add acid that would be required for distilled water.
+
+    //Step 8. Get the acid data.
+    int AT = product->sparge_acid_type;
+    if (AT < 0 || AT >= my_acids.size()) {
+	AT = 0;
+	product->sparge_acid_type = 0;
+	ui->sp_acidtypeEdit->setCurrentIndex(0);
+	product->sparge_acid_perc = my_acids[0].AcidPrc;
+	ui->sp_acidpercEdit->setValue(product->sparge_acid_perc);
+    }
+    double fract = Utils::CalcFrac(TargetpH, my_acids[AT].pK1, my_acids[AT].pK2, my_acids[AT].pK3);
+
+    // Step 9. Now divide the mEq required by the "fraction". This is the required number of moles of acid.
+    Acid /= fract;
+
+    // Step 10. Multiply by molecular weight of the acid
+    Acid *= my_acids[AT].MolWt; //mg
+
+    // Step 11. Divide by Specific Gravity and Percentage to get the final ml.
+    Acid = Acid / my_acids[AT].AcidSG / (product->sparge_acid_perc / 100); //ml
+    Acid *= product->sparge_volume; //ml acid total
+    Acid = round(Acid * 100.0) / 100.0;
+    product->sparge_acid_amount = Acid / 1000;
+    ui->sp_acidvolEdit->setValue(Acid);
+}
+
+
+void EditProduct::sp_source_changed(int val)
+{
+    product->sparge_source = val;
+    calcSparge();
+    is_changed();
+}
+
+
+void EditProduct::sp_type_changed(int val)
+{
+    product->sparge_acid_type = val;
+    product->sparge_acid_perc = my_acids[val].AcidPrc;
+    ui->sp_acidpercEdit->setValue(product->sparge_acid_perc);
+    calcSparge();
+    is_changed();
+}
+
+
+void EditProduct::sp_ph_changed(double val)
+{
+    product->sparge_ph = val;
+    calcSparge();
+    is_changed();
+}
+
+
+double EditProduct::GetBUGU()
+{
+    double gu = (product->est_og - 1) * 1000;
+    if (gu > 0)
+	return product->est_ibu / gu;
+    return 0.5;
+}
+
+
+double EditProduct::GetOptSO4Clratio()
+{
+    if (ui->wt_so4Edit->value() > 0 && ui->wt_clEdit->value()) {
+	/* If target water is selected .. */
+	return (ui->wt_so4Edit->value() / ui->wt_clEdit->value());
+    }
+    double BUGU = GetBUGU();
+    return (-1.2 * BUGU + 1.4);
+}
+
+
+void EditProduct::mw_calc_acid_clicked()
+{
+    product->calc_acid = ! product->calc_acid;
+    ui->mw_autoEdit->setChecked(product->calc_acid);
+    ui->mw_phEdit->setReadOnly(! product->calc_acid);
+    ui->mw_phEdit->setButtonSymbols(product->calc_acid ? QAbstractSpinBox::UpDownArrows : QAbstractSpinBox::NoButtons);
+    ui->mw_acidvolEdit->setReadOnly(product->calc_acid);
+    ui->mw_acidvolEdit->setButtonSymbols(product->calc_acid ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows);
+    is_changed();
+    calcWater();
+}
+
+
+void EditProduct::mw_ph_changed(double val)
+{
+    if (! product->calc_acid)
+	return;
+
+    if (product->mash_ph != val) {
+	qDebug() << "mw_ph_changed" << val << product->mash_ph;
+	product->mash_ph = val;
+	is_changed();
+	calcWater();
+    }
+}
+
+
+void EditProduct::mw_acid_changed(double val)
+{
+    if (product->calc_acid)
+	return;
+
+    qDebug() << "on_mw_acid_changed" << val;
+    QString w = my_acids[product->wa_acid_name].name_en + ' ' + my_acids[product->wa_acid_name].name_nl;
+    set_brewing_salt(w, val);
+}
+
+
+void EditProduct::mw_type_changed(int val)
+{
+    if (val == product->wa_acid_name)
+	return;
+
+    qDebug() << "on_mw_type_changed" << val << "old" << product->wa_acid_name;
+    /*
+     * First remove current acid.
+     */
+    QString w = my_acids[product->wa_acid_name].name_en + ' ' + my_acids[product->wa_acid_name].name_nl;
+    brewing_salt_sub(w, 0);
+
+    product->wa_acid_name = val;
+    w = my_acids[product->wa_acid_name].name_en + ' ' + my_acids[product->wa_acid_name].name_nl;
+
+    product->wa_acid_perc = my_acids.at(val).AcidPrc;
+    ui->mw_acidpercEdit->setValue(my_acids.at(val).AcidPrc);
+    brewing_salt_sub(w, ui->mw_acidvolEdit->value());   // For now, set old amount.
+
+    is_changed();
+    calcWater();
+}
+
+
+void EditProduct::w2_volume_changed(double val)
+{
+    qDebug() << "w2_vol_changed" << val;
+
+    if (product->w2_total_alkalinity && product->w2_sulfate) {
+	/*
+	 * Seems a valid water, but don't go over the total.
+	 */
+	if (val < (product->w1_amount + product->w2_amount)) {
+	    product->w1_amount -= val - product->w2_amount;
+	    product->w2_amount = val;
+	    ui->w1_volEdit->setValue(product->w1_amount);
+	}
+    } else {
+	/*
+	 * Invalid water, block changes.
+	 */
+	product->w2_amount = 0;
+    }
+    ui->w2_volEdit->setValue(product->w2_amount);
+
+    calcWater();
+    is_changed();
+}
+
+
+void EditProduct::w1_name_changed(int val)
+{
+    QSqlQuery query;
+
+    qDebug() << "w1_name_changed" << val;
+    const QSignalBlocker blocker1(ui->w1_nameEdit);
+    if (val == 0) {
+	/*
+	 * If no water is selected, take the default water.
+	 */
+	val = my_default_water;
+	ui->w1_nameEdit->setCurrentIndex(val);
+    }
+
+    query.prepare("SELECT * FROM inventory_waters ORDER BY record");
+    query.exec();
+    query.first();
+    for (int i = 0; i < (val - 1); i++) {
+	query.next();
+    }
+    qDebug() << "set water" << query.value(1).toString();
+
+    product->w1_name = query.value(1).toString();
+    product->w1_calcium = query.value(3).toDouble();
+    product->w1_magnesium = query.value(8).toDouble();
+    product->w1_total_alkalinity = query.value(11).toDouble();
+    product->w1_sodium = query.value(7).toDouble();
+    product->w1_chloride = query.value(6).toDouble();
+    product->w1_sulfate = query.value(5).toDouble();
+    product->w1_ph = query.value(9).toDouble();
+
+    ui->w1_caEdit->setValue(product->w1_calcium);
+    ui->w1_mgEdit->setValue(product->w1_magnesium);
+    ui->w1_hco3Edit->setValue(product->w1_total_alkalinity * 1.22);
+    ui->w1_caco3Edit->setValue(product->w1_total_alkalinity);
+    ui->w1_naEdit->setValue(product->w1_sodium);
+    ui->w1_clEdit->setValue(product->w1_chloride);
+    ui->w1_so4Edit->setValue(product->w1_sulfate);
+    ui->w1_phEdit->setValue(product->w1_ph);
+
+    is_changed();
+    calcWater();
+}
+
+
+void EditProduct::w2_name_changed(int val)
+{
+    QSqlQuery query;
+
+    qDebug() << "w2_name_changed" << val;
+
+    if (val == 0) {	// Clear water 2.
+	product->w2_name = "";
+	product->w2_calcium = 0;
+	product->w2_magnesium = 0;
+	product->w2_total_alkalinity = 0;
+	product->w2_sodium = 0;
+	product->w2_chloride = 0;
+	product->w2_sulfate = 0;
+	product->w2_ph = 0;
+	product->w1_amount += product->w2_amount;
+	product->w2_amount = 0;
+    } else {
+        query.prepare("SELECT * FROM inventory_waters ORDER BY record");
+        query.exec();
+        query.first();
+        for (int i = 0; i < (val - 1); i++) {
+            query.next();
+        }
+	qDebug() << "set water" << query.value(1).toString();
+
+	product->w2_name = query.value(1).toString();
+	product->w2_calcium = query.value(3).toDouble();
+        product->w2_magnesium = query.value(8).toDouble();
+        product->w2_total_alkalinity = query.value(11).toDouble();
+        product->w2_sodium = query.value(7).toDouble();
+        product->w2_chloride = query.value(6).toDouble();
+        product->w2_sulfate = query.value(5).toDouble();
+        product->w2_ph = query.value(9).toDouble();
+    }
+    ui->w1_volEdit->setValue(product->w1_amount);
+    ui->w2_volEdit->setValue(product->w2_amount);
+    ui->w2_caEdit->setValue(product->w2_calcium);
+    ui->w2_mgEdit->setValue(product->w2_magnesium);
+    ui->w2_hco3Edit->setValue(product->w2_total_alkalinity * 1.22);
+    ui->w2_caco3Edit->setValue(product->w2_total_alkalinity);
+    ui->w2_naEdit->setValue(product->w2_sodium);
+    ui->w2_clEdit->setValue(product->w2_chloride);
+    ui->w2_so4Edit->setValue(product->w2_sulfate);
+    ui->w2_phEdit->setValue(product->w2_ph);
+
+    is_changed();
+    calcWater();
+}
+
+
+void EditProduct::wt_target_changed(int val)
+{
+    QSqlQuery query;
+
+    if (val == 0) {
+	/* Clear values */
+	ui->wt_caEdit->setValue(0);
+	ui->wt_mgEdit->setValue(0);
+	ui->wt_hco3Edit->setValue(0);
+	ui->wt_caco3Edit->setValue(0);
+	ui->wt_naEdit->setValue(0);
+	ui->wt_clEdit->setValue(0);
+	ui->wt_so4Edit->setValue(0);
+    } else {
+	query.prepare("SELECT * FROM profile_water ORDER BY name");
+    	query.exec();
+	query.first();
+    	for (int i = 0; i < (val - 1); i++) {
+            query.next();
+    	}
+	ui->wt_caEdit->setValue(query.value(2).toDouble());
+        ui->wt_mgEdit->setValue(query.value(7).toDouble());
+        ui->wt_hco3Edit->setValue(query.value(3).toDouble());
+        ui->wt_caco3Edit->setValue(query.value(10).toDouble());
+        ui->wt_naEdit->setValue(query.value(6).toDouble());
+        ui->wt_clEdit->setValue(query.value(5).toDouble());
+        ui->wt_so4Edit->setValue(query.value(4).toDouble());
+    }
+    calcWater();
+}
+
+
+void EditProduct::adjustWaters(double factor)
+{
+    int i;
+    double amount;
+
+    if (product->mashs.size() == 0)
+	return;
+
+    double mash_infuse = 0;
+    for (i = 0; i < product->mashs.size(); i++) {
+	if (product->mashs.at(i).step_type == 0) { // Infusion
+	    amount = round(product->mashs.at(i).step_infuse_amount * factor * 1000000.0) / 1000000.0;
+	    product->mashs[i].step_infuse_amount = amount;
+   	    mash_infuse += amount;
+	    product->mashs[i].step_volume = mash_infuse;
+  	}
+    }
+
+    const QSignalBlocker blocker1(ui->w1_volEdit);
+    const QSignalBlocker blocker2(ui->w2_volEdit);
+
+    if (product->w2_amount == 0) {
+	product->w1_amount = mash_infuse;
+	ui->w1_volEdit->setValue(mash_infuse);
+    } else {
+	double w1 = (product->w1_amount / (product->w1_amount + product->w2_amount)) * mash_infuse;
+	double w2 = (product->w2_amount / (product->w1_amount + product->w2_amount)) * mash_infuse;
+	product->w1_amount = w1;
+	product->w2_amount = w2;
+	ui->w1_volEdit->setValue(product->w1_amount);
+	ui->w2_volEdit->setValue(product->w2_amount);
+    }
+    product->wg_amount = mash_infuse;
+    ui->wg_volEdit->setValue(mash_infuse);
+}
+
+
+void EditProduct::wb_cacl2_changed(double val)  { set_brewing_salt("CaCl2", val);  }
+void EditProduct::wb_caso4_changed(double val)  { set_brewing_salt("CaSO4", val);  }
+void EditProduct::wb_mgso4_changed(double val)  { set_brewing_salt("MgSO4", val);  }
+void EditProduct::wb_nacl_changed(double val)   { set_brewing_salt("NaCl", val);   }
+void EditProduct::wb_mgcl2_changed(double val)  { set_brewing_salt("MgCl2", val);  }
+void EditProduct::wb_nahco3_changed(double val) { set_brewing_salt("NaHCO3", val); }
+void EditProduct::wb_caco3_changed(double val)  { set_brewing_salt("CaCO3", val);  }
+
+

mercurial