src/EditRecipeTab6.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 454
2dfead81c72f
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.

/**
 * 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;

    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->setAccelerated(true);
    steptempEdit->setSingleStep(0.5);
    steptempEdit->setMaximum(82);
    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->setAccelerated(true);
    endtempEdit->setSingleStep(0.5);
    endtempEdit->setMaximum(82);
    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->setAccelerated(true);
    steptimeEdit->setMaximum(240.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