src/EditRecipeTab6.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 301
fe6346211b5b
child 452
c4c5d02131be
permissions
-rw-r--r--

Init est_carb field for new products.

/**
 * EditRecipe.cpp is part of bmsapp.
 *
 * tab 6, mash.
 *
 * 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::refreshMashs()
{
    QString w;
    QWidget* pWidget;
    QHBoxLayout* pLayout;
    QTableWidgetItem *item;
    QIcon down_icon, up_icon;

    qDebug() << "refreshMashs" << recipe->mashs.size();

    down_icon.addFile(QString::fromUtf8(":/icons/silk/bullet_arrow_down.png"), QSize(), QIcon::Normal, QIcon::Off);
    up_icon.addFile(QString::fromUtf8(":/icons/silk/bullet_arrow_up.png"), QSize(), QIcon::Normal, QIcon::Off);

    const QStringList labels({tr("Step name"), tr("Type"), tr("Start"), tr("End"), tr("Rest"), tr("Ramp"),
			      tr("Inf/dec"), tr("Inf/dec"), tr("Volume"), tr("W/G ratio"), "", "", tr("Delete"), tr("Edit") });

    ui->mashsTable->setColumnCount(14);
    ui->mashsTable->setColumnWidth(0, 189);	/* Step name	*/
    ui->mashsTable->setColumnWidth(1, 100);	/* Type		*/
    ui->mashsTable->setColumnWidth(2,  70);	/* Start temp	*/
    ui->mashsTable->setColumnWidth(3,  70);	/* End temp	*/
    ui->mashsTable->setColumnWidth(4,  70);	/* Rest time	*/
    ui->mashsTable->setColumnWidth(5,  70);	/* Ramp time	*/
    ui->mashsTable->setColumnWidth(6,  70);	/* Infusion vol	*/
    ui->mashsTable->setColumnWidth(7,  70);     /* Infusion tmp	*/
    ui->mashsTable->setColumnWidth(8,  70);	/* Volume	*/
    ui->mashsTable->setColumnWidth(9,  80);	/* W/G ratio	*/
    ui->mashsTable->setColumnWidth(10, 30);	/* Up button	*/
    ui->mashsTable->setColumnWidth(11, 30);	/* Down button	*/
    ui->mashsTable->setColumnWidth(12, 80);	/* Delete	*/
    ui->mashsTable->setColumnWidth(13, 80);	/* Edit		*/
    ui->mashsTable->setHorizontalHeaderLabels(labels);
    ui->mashsTable->verticalHeader()->hide();
    ui->mashsTable->setRowCount(recipe->mashs.size());

    for (int i = 0; i < recipe->mashs.size(); i++) {

	ui->mashsTable->setItem(i, 0, new QTableWidgetItem(recipe->mashs.at(i).step_name));

	item = new QTableWidgetItem(QCoreApplication::translate("StepType", g_step_types[recipe->mashs.at(i).step_type]));
        item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter);
        ui->mashsTable->setItem(i, 1, item);

	item = new QTableWidgetItem(QString("%1 °C").arg(recipe->mashs.at(i).step_temp, 2, 'f', 1, '0'));
	item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
	ui->mashsTable->setItem(i, 2, item);

	item = new QTableWidgetItem(QString("%1 °C").arg(recipe->mashs.at(i).end_temp, 2, 'f', 1, '0'));
        item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
        ui->mashsTable->setItem(i, 3, item);

	item = new QTableWidgetItem(QString("%1 min").arg(recipe->mashs.at(i).step_time, 1, 'f', 0, '0'));
        item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
        ui->mashsTable->setItem(i, 4, item);

	item = new QTableWidgetItem(QString("%1 min").arg(recipe->mashs.at(i).ramp_time, 1, 'f', 0, '0'));
        item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
        ui->mashsTable->setItem(i, 5, item);

	if (recipe->mashs.at(i).step_infuse_amount > 0) {
	    item = new QTableWidgetItem(QString("%1 L").arg(recipe->mashs.at(i).step_infuse_amount, 2, 'f', 1, '0'));
            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
            ui->mashsTable->setItem(i, 6, item);
	    item = new QTableWidgetItem(QString("%1 °C").arg(recipe->mashs.at(i).step_infuse_temp, 3, 'f', 2, '0'));
            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
            ui->mashsTable->setItem(i, 7, item);
	} else {
	    ui->mashsTable->removeCellWidget(i, 6);
	    ui->mashsTable->removeCellWidget(i, 7);
	}

	item = new QTableWidgetItem(QString("%1 L").arg(recipe->mashs.at(i).step_volume, 2, 'f', 1, '0'));
        item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
        ui->mashsTable->setItem(i, 8, item);

	item = new QTableWidgetItem(QString("%1 L/kg").arg(recipe->mashs.at(i).step_wg_ratio, 3, 'f', 2, '0'));
        item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
        ui->mashsTable->setItem(i, 9, item);

	if (i > 0) {
	    pWidget = new QWidget();
            QPushButton* btn_up = new QPushButton();
            btn_up->setObjectName(QString("%1").arg(i));  /* Send row with the button */
            btn_up->setIcon(up_icon);
            connect(btn_up, SIGNAL(clicked()), this, SLOT(upMashRow_clicked()));
            pLayout = new QHBoxLayout(pWidget);
            pLayout->addWidget(btn_up);
            pLayout->setContentsMargins(5, 0, 5, 0);
            pWidget->setLayout(pLayout);
            ui->mashsTable->setCellWidget(i, 10, pWidget);
	} else {
	    ui->mashsTable->removeCellWidget(i, 10);
	}

	if (i < (recipe->mashs.size() - 1)) {
	    pWidget = new QWidget();
            QPushButton* btn_down = new QPushButton();
            btn_down->setObjectName(QString("%1").arg(i));  /* Send row with the button */
	    btn_down->setIcon(down_icon);
            connect(btn_down, SIGNAL(clicked()), this, SLOT(downMashRow_clicked()));
            pLayout = new QHBoxLayout(pWidget);
            pLayout->addWidget(btn_down);
            pLayout->setContentsMargins(5, 0, 5, 0);
            pWidget->setLayout(pLayout);
            ui->mashsTable->setCellWidget(i, 11, pWidget);
	} else {
	    ui->mashsTable->removeCellWidget(i, 11);
	}

	pWidget = new QWidget();
        QPushButton* btn_dele = new QPushButton();
        btn_dele->setObjectName(QString("%1").arg(i));  /* Send row with the button */
        btn_dele->setText(tr("Delete"));
        connect(btn_dele, SIGNAL(clicked()), this, SLOT(deleteMashRow_clicked()));
        pLayout = new QHBoxLayout(pWidget);
        pLayout->addWidget(btn_dele);
        pLayout->setContentsMargins(5, 0, 5, 0);
        pWidget->setLayout(pLayout);
        ui->mashsTable->setCellWidget(i, 12, pWidget);

        pWidget = new QWidget();
        QPushButton* btn_edit = new QPushButton();
        btn_edit->setObjectName(QString("%1").arg(i));  /* Send row with the button */
        btn_edit->setText(tr("Edit"));
        connect(btn_edit, SIGNAL(clicked()), this, SLOT(editMashRow_clicked()));
        pLayout = new QHBoxLayout(pWidget);
        pLayout->addWidget(btn_edit);
        pLayout->setContentsMargins(5, 0, 5, 0);
        pWidget->setLayout(pLayout);
        ui->mashsTable->setCellWidget(i, 13, pWidget);
    }
}


