src/EditRecipeTab7.cpp

Thu, 18 Aug 2022 20:34:15 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Thu, 18 Aug 2022 20:34:15 +0200
changeset 401
583148eb6e01
parent 395
7212b980a527
permissions
-rw-r--r--

Init est_carb field for new products.

/**
 * EditRecipe.cpp is part of bmsapp.
 *
 * tab 6, 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 EditRecipe::refreshWaters()
{

    // calc_acid
    ui->mw_phEdit->setValue(recipe->mash_ph);
    // mash_name
    //ui->mw_acidpercEdit->setValue(recipe->

}


/*
 * Z alkalinity is the amount of acid (in mEq/l) needed to bring water to the target pH (Z pH)
 */
double EditRecipe::ZAlkalinity(double pHZ)
{
    double C43 = Utils::Charge(4.3);
    double Cw = Utils::Charge(recipe->wg_ph);
    double Cz = Utils::Charge(pHZ);
    double DeltaCNaught = -C43 + Cw;
    double CT = recipe->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 EditRecipe::ZRA(double pHZ)
{
    double Calc = recipe->wg_calcium / (MMCa / 2);
    double Magn = recipe->wg_magnesium / (MMMg / 2);
    double Z = ZAlkalinity(pHZ);
    return Z - (Calc / 3.5 + Magn / 7);
}


double EditRecipe::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);
     } 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;
	}
    }
    return C1;
}


double EditRecipe::AcidRequired(double ZpH, Fermentables F)
{
    double C1 = BufferCapacity(F);
    double x = F.di_ph;
    return C1 * (ZpH - x);
}


