src/EditProductTab8.cpp

Sun, 12 Feb 2023 13:58:36 +0100

author
Michiel Broek <mbroek@mbse.eu>
date
Sun, 12 Feb 2023 13:58:36 +0100
changeset 494
49ac23d25f61
parent 435
6f84ab6125ad
permissions
-rw-r--r--

In monitor iSpindel: in the chart calculate the ranges, do't let the toolkit do that. Save the path for chart image download in the user settings. In the tooltip for the battery voltage line, also show the remaining battery capacity. In the monitor window show the battery capacity digit instead of allways 0. Updated the translations.

/**
 * 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.di_ph != 5.7) && ((F.acid_to_ph_57 < - 0.1) || (F.acid_to_ph_57 > 0.1))) {
	C1 = F.acid_to_ph_57 / (F.di_ph - 5.7);
	//qDebug() << "  BufferCapacity(" << F.name << C1 << F.acid_to_ph_57 << F.di_ph << "from database";
    } else {
	/*
	 * If the acid_to_ph_5.7 is unknown from the maltster, guess the required acid.
	 */
	switch (F.graintype) {
	   case FERMENTABLE_GRAINTYPE_BASE:
	   case FERMENTABLE_GRAINTYPE_KILNED:
	   case FERMENTABLE_GRAINTYPE_SPECIAL:		C1 = 0.014 * F.color - 34.192;
							break;
	   case FERMENTABLE_GRAINTYPE_CRYSTAL:		C1 = -0.0597 * F.color - 32.457;
							break;
	   case FERMENTABLE_GRAINTYPE_ROAST:		C1 = 0.0107 * F.color - 54.768;
							break;
	   case FERMENTABLE_GRAINTYPE_SOUR_MALT:	C1 = -149;
							break;
	}
	//qDebug() << "  BufferCapacity(" << F.name << C1 << "educated guess";
    }
    return C1;
}