double EditRecipe::infusionVol(double step_infused, double step_mashkg, double infuse_temp, double step_temp, double last_temp)
{
    double a = last_temp * (equip_tun_weight * equip_tun_specific_heat + step_infused * SpecificHeatWater + step_mashkg * SpecificHeatMalt);
    double b = step_temp * (equip_tun_weight * equip_tun_specific_heat + step_infused * SpecificHeatWater + step_mashkg * SpecificHeatMalt);
    double vol = round(((b - a) / ((infuse_temp - step_temp) * SpecificHeatWater)) * 100.0) / 100.0;

    if (vol < 0)
	vol = 0;
    qDebug() << "  infusionVol(" << step_infused << "," << step_mashkg << "," << infuse_temp <<"," << step_temp << "," << last_temp << "):" << vol;
    return vol;
}


double EditRecipe::decoctionVol(double step_volume, double step_temp, double prev_temp)
{
    double a = (equip_tun_weight * equip_tun_specific_heat + step_volume * SpecificHeatWater) * (step_temp - prev_temp);
    double b = SpecificHeatWater * (99 - step_temp);
    double vol = 0;

    if (b > 0)
	vol = round((a / b) * 1000000.0) / 1000000.0;
    qDebug() << "  decoctionVol(" << step_volume << "," << step_temp << "," << prev_temp << "):" << vol;
    return vol;
}