double EditRecipe::ProtonDeficit(double pHZ)
{
    double C1, x;
    int i, error_count = 0;
    double Result = ZRA(pHZ) * recipe->wg_amount;
    Fermentables F;

    /*
     * proton deficit for the grist
     */
    if (recipe->fermentables.size()) {
	for (i = 0; i < recipe->fermentables.size(); i++) {
	    F = recipe->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 EditRecipe::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 EditRecipe::setButtons(bool locked)
{
    if (locked) {
	/*
	 * If recipe is locked, 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 (recipe->w2_name != "") {
	ui->w2_spButton->setDisabled(false);
	if (recipe->w2_amount > 0.1 && recipe->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 (recipe->sparge_source == 2) {
		/*
		 * If mixed was selected, switch to source 2.
		 */
		recipe->sparge_source = 1;
		ui->w2_spButton->setChecked(true);
	    }
	}
    } else {
	ui->w2_spButton->setDisabled(true);
        ui->wg_spButton->setDisabled(true);
	recipe->sparge_source = 0; // Fallback to source 1
        ui->w1_spButton->setChecked(true);
    }
}


void EditRecipe::calcBU()
{
    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 = recipe->est_og;
    double fg = recipe->est_fg;
    double ibu = recipe->est_ibu;

    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 << recipe->est_fg << recipe->est_ibu;
    ui->est_bufguEdit->setValue(bure);
    ui->est_bufguShow->setValue(bure);

}


void EditRecipe::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(recipe->w1_calcium, recipe->w1_magnesium));
    ui->w1_raEdit->setValue(Utils::ResidualAlkalinity(recipe->w1_total_alkalinity, recipe->w1_calcium, recipe->w1_magnesium));

    /*
     * If there is a dilute water source, mix the waters.
     */
    if (recipe->w2_name != "") {
	liters = recipe->w1_amount + recipe->w2_amount;
	calcium = Utils::mix(recipe->w1_amount, recipe->w2_amount, recipe->w1_calcium, recipe->w2_calcium);
	magnesium = Utils::mix(recipe->w1_amount, recipe->w2_amount, recipe->w1_magnesium, recipe->w2_magnesium);
	sodium = Utils::mix(recipe->w1_amount, recipe->w2_amount, recipe->w1_sodium, recipe->w2_sodium);
	chloride = Utils::mix(recipe->w1_amount, recipe->w2_amount, recipe->w1_chloride, recipe->w2_chloride);
	sulfate = Utils::mix(recipe->w1_amount, recipe->w2_amount, recipe->w1_sulfate, recipe->w2_sulfate);
	total_alkalinity = Utils::mix(recipe->w1_amount, recipe->w2_amount, recipe->w1_total_alkalinity, recipe->w2_total_alkalinity);
	ph = -log10(((pow(10, -recipe->w1_ph) * recipe->w1_amount) + (pow(10, -recipe->w2_ph) * recipe->w2_amount)) / liters);
	ui->w2_hardnessEdit->setValue(Utils::Hardness(recipe->w2_calcium, recipe->w2_magnesium));
	ui->w2_raEdit->setValue(Utils::ResidualAlkalinity(recipe->w2_total_alkalinity, recipe->w2_calcium, recipe->w2_magnesium));
    } else {
	liters = recipe->w1_amount;
	calcium = recipe->w1_calcium;
	magnesium = recipe->w1_magnesium;
	sodium = recipe->w1_sodium;
	chloride = recipe->w1_chloride;
	sulfate = recipe->w1_sulfate;
	total_alkalinity = recipe->w1_total_alkalinity;
	ph = recipe->w1_ph;
    }

    recipe->wg_amount = liters;
    recipe->wg_calcium = round(calcium * 10.0) / 10.0;
    recipe->wg_magnesium = round(magnesium * 10.0) / 10.0;
    recipe->wg_sodium = round(sodium * 10.0) / 10.0;
    recipe->wg_chloride = round(chloride * 10.0) / 10.0;
    recipe->wg_sulfate = round(sulfate * 10.0) / 10.0;
    recipe->wg_total_alkalinity = round(total_alkalinity * 10.0) / 10.0;
    recipe->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 (recipe->wa_acid_name < 0 || recipe->wa_acid_name >= my_acids.size()) {
	recipe->wa_acid_name = 0;
	recipe->wa_acid_perc = my_acids.at(0).AcidPrc;
	ui->mw_acidPick->setCurrentIndex(0);
	ui->mw_acidpercEdit->setValue(my_acids.at(0).AcidPrc);
    }
    AT = recipe->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 (recipe->calc_acid) {
	/*
	 * Auto calculate the needed acid.
	 */
	TpH = recipe->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 / (recipe->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) * (recipe->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);
	recipe->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) * (recipe->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() << "  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);
        recipe->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;
    }

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

    recipe->wb_calcium = calcium;
    recipe->wb_magnesium = magnesium;
    recipe->wb_total_alkalinity = total_alkalinity;
    recipe->wb_sodium = sodium;
    recipe->wb_chloride = chloride;
    recipe->wb_sulfate = sulfate;
    recipe->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 EditRecipe::calcSparge()
{
    double TargetpH = recipe->sparge_ph;
    double Source_pH = 7.0;

    qDebug() << "calcSparge()";
    setButtons(recipe->locked);

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

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

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

    int AT = recipe->sparge_acid_type;
    if (AT < 0 || AT >= my_acids.size()) {
        AT = 0;
        recipe->sparge_acid_type = 0;
        ui->sp_acidtypeEdit->setCurrentIndex(0);
        recipe->sparge_acid_perc = my_acids[0].AcidPrc;
        ui->sp_acidpercEdit->setValue(recipe->sparge_acid_perc);
    }

    /*
     * Auto calculate the required acid
     */
    if (recipe->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 = recipe->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 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) * (recipe->sparge_acid_perc / 100)) + 1000);
	Acid = Acid / RealSG;           //ml
	Acid *= recipe->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;
	recipe->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);
    }

    // Finally calculate the estimate preboil pH
    recipe->preboil_ph = -log10(((pow(10, -recipe->mash_ph) * recipe->wg_amount) + (pow(10, -recipe->sparge_ph) * recipe->sparge_volume)) /
                             (recipe->wg_amount + recipe->sparge_volume));
    ui->preboil_phEdit->setValue(recipe->preboil_ph);
}