double EditProduct::AcidRequired(double ZpH, Fermentables F)
{
    double C1 = BufferCapacity(F);
    double x = 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.added == FERMENTABLE_ADDED_MASH && F.graintype != FERMENTABLE_GRAINTYPE_NO_MALT) {
		x = AcidRequired(pHZ, 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::setButtons(bool locked)
{
    if (locked) {
       /*
        * If the brew is done, disable the buttons and that's it.
        */
       ui->w1_spButton->setDisabled(true);
       ui->w2_spButton->setDisabled(true);
       ui->wg_spButton->setDisabled(true);
       return;
    }
    ui->w1_spButton->setDisabled(false);

    const QSignalBlocker blocker1(ui->w1_spButton);
    const QSignalBlocker blocker2(ui->w2_spButton);
    const QSignalBlocker blocker3(ui->wg_spButton);

    if (product->w2_name != "") {
       ui->w2_spButton->setDisabled(false);
       if (product->w2_amount > 0.1 && product->w2_ph > 5) {
           /*
            * Water 2 is valid and used for mash, mixed is available.
            */
           ui->wg_spButton->setDisabled(false);
       } else {
           /*
            * No mixed water for mash. We can still sparge with source 2.
            */
           ui->wg_spButton->setDisabled(true);
           if (product->sparge_source == 2) {
               /*
                * If mixed was selected, switch to source 2.
                */
               product->sparge_source = 1;
               ui->w2_spButton->setChecked(true);
           }
       }
    } else {
	ui->w2_spButton->setDisabled(true);
        ui->wg_spButton->setDisabled(true);
	product->sparge_source = 0; // Fallback to source 1
        ui->w1_spButton->setChecked(true);
    }
}


void EditProduct::calcBU()
{
    if (product->stage < PROD_STAGE_WAIT)
	return;

    double BUGU = GetBUGU();
    ui->buguEdit->setValue(BUGU);
    ui->est_buguEdit->setValue(BUGU);
    ui->est_buguShow->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 og = product->est_og;
    double fg = product->est_fg;
    double ibu = product->est_ibu;

    if (product->stage > PROD_STAGE_BREW) {
	og = product->brew_fermenter_sg;
	ibu = product->brew_fermenter_ibu;
    }
    if (product->stage > PROD_STAGE_TERTIARY) {
	fg = product->fg;
    }

    if (fg < 1.002)	/* Can't be too low for this */
	fg = 1.002;

    double bure = ibu / ((0.1808 * Utils::sg_to_plato(og)) + (0.8192 * Utils::sg_to_plato(fg)));
    qDebug() << "BU:RE" << bure << product->est_fg << product->fg << product->est_ibu << product->brew_fermenter_ibu;
    ui->est_bufguEdit->setValue(bure);
    ui->est_bufguShow->setValue(bure);

}


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()";

    ui->w1_hardnessEdit->setValue(Utils::Hardness(product->w1_calcium, product->w1_magnesium));
    ui->w1_raEdit->setValue(Utils::ResidualAlkalinity(product->w1_total_alkalinity, product->w1_calcium, product->w1_magnesium));

    /*
     * 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);
	ui->w2_hardnessEdit->setValue(Utils::Hardness(product->w2_calcium, product->w2_magnesium));
    	ui->w2_raEdit->setValue(Utils::ResidualAlkalinity(product->w2_total_alkalinity, product->w2_calcium, product->w2_magnesium));
    } 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;

    bicarbonate = Utils::Bicarbonate(total_alkalinity, ph);
    ui->wg_volEdit->setValue(liters);
    ui->wg_caEdit->setValue(calcium);
    ui->wg_mgEdit->setValue(magnesium);
    ui->wg_hco3Edit->setValue(bicarbonate);
    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);
    ui->wg_hardnessEdit->setValue(Utils::Hardness(calcium, magnesium));
    ui->wg_raEdit->setValue(Utils::ResidualAlkalinity(total_alkalinity, calcium, magnesium));

    /* 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 old ml:" << Acid;
	    Acid = Acidmg;

	    double RealSG = round(((my_acids[AT].AcidSG - 1000) * (product->wa_acid_perc / 100)) + 1000);
	    Acid /= RealSG;
	    Acid /= my_acids[AT].AcidPrc / 100;
	    Acid = round(Acid * 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, MISC_USES_MASH);
	    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.
	double RealSG = round(((my_acids[AT].AcidSG - 1000) * (product->wa_acid_perc / 100)) + 1000);
	// Then calculate the new pH with added acids and malts
	qDebug() << "  Mash pH:" << pHa;
	Acid = RealSG;
	Acid *= ui->mw_acidvolEdit->value();
	Acid *= my_acids[AT].AcidPrc / 100;
	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() << "  Acid:" << Acid << "protonDeficit:" << 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;
    } 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;
    }

    calcBU();

    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_hardnessEdit->setValue(Utils::Hardness(calcium, magnesium));
    ui->wb_raEdit->setValue(Utils::ResidualAlkalinity(total_alkalinity, calcium, magnesium));

    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 = 7.0;

    qDebug() << "calcSparge()";
    setButtons(product->stage > PROD_STAGE_BREW);

    if (product->sparge_source == 1 && product->w2_ph > 0.0) {
	product->ws_calcium = product->w2_calcium;
	product->ws_magnesium = product->w2_magnesium;
	product->ws_total_alkalinity = product->w2_total_alkalinity;
	product->ws_sodium = product->w2_sodium;
	product->ws_chloride = product->w2_chloride;
	product->ws_sulfate = product->w2_sulfate;
	Source_pH = product->w2_ph;
    } else if (product->sparge_source == 2 && product->w2_ph > 0.0) {
	product->ws_calcium = product->wg_calcium;
	product->ws_magnesium = product->wg_magnesium;
	product->ws_total_alkalinity = product->wg_total_alkalinity;
	product->ws_sodium = product->wg_sodium;
	product->ws_chloride = product->wg_chloride;
	product->ws_sulfate = product->wg_sulfate;
	Source_pH = product->wg_ph;
    } else {
	product->ws_calcium = product->w1_calcium;
	product->ws_magnesium = product->w1_magnesium;
	product->ws_total_alkalinity = product->w1_total_alkalinity;
	product->ws_sodium = product->w1_sodium;
	product->ws_chloride = product->w1_chloride;
	product->ws_sulfate = product->w1_sulfate;
	Source_pH = product->w1_ph;
    }

    /* Calculate Salt additions */
    if (product->sparge_volume > 0) {
	product->ws_calcium += ( ui->ss_cacl2Edit->value() * MMCa / MMCaCl2 * 1000 + ui->ss_caso4Edit->value() * MMCa / MMCaSO4 * 1000) / product->sparge_volume;
	product->ws_magnesium += (ui->ss_mgso4Edit->value() * MMMg / MMMgSO4 * 1000 + ui->ss_mgcl2Edit->value() * MMMg / MMMgCl2 * 1000) / product->sparge_volume;
	product->ws_sodium += (ui->ss_naclEdit->value() * MMNa / MMNaCl * 1000) / product->sparge_volume;
	product->ws_sulfate += (ui->ss_caso4Edit->value() * MMSO4 / MMCaSO4 * 1000 + ui->ss_mgso4Edit->value() * MMSO4 / MMMgSO4 * 1000) / product->sparge_volume;
	product->ws_chloride += (2 * ui->ss_cacl2Edit->value() * MMCl / MMCaCl2 * 1000 + ui->ss_naclEdit->value() * MMCl / MMNaCl * 1000 +
				ui->ss_mgcl2Edit->value() * MMCl / MMMgCl2 * 1000) / product->sparge_volume;
    }

    /* Show the spargewater with salt additions. */
    ui->sp_caEdit->setValue(product->ws_calcium);
    ui->sp_mgEdit->setValue(product->ws_magnesium);
    ui->sp_hco3Edit->setValue(Utils::Bicarbonate(product->ws_total_alkalinity, Source_pH));
    ui->sp_caco3Edit->setValue(product->ws_total_alkalinity);
    ui->sp_naEdit->setValue(product->ws_sodium);
    ui->sp_clEdit->setValue(product->ws_chloride);
    ui->sp_so4Edit->setValue(product->ws_sulfate);
    ui->sp_phShow->setValue(product->sparge_ph);
    ui->sp_hardnessEdit->setValue(Utils::Hardness(product->ws_calcium, product->ws_magnesium));
    ui->sp_raEdit->setValue(Utils::ResidualAlkalinity(product->ws_total_alkalinity, product->ws_calcium, product->ws_magnesium));

    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);
    }

    /*
     * Auto calculate the required acid
     */
    if (product->calc_acid) {
    	// 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 = product->ws_total_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 7. There is no step 7.

    	// Step 8. Get the acid data.
    	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.
    	double RealSG = round(((my_acids[AT].AcidSG - 1000) * (product->sparge_acid_perc / 100)) + 1000);
    	Acid = Acid / RealSG;		//ml
    	Acid *= product->sparge_volume;	//ml acid total at 100%
    	Acid /= my_acids[AT].AcidPrc / 100;	//ml acid at supplied strength
    	Acid = round(Acid * 100.0) / 100.0;
    	product->sparge_acid_amount = Acid / 1000;
    	QString w = my_acids[AT].name_en + ' ' + my_acids[AT].name_nl;
    	brewing_salt_sub(w, Acid, MISC_USES_SPARGE);	// Put it in the miscs table.
    	ui->sp_acidvolEdit->setValue(Acid);
    }