void EditRecipe::calcMash()
{
    double infused = 0, vol, a, b, temp;
    int    i, j, n;
    double lasttemp = 18.0;
    double graintemp = 18.0;
    double tuntemp = 18.0;

    recipe->mashs_time = 0;

    if (recipe->mashs.size() && recipe->mashs_kg > 0) {
	qDebug() << "calcMash()";

	for (i = 0; i < recipe->mashs.size(); i++) {
	    if (recipe->mashs.at(i).step_type == 0) { // Infusion
		if (i == 0) {
		    // First mash step, temperature from the mashtun and malt.
		    n = 20; // tun is preheated.
		    tuntemp = recipe->mashs.at(i).step_temp;
		    for (j = 0; j < n; j++) {
			a = recipe->mashs_kg * graintemp * SpecificHeatMalt + equip_tun_weight * tuntemp * equip_tun_specific_heat;
			b = recipe->mashs[i].step_temp *
			      (equip_tun_weight * equip_tun_specific_heat +
			       recipe->mashs.at(i).step_infuse_amount * SpecificHeatWater + 
			       recipe->mashs_kg * SpecificHeatMalt) -
			      SlakingHeat * recipe->mashs_kg;
			if (recipe->mashs.at(i).step_infuse_amount > 0) {
			    temp = (b - a) / (recipe->mashs.at(i).step_infuse_amount * SpecificHeatWater);
			} else {
			    temp = 99;
			}
			tuntemp += (temp - tuntemp) / 2;
			recipe->mashs[i].step_infuse_temp = round(temp * 1000000.0) / 1000000.0;
		    }
		    qDebug() << "  init infuse temp:" << recipe->mashs.at(i).step_infuse_temp;
		} else {
		    // Calculate amount of infusion water.
		    recipe->mashs[i].step_infuse_amount =
			    infusionVol(infused, recipe->mashs_kg, recipe->mashs.at(i).step_infuse_temp, recipe->mashs.at(i).step_temp, lasttemp);
		    qDebug() << i << "  vol:" << recipe->mashs.at(i).step_infuse_amount << "temp:" << recipe->mashs.at(i).step_infuse_temp;
		}
		infused += recipe->mashs.at(i).step_infuse_amount;
	    } else if (recipe->mashs.at(i).step_type == 1) { // Temperature
		if (i > 0)
		    recipe->mashs[i].step_infuse_amount = 0;
		recipe->mashs[i].step_infuse_temp = 0;
	    } else if (recipe->mashs.at(i).step_type == 2) { // Decoction
		recipe->mashs[i].step_infuse_amount = decoctionVol(infused, recipe->mashs.at(i).step_temp, lasttemp);
		recipe->mashs[i].step_infuse_temp = 99;
	    }
	    recipe->mashs[i].step_volume = infused;
	    //qDebug() << i << "  type:" << recipe->mashs.at(i).step_type << "volume:" << recipe->mashs.at(i).step_infuse_amount << "temp:" << recipe->mashs.at(i).step_infuse_temp;
	    lasttemp = recipe->mashs.at(i).step_temp;
	    recipe->mashs_time += recipe->mashs.at(i).step_time;
	    if (i > 0)
	    	recipe->mashs_time += recipe->mashs.at(i).ramp_time;
	    recipe->mashs[i].step_wg_ratio = round((infused / recipe->mashs_kg) * 1000000.0) / 1000000.0;
	}
    }

    /* Show the calculated total mash time. */
    ui->mash_timeEdit->setText(QString("%1:%2").arg(recipe->mashs_time / 60).arg(recipe->mashs_time % 60, 2, 'f', 0, '0'));
}


void EditRecipe::addMashRow_clicked()
{
    MashSteps newm;

    for (int i = 0; i < recipe->mashs.size(); i++) {
        if (recipe->mashs.at(i).step_time == 0)
            return;     // Add only one at a time.
    }

    newm.step_name = "Name me";
    newm.step_temp = newm.end_temp = 67.0;
    newm.step_time = 20;
    newm.ramp_time = 10;
    if (recipe->mashs.size()) {
	newm.step_volume = recipe->mashs.at(recipe->mashs.size() - 1).step_volume;
	newm.step_wg_ratio = recipe->mashs.at(recipe->mashs.size() - 1).step_wg_ratio;
	newm.step_type = recipe->mashs.at(recipe->mashs.size() - 1).step_type;
    } else {
	newm.step_volume = 0;
	newm.step_wg_ratio = 0;
	newm.step_type = 1;
    }
    newm.step_infuse_amount = newm.step_infuse_temp = 0;

    recipe->mashs.append(newm);
    is_changed();
    emit refreshAll();
}