void EditRecipe::sp_group_changed(int val)
{
    if (val != recipe->sparge_source) {
           qDebug() << "sp_group_changed" << val;
       recipe->sparge_source = val;
       calcSparge();
       is_changed();
    }
}


void EditRecipe::sp_type_changed(int val)
{
    if (val == recipe->sparge_acid_type)
        return;

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

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

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

    calcSparge();
    is_changed();
}


void EditRecipe::sp_ph_changed(double val)
{
    recipe->sparge_ph = val;
    calcSparge();
    is_changed();
}


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

    recipe->sparge_volume = val;
    calcSparge();
    is_changed();
}


void EditRecipe::sp_acid_changed(double val)
{
    if (recipe->calc_acid)
        return;

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

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

    if (recipe->sparge_source == 1) { // Source 2
       if (recipe->w2_ph > 0.0 && recipe->w2_amount > 0) {
           Source_pH = recipe->w2_ph;
            Source_alkalinity = recipe->w2_total_alkalinity;
       }
    } else if (recipe->sparge_source == 2) { // Mixed
       if (recipe->w2_ph > 0.0 && recipe->w2_amount > 0) {
           Source_pH = recipe->wg_ph;
            Source_alkalinity = recipe->wg_total_alkalinity;
       }
    }

    int AT = recipe->sparge_acid_type;
    if (AT < 0 || AT >= my_acids.size()) {
        AT = 0;
        recipe->sparge_acid_type = 0;
        ui->sp_acidtypeEdit->setCurrentIndex(0);
        recipe->sparge_acid_perc = my_acids[0].AcidPrc;
        ui->sp_acidpercEdit->setValue(recipe->sparge_acid_perc);
    }

    bool go_up = (val < (recipe->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) * (recipe->sparge_acid_perc / 100)) + 1000);
        Acid = Acid / RealSG;           //ml
        Acid *= recipe->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;
        recipe->sparge_acid_amount = Acid / 1000;
       //qDebug() << "  acid" << recipe->sparge_acid_amount;

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

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

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

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


double EditRecipe::GetBUGU()
{
    double gu = (recipe->est_og - 1) * 1000;
    if (gu > 0)
	return recipe->est_ibu / gu;
    return 0.5;
}


double EditRecipe::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 EditRecipe::mw_calc_acid_clicked()
{
    recipe->calc_acid = ! recipe->calc_acid;
    ui->mw_autoEdit->setChecked(recipe->calc_acid);
    ui->mw_phEdit->setReadOnly(! recipe->calc_acid);
    ui->mw_phEdit->setButtonSymbols(recipe->calc_acid ? QAbstractSpinBox::UpDownArrows : QAbstractSpinBox::NoButtons);
    ui->mw_acidvolEdit->setReadOnly(recipe->calc_acid);
    ui->mw_acidvolEdit->setButtonSymbols(recipe->calc_acid ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows);
    ui->sp_phEdit->setReadOnly(! recipe->calc_acid);
    ui->sp_phEdit->setButtonSymbols(recipe->calc_acid ? QAbstractSpinBox::UpDownArrows : QAbstractSpinBox::NoButtons);
    ui->sp_acidvolEdit->setReadOnly(recipe->calc_acid);
    ui->sp_acidvolEdit->setButtonSymbols(recipe->calc_acid ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows);
    is_changed();
    calcWater();
}


void EditRecipe::mw_ph_changed(double val)
{
    if (! recipe->calc_acid)
	return;

    if (recipe->mash_ph != val) {
	qDebug() << "mw_ph_changed" << val << recipe->mash_ph;
	recipe->mash_ph = val;
	is_changed();
	calcWater();
    }
}


void EditRecipe::mw_acid_changed(double val)
{
    if (recipe->calc_acid)
	return;

    qDebug() << "on_mw_acid_changed" << val;
    QString w = my_acids[recipe->wa_acid_name].name_en + ' ' + my_acids[recipe->wa_acid_name].name_nl;
    set_brewing_salt(w, val, MISC_USES_MASH);
}