//    ui->sp_phShow->setValue(product->sparge_ph);
    // Finally calculate the estimate preboil pH
    product->est_preboil_ph = -log10(((pow(10, -product->mash_ph) * product->wg_amount) + (pow(10, -product->sparge_ph) * product->brew_sparge_est)) /
		     	      (product->wg_amount + product->brew_sparge_est));
    ui->preboil_phEdit->setValue(product->est_preboil_ph);
    ui->brew_preboilphShow->setValue(product->est_preboil_ph);
}


void EditProduct::sp_group_changed(int val)
{
    if (val != product->sparge_source) {
	product->sparge_source = val;
    	calcSparge();
    	is_changed();
    }
}


void EditProduct::sp_volume_changed(double val)
{
    if (! product->calc_acid) {
	product->sparge_acid_amount *= val / product->sparge_volume;
	const QSignalBlocker blocker1(ui->sp_acidvolEdit);
	ui->sp_acidvolEdit->setValue(product->sparge_acid_amount * 1000.0);
    }

    product->sparge_volume = val;
    ui->brew_spargevolShow->setValue(val);
    calcSparge();
    is_changed();
}


void EditProduct::sp_temp_changed(double val)
{
    product->sparge_temp = val;
    calcSparge();
    is_changed();
}


void EditProduct::sp_type_changed(int val)
{
    if (val == product->sparge_acid_type)
        return;

    qDebug() << "sp_type_changed" << val << "old" << product->sparge_acid_type;
    /*
     * First remove current acid.
     */
    QString w = my_acids[product->sparge_acid_type].name_en + ' ' + my_acids[product->sparge_acid_type].name_nl;
    brewing_salt_sub(w, 0, MISC_USES_SPARGE);

    product->sparge_acid_type = val;
    w = my_acids[product->sparge_acid_type].name_en + ' ' + my_acids[product->sparge_acid_type].name_nl;

    product->sparge_acid_perc = my_acids[val].AcidPrc;
    ui->sp_acidpercEdit->setValue(product->sparge_acid_perc);
    brewing_salt_sub(w, ui->sp_acidvolEdit->value(), MISC_USES_SPARGE);	// For now, set old amount.

    calcSparge();
    is_changed();
}