void EditRecipe::deleteMashRow_clicked()
{
    if (recipe->locked || recipe->mashs.size() < 1)
	return;

    QPushButton *pb = qobject_cast<QPushButton *>(QObject::sender());
    int row = pb->objectName().toInt();
    qDebug() << "Delete mash row" << row << recipe->mashs.size();

    int rc = QMessageBox::warning(this, tr("Delete mash step"), tr("Delete %1").arg(recipe->mashs.at(row).step_name),
                    QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
    if (rc == QMessageBox::No)
        return;

    recipe->mashs.removeAt(row);
    is_changed();
    emit refreshAll();
}


void EditRecipe::upMashRow_clicked()
{
    if (recipe->locked || recipe->mashs.size() < 1)
        return;

    QPushButton *pb = qobject_cast<QPushButton *>(QObject::sender());
    int row = pb->objectName().toInt();
    qDebug() << "Move up mash row" << row << recipe->mashs.size();

    MashSteps temp;
    temp = recipe->mashs[row - 1];
    recipe->mashs[row - 1] = recipe->mashs[row];
    recipe->mashs[row] = temp;
    is_changed();
    emit refreshAll();
}


void EditRecipe::downMashRow_clicked()
{
    if (recipe->locked || recipe->mashs.size() < 1)
        return;

    QPushButton *pb = qobject_cast<QPushButton *>(QObject::sender());
    int row = pb->objectName().toInt();
    qDebug() << "Move down mash row" << row << recipe->mashs.size();

    MashSteps temp;
    temp = recipe->mashs[row + 1];
    recipe->mashs[row + 1] = recipe->mashs[row];
    recipe->mashs[row] = temp;
    is_changed();
    emit refreshAll();
}


void EditRecipe::step_name_changed(QString val)
{
    recipe->mashs[recipe->mashs_row].step_name = val;
    ui->mashsTable->setItem(recipe->mashs_row, 0, new QTableWidgetItem(val));
    is_changed();
}


void EditRecipe::step_type_changed(int val)
{
    qDebug() << "step_type_changed" << recipe->mashs_row << val;

    recipe->mashs[recipe->mashs_row].step_type = val;
    ivolLabel->setVisible(recipe->mashs.at(recipe->mashs_row).step_type == 0);
    stepivolEdit->setVisible(recipe->mashs.at(recipe->mashs_row).step_type == 0);
    itmpLabel->setVisible(recipe->mashs.at(recipe->mashs_row).step_type == 0);
    stepitmpEdit->setVisible(recipe->mashs.at(recipe->mashs_row).step_type == 0);
    is_changed();
    emit refreshAll();
}


void EditRecipe::step_temp_changed(double val)
{
    qDebug() << "step_temp_changed" << recipe->mashs_row << val;
    recipe->mashs[recipe->mashs_row].step_temp = val;
    QTableWidgetItem *item = new QTableWidgetItem(QString("%1 °C").arg(val, 2, 'f', 1, '0'));
    item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
    ui->mashsTable->setItem(recipe->mashs_row, 2, item);
    is_changed();
    emit refreshAll();
}


void EditRecipe::end_temp_changed(double val)
{
    qDebug() << "end_temp_changed" << recipe->mashs_row << val;
    recipe->mashs[recipe->mashs_row].end_temp = val;
    QTableWidgetItem *item = new QTableWidgetItem(QString("%1 °C").arg(val, 2, 'f', 1, '0'));
    item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
    ui->mashsTable->setItem(recipe->mashs_row, 3, item);
    is_changed();
    emit refreshAll();
}


void EditRecipe::step_time_changed(double val)
{
    qDebug() << "step_time_changed" << recipe->mashs_row << val;
    recipe->mashs[recipe->mashs_row].step_time = val;
    QTableWidgetItem *item = new QTableWidgetItem(QString("%1 min").arg(val, 1, 'f', 0, '0'));
    item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
    ui->mashsTable->setItem(recipe->mashs_row, 4, item);
    is_changed();
    emit refreshAll();
}


void EditRecipe::ramp_time_changed(double val)
{
    qDebug() << "ramp_time_changed" << recipe->mashs_row << val;
    recipe->mashs[recipe->mashs_row].ramp_time = val;
    QTableWidgetItem *item = new QTableWidgetItem(QString("%1 min").arg(val, 1, 'f', 0, '0'));
    item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
    ui->mashsTable->setItem(recipe->mashs_row, 5, item);
    is_changed();
    emit refreshAll();
}


void EditRecipe::infuse_changed(double val)
{
    qDebug() << "infuse_changed" << recipe->mashs_row << val;
    recipe->mashs[recipe->mashs_row].step_infuse_amount = val;
    QTableWidgetItem *item = new QTableWidgetItem(QString("%1 L").arg(val, 1, 'f', 0, '0'));
    item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
    ui->mashsTable->setItem(recipe->mashs_row, 6, item);

    /*
     * Recalculate water volumes
     */
    double volume = 0;
    for (int i = 0; i < recipe->mashs.size(); i++) {
	if (recipe->mashs.at(i).step_type == 0) {
	    volume += recipe->mashs.at(i).step_infuse_amount;
	}
	recipe->mashs[i].step_volume = volume;
    }
    recipe->w1_amount = volume - recipe->w2_amount;
    recipe->wg_amount = volume;
    ui->w1_volEdit->setValue(recipe->w1_amount);

    is_changed();
    emit refreshAll();
}


void EditRecipe::editMashRow_clicked()
{
    QSqlQuery query;

    if (recipe->locked)
	return;

    QPushButton *pb = qobject_cast<QPushButton *>(QObject::sender());
    recipe->mashs_row = pb->objectName().toInt();
    qDebug() << "Edit mash row" << recipe->mashs_row;
    MashSteps backup = recipe->mashs.at(recipe->mashs_row);

    QDialog* dialog = new QDialog(this);
    dialog->resize(738, 230);
    QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog);
    buttonBox->setObjectName(QString::fromUtf8("buttonBox"));
    buttonBox->setGeometry(QRect(30, 180, 671, 32));
    buttonBox->setLayoutDirection(Qt::LeftToRight);
    buttonBox->setOrientation(Qt::Horizontal);
    buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
    buttonBox->setCenterButtons(true);

    QLabel *nameLabel = new QLabel(dialog);
    nameLabel->setObjectName(QString::fromUtf8("nameLabel"));
    nameLabel->setText(tr("Step name:"));
    nameLabel->setGeometry(QRect(10, 10, 141, 20));
    nameLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    stepnameEdit = new QLineEdit(dialog);
    stepnameEdit->setObjectName(QString::fromUtf8("stepnameEdit"));
    stepnameEdit->setText(recipe->mashs.at(recipe->mashs_row).step_name);
    stepnameEdit->setGeometry(QRect(160, 10, 511, 23));

    QLabel *typeLabel = new QLabel(dialog);
    typeLabel->setObjectName(QString::fromUtf8("typeLabel"));
    typeLabel->setText(tr("Step type:"));
    typeLabel->setGeometry(QRect(10, 40, 141, 20));
    typeLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    QComboBox *typeEdit = new QComboBox(dialog);
    typeEdit->setObjectName(QString::fromUtf8("typeEdit"));
    typeEdit->setGeometry(QRect(160, 40, 161, 23));
    typeEdit->addItem(tr("Infusion"));
    typeEdit->addItem(tr("Temperature"));
    typeEdit->addItem(tr("Decoction"));
    typeEdit->setCurrentIndex(recipe->mashs.at(recipe->mashs_row).step_type);

    QLabel *tempLabel = new QLabel(dialog);
    tempLabel->setObjectName(QString::fromUtf8("tempLabel"));
    tempLabel->setText(tr("Step start temp:"));
    tempLabel->setGeometry(QRect(10, 70, 141, 20));
    tempLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    steptempEdit = new QDoubleSpinBox(dialog);
    steptempEdit->setObjectName(QString::fromUtf8("steptempEdit"));
    steptempEdit->setGeometry(QRect(160, 70, 121, 24));
    steptempEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    steptempEdit->setDecimals(1);
    steptempEdit->setValue(recipe->mashs.at(recipe->mashs_row).step_temp);

    QLabel *endLabel = new QLabel(dialog);
    endLabel->setObjectName(QString::fromUtf8("endLabel"));
    endLabel->setText(tr("Step end temp:"));
    endLabel->setGeometry(QRect(360, 70, 141, 20));
    endLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    endtempEdit = new QDoubleSpinBox(dialog);
    endtempEdit->setObjectName(QString::fromUtf8("endtempEdit"));
    endtempEdit->setGeometry(QRect(510, 70, 121, 24));
    endtempEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    endtempEdit->setDecimals(1);
    endtempEdit->setValue(recipe->mashs.at(recipe->mashs_row).end_temp);

    QLabel *timeLabel = new QLabel(dialog);
    timeLabel->setObjectName(QString::fromUtf8("timeLabel"));
    timeLabel->setText(tr("Step rest time:"));
    timeLabel->setGeometry(QRect(10, 100, 141, 20));
    timeLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    steptimeEdit = new QDoubleSpinBox(dialog);
    steptimeEdit->setObjectName(QString::fromUtf8("steptimeEdit"));
    steptimeEdit->setGeometry(QRect(160, 100, 121, 24));
    steptimeEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    steptimeEdit->setDecimals(0);
    steptimeEdit->setValue(recipe->mashs.at(recipe->mashs_row).step_time);

    QLabel *rampLabel = new QLabel(dialog);
    rampLabel->setObjectName(QString::fromUtf8("rampLabel"));
    rampLabel->setText(tr("Step ramp time:"));
    rampLabel->setGeometry(QRect(360, 100, 141, 20));
    rampLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    ramptimeEdit = new QDoubleSpinBox(dialog);
    ramptimeEdit->setObjectName(QString::fromUtf8("ramptimeEdit"));
    ramptimeEdit->setGeometry(QRect(510, 100, 121, 24));
    ramptimeEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    ramptimeEdit->setDecimals(0);
    ramptimeEdit->setValue(recipe->mashs.at(recipe->mashs_row).ramp_time);

    /*
     * Only used for Infusion steps.
     */
    ivolLabel = new QLabel(dialog);
    ivolLabel->setObjectName(QString::fromUtf8("ivolLabel"));
    ivolLabel->setText(tr("Infusion volume:"));
    ivolLabel->setGeometry(QRect(10, 130, 141, 20));
    ivolLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    ivolLabel->setVisible(recipe->mashs.at(recipe->mashs_row).step_type == 0);
    stepivolEdit = new QDoubleSpinBox(dialog);
    stepivolEdit->setObjectName(QString::fromUtf8("stepivolEdit"));
    stepivolEdit->setGeometry(QRect(160, 130, 121, 24));
    stepivolEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    stepivolEdit->setVisible(recipe->mashs.at(recipe->mashs_row).step_type == 0);
    stepivolEdit->setDecimals(1);
    stepivolEdit->setAccelerated(true);
    stepivolEdit->setMaximum(100000.0);
    stepivolEdit->setSingleStep(0.5);
    stepivolEdit->setValue(recipe->mashs.at(recipe->mashs_row).step_infuse_amount);

    itmpLabel = new QLabel(dialog);
    itmpLabel->setObjectName(QString::fromUtf8("itmpLabel"));
    itmpLabel->setText(tr("Infusion Temperature:"));
    itmpLabel->setGeometry(QRect(360, 130, 141, 20));
    itmpLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    itmpLabel->setVisible(recipe->mashs.at(recipe->mashs_row).step_type == 0);
    stepitmpEdit = new QDoubleSpinBox(dialog);
    stepitmpEdit->setObjectName(QString::fromUtf8("stepitmpEdit"));
    stepitmpEdit->setGeometry(QRect(510, 130, 121, 24));
    stepitmpEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    stepitmpEdit->setVisible(recipe->mashs.at(recipe->mashs_row).step_type == 0);
    stepitmpEdit->setDecimals(1);
    stepitmpEdit->setReadOnly(true);
    stepitmpEdit->setButtonSymbols(QAbstractSpinBox::NoButtons);
    stepitmpEdit->setValue(recipe->mashs.at(recipe->mashs_row).step_infuse_temp);

    connect(stepnameEdit, &QLineEdit::textEdited, this, &EditRecipe::step_name_changed);
    connect(typeEdit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &EditRecipe::step_type_changed);
    connect(steptempEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditRecipe::step_temp_changed);
    connect(endtempEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditRecipe::end_temp_changed);
    connect(steptimeEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditRecipe::step_time_changed);
    connect(ramptimeEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditRecipe::ramp_time_changed);
    connect(stepivolEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditRecipe::infuse_changed);
    connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject()));
    connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept()));

    dialog->setModal(true);
    dialog->exec();
    if (dialog->result() == QDialog::Rejected) {
        qDebug() << "reject and rollback";
        recipe->mashs[recipe->mashs_row] = backup;
	/* Rollback water volumes too */
	double volume = 0;
    	for (int i = 0; i < recipe->mashs.size(); i++) {
            if (recipe->mashs.at(i).step_type == 0) {
            	volume += recipe->mashs.at(i).step_infuse_amount;
            }
            recipe->mashs[i].step_volume = volume;
    	}
    	recipe->w1_amount = volume - recipe->w2_amount;
    	recipe->wg_amount = volume;
    	ui->w1_volEdit->setValue(recipe->w1_amount);
    }

    disconnect(stepnameEdit, nullptr, nullptr, nullptr);
    disconnect(steptempEdit, nullptr, nullptr, nullptr);
    disconnect(endtempEdit, nullptr, nullptr, nullptr);
    disconnect(steptimeEdit, nullptr, nullptr, nullptr);
    disconnect(ramptimeEdit, nullptr, nullptr, nullptr);
    disconnect(stepivolEdit, nullptr, nullptr, nullptr);
    disconnect(buttonBox, nullptr, nullptr, nullptr);

    emit refreshAll();
}