void EditRecipe::mw_type_changed(int val)
{
    if (val == recipe->wa_acid_name)
	return;

    qDebug() << "on_mw_type_changed" << val << "old" << recipe->wa_acid_name;
    /*
     * First remove current acid.
     */
    QString w = my_acids[recipe->wa_acid_name].name_en + ' ' + my_acids[recipe->wa_acid_name].name_nl;
    brewing_salt_sub(w, 0, MISC_USES_MASH);

    recipe->wa_acid_name = val;
    w = my_acids[recipe->wa_acid_name].name_en + ' ' + my_acids[recipe->wa_acid_name].name_nl;

    recipe->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 EditRecipe::w2_volume_changed(double val)
{
    qDebug() << "w2_vol_changed" << val;

    if (recipe->w2_total_alkalinity && recipe->w2_sulfate) {
	/*
	 * Seems a valid water, but don't go over the total.
	 */
	if (val < (recipe->w1_amount + recipe->w2_amount)) {
	    recipe->w1_amount -= val - recipe->w2_amount;
	    recipe->w2_amount = val;
	    ui->w1_volEdit->setValue(recipe->w1_amount);
	}
    } else {
	/*
	 * Invalid water, block changes.
	 */
	recipe->w2_amount = 0;
    }
    ui->w2_volEdit->setValue(recipe->w2_amount);

    calcWater();
    is_changed();
}


void EditRecipe::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();

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

    ui->w1_caEdit->setValue(recipe->w1_calcium);
    ui->w1_mgEdit->setValue(recipe->w1_magnesium);
    ui->w1_hco3Edit->setValue(Utils::Bicarbonate(recipe->w1_total_alkalinity, recipe->w1_ph));
    ui->w1_caco3Edit->setValue(recipe->w1_total_alkalinity);
    ui->w1_naEdit->setValue(recipe->w1_sodium);
    ui->w1_clEdit->setValue(recipe->w1_chloride);
    ui->w1_so4Edit->setValue(recipe->w1_sulfate);
    ui->w1_phEdit->setValue(recipe->w1_ph);
    ui->w1_hardnessEdit->setValue(Utils::Hardness(recipe->w1_calcium, recipe->w1_magnesium));
    ui->w1_raEdit->setValue(Utils::ResidualAlkalinity(recipe->w1_total_alkalinity, recipe->w1_calcium, recipe->w1_magnesium));

    is_changed();
    calcWater();
}


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

    qDebug() << "w2_name_changed" << val;

    if (val == 0) {	// Clear water 2.
	recipe->w2_name = "";
	recipe->w2_calcium = 0;
	recipe->w2_magnesium = 0;
	recipe->w2_total_alkalinity = 0;
	recipe->w2_sodium = 0;
	recipe->w2_chloride = 0;
	recipe->w2_sulfate = 0;
	recipe->w2_ph = 0;
	recipe->w1_amount += recipe->w2_amount;
	recipe->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();

	recipe->w2_name = query.value("name").toString();
	recipe->w2_calcium = query.value("calcium").toDouble();
        recipe->w2_magnesium = query.value("magnesium").toDouble();
        recipe->w2_total_alkalinity = query.value("total_alkalinity").toDouble();
        recipe->w2_sodium = query.value("sodium").toDouble();
        recipe->w2_chloride = query.value("chloride").toDouble();
        recipe->w2_sulfate = query.value("sulfate").toDouble();
        recipe->w2_ph = query.value("ph").toDouble();
	hardness = Utils::Hardness(recipe->w2_calcium, recipe->w2_magnesium);
	ra_ppm = Utils::ResidualAlkalinity(recipe->w2_total_alkalinity, recipe->w2_calcium, recipe->w2_magnesium);
    }
    ui->w1_volEdit->setValue(recipe->w1_amount);
    ui->w2_volEdit->setValue(recipe->w2_amount);
    ui->w2_caEdit->setValue(recipe->w2_calcium);
    ui->w2_mgEdit->setValue(recipe->w2_magnesium);
    ui->w2_hco3Edit->setValue(Utils::Bicarbonate(recipe->w2_total_alkalinity, recipe->w2_ph));
    ui->w2_caco3Edit->setValue(recipe->w2_total_alkalinity);
    ui->w2_naEdit->setValue(recipe->w2_sodium);
    ui->w2_clEdit->setValue(recipe->w2_chloride);
    ui->w2_so4Edit->setValue(recipe->w2_sulfate);
    ui->w2_phEdit->setValue(recipe->w2_ph);
    ui->w2_hardnessEdit->setValue(hardness);
    ui->w2_raEdit->setValue(ra_ppm);

    is_changed();
    calcWater();
}