void EditProduct::sp_ph_changed(double val)
{
    product->sparge_ph = val;
    ui->brew_spargephShow->setValue(product->sparge_ph);
    calcSparge();
    is_changed();
}


void EditProduct::sp_acid_changed(double val)
{
    if (product->calc_acid)
        return;

    qDebug() << "sp_acid_changed" << val << product->sparge_acid_amount * 1000.0;

    double TargetpH = product->sparge_ph;
    double Source_pH = product->w1_ph;
    double Source_alkalinity = product->w1_total_alkalinity;

    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 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;
	}
    }

    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);
    }

    bool go_up = (val < (product->sparge_acid_amount * 1000.0));
    bool loop = true;

    while (loop) {

	if (go_up)
	    TargetpH += 0.001;
	else
	    TargetpH -= 0.001;
	//qDebug() << "  TargetpH" << TargetpH << "up" << go_up;

        // 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 / 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 7. There is no step 7.

        // Step 8. Get the acid data.
        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.
        double RealSG = round(((my_acids[AT].AcidSG - 1000) * (product->sparge_acid_perc / 100)) + 1000);
        Acid = Acid / RealSG;           //ml
        Acid *= product->sparge_volume; //ml acid total at 100%
        Acid /= my_acids[AT].AcidPrc / 100;     //ml acid at supplied strength
        Acid = round(Acid * 100.0) / 100.0;
        product->sparge_acid_amount = Acid / 1000;
	//qDebug() << "  acid" << product->sparge_acid_amount;

	if (go_up && (val > (product->sparge_acid_amount * 1000.0)))
	    loop = false;
	else if (! go_up && (val < (product->sparge_acid_amount * 1000.0)))
	    loop = false;

	//qDebug() << "  test" << loop << go_up << val << product->sparge_acid_amount * 1000.0;
    }

    const QSignalBlocker blocker1(ui->sp_phEdit);
    product->sparge_ph = round(TargetpH * 100) / 100;
    ui->sp_phEdit->setValue(product->sparge_ph);
    ui->sp_phShow->setValue(product->sparge_ph);

    QString w = my_acids[AT].name_en + ' ' + my_acids[AT].name_nl;
    set_brewing_salt(w, val, MISC_USES_SPARGE);
    //qDebug() << "  new" << product->sparge_ph << val;
}


double EditProduct::GetBUGU()
{
    double og = product->est_og;
    double ibu = product->est_ibu;

    if (product->stage > PROD_STAGE_BREW) {
        og = product->brew_fermenter_sg;
	ibu = product->brew_fermenter_ibu;
    }

    double gu = (og - 1) * 1000;
    if (gu > 0)
	return 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);
    ui->sp_phEdit->setReadOnly(! product->calc_acid);
    ui->sp_phEdit->setButtonSymbols(product->calc_acid ? QAbstractSpinBox::UpDownArrows : QAbstractSpinBox::NoButtons);
    ui->sp_acidvolEdit->setReadOnly(product->calc_acid);
    ui->sp_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, MISC_USES_MASH);
}


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, MISC_USES_MASH);

    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(), MISC_USES_MASH);   // 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);

    check_waters();
    calcWater();
    is_changed();
}