void EditRecipe::mash_name_changed(QString name)
{
    qDebug() << "mash_name_changed" << name;
    recipe->mash_name = name;
    is_changed();
}


void EditRecipe::mash_select_changed(int val)
{
    QSqlQuery query;
    int  i;

    qDebug() << "mash_select_changed" << val;

    const QSignalBlocker blocker1(ui->mash_nameEdit);
    query.prepare("SELECT name,steps FROM profile_mash ORDER BY name");
    query.exec();
    query.first();
    for (i = 0; i < (val -1); i++) {
	query.next();
    }

    recipe->mash_name = query.value(0).toString();
    ui->mash_nameEdit->setText(recipe->mash_name);

    QJsonParseError parseError;
    const auto& json = query.value(1).toString();
    if (!json.trimmed().isEmpty()) {
	const auto& formattedJson = QString("%1").arg(json);
	QJsonDocument newsteps = QJsonDocument::fromJson(formattedJson.toUtf8(), &parseError);

	if (parseError.error != QJsonParseError::NoError) {
	    qDebug() << "Parse error: " << parseError.errorString() << "at" << parseError.offset;
	} else {
	    /*
	     * Got the json data in the steps array, replace the recipe steps.
	     */
	    double infuse = 0;
	    if (recipe->mashs.size()) {
	    	infuse = recipe->mashs.at(0).step_infuse_amount;
	    	recipe->mashs.clear();
	    	ui->mashsTable->clear();
	    }
	    if (newsteps.isArray()) {
		for (i = 0; i < newsteps.array().size(); i++) {
		    QJsonObject obj = newsteps.array().at(i).toObject();
		    MashSteps m;
		    m.step_name = obj["step_name"].toString();
		    if (obj["step_type"].isString())
			m.step_type = QString(obj["step_type"].toString()).toInt();
		    else
		        m.step_type = obj["step_type"].toInt();
		    m.step_volume = 0;
		    m.step_infuse_amount = 0;
		    m.step_infuse_temp = 0;
		    if (obj["step_temp"].isString())
			m.step_temp = QString(obj["step_temp"].toString()).toDouble();
		    else
		        m.step_temp = obj["step_temp"].toDouble();
		    if (obj["step_time"].isString())
			m.step_time = QString(obj["step_time"].toString()).toDouble();
		    else
		        m.step_time = obj["step_time"].toDouble();
		    if (obj["ramp_time"].isString())
			m.ramp_time = QString(obj["ramp_time"].toString()).toDouble();
		    else
		        m.ramp_time = obj["ramp_time"].toDouble();
		    if (obj["end_temp"].isString())
			m.end_temp = QString(obj["end_temp"].toString()).toDouble();
		    else
		        m.end_temp = obj["end_temp"].toDouble();
		    m.step_wg_ratio = 0;
		    recipe->mashs.append(m);
		}
	    }
	    if (recipe->mashs.at(0).step_type == 0)
		recipe->mashs[0].step_infuse_amount = infuse;	// Restore saved initial infusion
	}
    }
    is_changed();
    emit refreshAll();
}

mercurial