void EditRecipe::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 EditRecipe::adjustWaters(double factor)
{
    int i;
    double amount;

    if (recipe->mashs.size() == 0)
	return;

    double mash_infuse = 0;
    for (i = 0; i < recipe->mashs.size(); i++) {
	if (recipe->mashs.at(i).step_type == 0) { // Infusion
	    amount = round(recipe->mashs.at(i).step_infuse_amount * factor * 1000000.0) / 1000000.0;
	    recipe->mashs[i].step_infuse_amount = amount;
   	    mash_infuse += amount;
	    recipe->mashs[i].step_volume = mash_infuse;
  	}
    }

    const QSignalBlocker blocker1(ui->w1_volEdit);
    const QSignalBlocker blocker2(ui->w2_volEdit);

    if (recipe->w2_amount == 0) {
	recipe->w1_amount = mash_infuse;
	ui->w1_volEdit->setValue(mash_infuse);
    } else {
	double w1 = (recipe->w1_amount / (recipe->w1_amount + recipe->w2_amount)) * mash_infuse;
	double w2 = (recipe->w2_amount / (recipe->w1_amount + recipe->w2_amount)) * mash_infuse;
	recipe->w1_amount = w1;
	recipe->w2_amount = w2;
	ui->w1_volEdit->setValue(recipe->w1_amount);
	ui->w2_volEdit->setValue(recipe->w2_amount);
    }
    recipe->wg_amount = mash_infuse;
    ui->wg_volEdit->setValue(mash_infuse);
}


void EditRecipe::wb_cacl2_changed(double val)  { set_brewing_salt("CaCl2", val, MISC_USES_MASH);   }
void EditRecipe::wb_caso4_changed(double val)  { set_brewing_salt("CaSO4", val, MISC_USES_MASH);   }
void EditRecipe::wb_mgso4_changed(double val)  { set_brewing_salt("MgSO4", val, MISC_USES_MASH);   }
void EditRecipe::wb_nacl_changed(double val)   { set_brewing_salt("NaCl", val, MISC_USES_MASH);    }
void EditRecipe::wb_mgcl2_changed(double val)  { set_brewing_salt("MgCl2", val, MISC_USES_MASH);   }
void EditRecipe::wb_nahco3_changed(double val) { set_brewing_salt("NaHCO3", val, MISC_USES_MASH);  }
void EditRecipe::wb_caco3_changed(double val)  { set_brewing_salt("CaCO3", val, MISC_USES_MASH);   }
void EditRecipe::sp_cacl2_changed(double val)  { set_brewing_salt("CaCl2", val, MISC_USES_SPARGE); }
void EditRecipe::sp_caso4_changed(double val)  { set_brewing_salt("CaSO4", val, MISC_USES_SPARGE); }
void EditRecipe::sp_mgso4_changed(double val)  { set_brewing_salt("MgSO4", val, MISC_USES_SPARGE); }
void EditRecipe::sp_nacl_changed(double val)   { set_brewing_salt("NaCl", val, MISC_USES_SPARGE);  }
void EditRecipe::sp_mgcl2_changed(double val)  { set_brewing_salt("MgCl2", val, MISC_USES_SPARGE); }

mercurial