void EditProduct::check_waters()
{
    QSqlQuery query;
    product->waters_ok = true;

    if (product->w1_name != "") {
	qDebug() << "check_waters 1" << product->w1_name;
    	query.prepare("SELECT unlimited_stock,inventory FROM inventory_waters WHERE name=:name");
    	query.bindValue(":name", product->w1_name);
    	query.exec();
	if (query.first()) {
	    if ((query.value("unlimited_stock").toInt() == 0) && (query.value("inventory").toDouble() < product->w1_amount)) {
		product->waters_ok = false;
		qDebug() << "w1_amount too low";
	    }
	}
    }

    if ((product->w2_name != "") && (product->w2_amount > 0)) {
        qDebug() << "check_waters 2" << product->w2_name;
        query.prepare("SELECT unlimited_stock,inventory FROM inventory_waters WHERE name=:name");
        query.bindValue(":name", product->w2_name);
        query.exec();
        if (query.first()) {
            if ((query.value("unlimited_stock").toInt() == 0) && (query.value("inventory").toDouble() < product->w2_amount)) {
                product->waters_ok = false;
                qDebug() << "w2_amount too low";
            }
	    ui->w2_spButton->setEnabled(true);
            ui->wg_spButton->setEnabled(true);
        }
    } else {
	/*
	 * Block selecting sparge water 2 and mixed water.
	 */
	ui->w2_spButton->setEnabled(false);
	ui->wg_spButton->setEnabled(false);
	product->sparge_source = 0;      // Only water source 1
    }
}


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("name").toString();

    product->w1_name = query.value("name").toString();
    product->w1_calcium = query.value("calcium").toDouble();
    product->w1_magnesium = query.value("magnesium").toDouble();
    product->w1_total_alkalinity = query.value("total_alkalinity").toDouble();
    product->w1_sodium = query.value("sodium").toDouble();
    product->w1_chloride = query.value("chloride").toDouble();
    product->w1_sulfate = query.value("sulfate").toDouble();
    product->w1_ph = query.value("ph").toDouble();

    ui->w1_caEdit->setValue(product->w1_calcium);
    ui->w1_mgEdit->setValue(product->w1_magnesium);
    ui->w1_hco3Edit->setValue(Utils::Bicarbonate(product->w1_total_alkalinity, product->w1_ph));
    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);
    ui->w1_hardnessEdit->setValue(Utils::Hardness(product->w1_calcium, product->w1_magnesium));
    ui->w1_raEdit->setValue(Utils::ResidualAlkalinity(product->w1_total_alkalinity, product->w1_calcium, product->w1_magnesium));

    check_waters();
    is_changed();
    calcWater();
}


void EditProduct::w2_name_changed(int val)
{
    QSqlQuery query;
    double hardness, ra_ppm;

    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;
	hardness = ra_ppm = 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();
	hardness = Utils::Hardness(product->w2_calcium, product->w2_magnesium);
	ra_ppm = Utils::ResidualAlkalinity(product->w2_total_alkalinity, product->w2_calcium, product->w2_magnesium);
    }
    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(Utils::Bicarbonate(product->w2_total_alkalinity, product->w2_ph));
    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);
    ui->w2_hardnessEdit->setValue(hardness);
    ui->w2_raEdit->setValue(ra_ppm);

    check_waters();
    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);
	ui->wt_hardnessEdit->setValue(0);
    	ui->wt_raEdit->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("calcium").toDouble());
        ui->wt_mgEdit->setValue(query.value("magnesium").toDouble());
        ui->wt_hco3Edit->setValue(query.value("bicarbonate").toDouble());
        ui->wt_caco3Edit->setValue(query.value("total_alkalinity").toDouble());
        ui->wt_naEdit->setValue(query.value("sodium").toDouble());
        ui->wt_clEdit->setValue(query.value("chloride").toDouble());
        ui->wt_so4Edit->setValue(query.value("sulfate").toDouble());
	ui->wt_hardnessEdit->setValue(Utils::Hardness(query.value("calcium").toDouble(), query.value("magnesium").toDouble()));
    	ui->wt_raEdit->setValue(Utils::ResidualAlkalinity(query.value("total_alkalinity").toDouble(), query.value("calcium").toDouble(), query.value("magnesium").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, MISC_USES_MASH);   }
void EditProduct::wb_caso4_changed(double val)  { set_brewing_salt("CaSO4", val, MISC_USES_MASH);   }
void EditProduct::wb_mgso4_changed(double val)  { set_brewing_salt("MgSO4", val, MISC_USES_MASH);   }
void EditProduct::wb_nacl_changed(double val)   { set_brewing_salt("NaCl", val, MISC_USES_MASH);    }
void EditProduct::wb_mgcl2_changed(double val)  { set_brewing_salt("MgCl2", val, MISC_USES_MASH);   }
void EditProduct::wb_nahco3_changed(double val) { set_brewing_salt("NaHCO3", val, MISC_USES_MASH);  }
void EditProduct::wb_caco3_changed(double val)  { set_brewing_salt("CaCO3", val, MISC_USES_MASH);   }
void EditProduct::sp_cacl2_changed(double val)  { set_brewing_salt("CaCl2", val, MISC_USES_SPARGE); }
void EditProduct::sp_caso4_changed(double val)  { set_brewing_salt("CaSO4", val, MISC_USES_SPARGE); }
void EditProduct::sp_mgso4_changed(double val)  { set_brewing_salt("MgSO4", val, MISC_USES_SPARGE); }
void EditProduct::sp_nacl_changed(double val)   { set_brewing_salt("NaCl", val, MISC_USES_SPARGE);  }
void EditProduct::sp_mgcl2_changed(double val)  { set_brewing_salt("MgCl2", val, MISC_USES_SPARGE); }

mercurial