src/EditProductTab7.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 307
afd711e37f68
child 411
c78f8cf11849
permissions
-rw-r--r--

Init est_carb field for new products.

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

    qDebug() << "refreshMashs" << product->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);
    del_icon.addFile(QString::fromUtf8(":/icons/silk/delete.png"), QSize(), QIcon::Normal, QIcon::Off);
    ed_icon.addFile(QString::fromUtf8(":/icons/silk/pencil.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("SG"), tr("pH"), "", "", "", "" });

    ui->mashsTable->setColumnCount(16);
    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, 50);	/* SG		*/
    ui->mashsTable->setColumnWidth(11, 50);	/* pH		*/
    ui->mashsTable->setColumnWidth(12, 30);	/* Up button	*/
    ui->mashsTable->setColumnWidth(13, 30);	/* Down button	*/
    ui->mashsTable->setColumnWidth(14, 30);	/* Delete	*/
    ui->mashsTable->setColumnWidth(15, 30);	/* Edit		*/
    ui->mashsTable->setHorizontalHeaderLabels(labels);
    ui->mashsTable->verticalHeader()->hide();
    ui->mashsTable->setRowCount(product->mashs.size());

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

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

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

	item = new QTableWidgetItem(QString("%1 °C").arg(product->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(product->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(product->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(product->mashs.at(i).ramp_time, 1, 'f', 0, '0'));
        item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
        ui->mashsTable->setItem(i, 5, item);

	if (product->mashs.at(i).step_infuse_amount > 0) {
	    item = new QTableWidgetItem(QString("%1 L").arg(product->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(product->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(product->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(product->mashs.at(i).step_wg_ratio, 3, 'f', 2, '0'));
        item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
        ui->mashsTable->setItem(i, 9, item);

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

	item = new QTableWidgetItem(QString("%1").arg(product->mashs.at(i).step_ph, 3, 'f', 2, '0'));
        item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
        ui->mashsTable->setItem(i, 11, 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, 12, pWidget);
	} else {
	    ui->mashsTable->removeCellWidget(i, 12);
	}

	if (i < (product->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, 13, pWidget);
	} else {
	    ui->mashsTable->removeCellWidget(i, 13);
	}

	pWidget = new QWidget();
        QToolButton* btn_dele = new QToolButton();
        btn_dele->setObjectName(QString("%1").arg(i));  /* Send row with the button */
        btn_dele->setIcon(del_icon);
        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, 14, pWidget);

        pWidget = new QWidget();
        QToolButton* btn_edit = new QToolButton();
        btn_edit->setObjectName(QString("%1").arg(i));  /* Send row with the button */
        btn_edit->setIcon(ed_icon);
        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, 15, pWidget);
    }
}


double EditProduct::infusionVol(double step_infused, double step_mashkg, double infuse_temp, double step_temp, double last_temp)
{
    double a = last_temp * (product->eq_tun_weight * product->eq_tun_specific_heat + step_infused * SpecificHeatWater + step_mashkg * SpecificHeatMalt);
    double b = step_temp * (product->eq_tun_weight * product->eq_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 EditProduct::decoctionVol(double step_volume, double step_temp, double prev_temp)
{
    double a = (product->eq_tun_weight * product->eq_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 EditProduct::calcMash()
{
    double infused = 0, vol, a, b, temp;
    int    i, j, n;
    double lasttemp = 18.0;
    double graintemp = 18.0;
    double tuntemp = 18.0;

    product->mashs_time = 0;

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

	for (i = 0; i < product->mashs.size(); i++) {
	    if (product->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 = product->mashs.at(i).step_temp;
		    for (j = 0; j < n; j++) {
			a = product->mashs_kg * graintemp * SpecificHeatMalt + product->eq_tun_weight * tuntemp * product->eq_tun_specific_heat;
			b = product->mashs[i].step_temp *
			      (product->eq_tun_weight * product->eq_tun_specific_heat +
			       product->mashs.at(i).step_infuse_amount * SpecificHeatWater + 
			       product->mashs_kg * SpecificHeatMalt) -
			      SlakingHeat * product->mashs_kg;
			if (product->mashs.at(i).step_infuse_amount > 0) {
			    temp = (b - a) / (product->mashs.at(i).step_infuse_amount * SpecificHeatWater);
			} else {
			    temp = 99;
			}
			tuntemp += (temp - tuntemp) / 2;
			product->mashs[i].step_infuse_temp = round(temp * 1000000.0) / 1000000.0;
		    }
		    qDebug() << "  init infuse temp:" << product->mashs.at(i).step_infuse_temp;
		} else {
		    // Calculate amount of infusion water.
		    product->mashs[i].step_infuse_amount =
			    infusionVol(infused, product->mashs_kg, product->mashs.at(i).step_infuse_temp, product->mashs.at(i).step_temp, lasttemp);
		    qDebug() << i << "  vol:" << product->mashs.at(i).step_infuse_amount << "temp:" << product->mashs.at(i).step_infuse_temp;
		}
		infused += product->mashs.at(i).step_infuse_amount;
	    } else if (product->mashs.at(i).step_type == 1) { // Temperature
		if (i > 0)
		    product->mashs[i].step_infuse_amount = 0;
		product->mashs[i].step_infuse_temp = 0;
	    } else if (product->mashs.at(i).step_type == 2) { // Decoction
		product->mashs[i].step_infuse_amount = decoctionVol(infused, product->mashs.at(i).step_temp, lasttemp);
		product->mashs[i].step_infuse_temp = 99;
	    }
	    product->mashs[i].step_volume = infused;
	    //qDebug() << i << "  type:" << product->mashs.at(i).step_type << "volume:" << product->mashs.at(i).step_infuse_amount << "temp:" << product->mashs.at(i).step_infuse_temp;
	    lasttemp = product->mashs.at(i).step_temp;
	    product->mashs_time += product->mashs.at(i).step_time;
	    if (i > 0)
	    	product->mashs_time += product->mashs.at(i).ramp_time;
	    product->mashs[i].step_wg_ratio = round((infused / product->mashs_kg) * 1000000.0) / 1000000.0;
	}
    }

    ui->mash_volEdit->setValue(product->mashs_kg * MaltVolume + infused);
    /* Show the calculated total mash time. */
    ui->mash_timeEdit->setText(QString("%1:%2").arg(product->mashs_time / 60).arg(product->mashs_time % 60, 2, 'f', 0, '0'));

    /* Estimated needed sparge water corrected for the temperature. */
    product->brew_sparge_est =
	round((product->boil_size - infused + (product->mashs_kg * my_grain_absorbtion) + product->eq_lauter_deadspace) * 1.03 * 1000) / 1000;
    ui->brew_spargeestShow->setValue(product->brew_sparge_est);
}


void EditProduct::addMashRow_clicked()
{
    MashSteps newm;

    for (int i = 0; i < product->mashs.size(); i++) {
        if (product->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 (product->mashs.size()) {
	newm.step_volume = product->mashs.at(product->mashs.size() - 1).step_volume;
	newm.step_wg_ratio = product->mashs.at(product->mashs.size() - 1).step_wg_ratio;
	newm.step_type = product->mashs.at(product->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;

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


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

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

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

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


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

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

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


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

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

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


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


void EditProduct::step_type_changed(int val)
{
    qDebug() << "step_type_changed" << product->mashs_row << val;

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


void EditProduct::step_temp_changed(double val)
{
    qDebug() << "step_temp_changed" << product->mashs_row << val;
    product->mashs[product->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(product->mashs_row, 2, item);
    is_changed();
    emit refreshAll();
}


void EditProduct::end_temp_changed(double val)
{
    qDebug() << "end_temp_changed" << product->mashs_row << val;
    product->mashs[product->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(product->mashs_row, 3, item);
    is_changed();
    emit refreshAll();
}


void EditProduct::step_time_changed(double val)
{
    qDebug() << "step_time_changed" << product->mashs_row << val;
    product->mashs[product->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(product->mashs_row, 4, item);
    is_changed();
    emit refreshAll();
}


void EditProduct::ramp_time_changed(double val)
{
    qDebug() << "ramp_time_changed" << product->mashs_row << val;
    product->mashs[product->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(product->mashs_row, 5, item);
    is_changed();
    emit refreshAll();
}


void EditProduct::infuse_changed(double val)
{
    qDebug() << "infuse_changed" << product->mashs_row << val;
    product->mashs[product->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(product->mashs_row, 6, item);

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

    check_waters();
    is_changed();
    emit refreshAll();
}


void EditProduct::stepph_changed(double val)
{
    qDebug() << "stepph_changed" << product->mashs_row << val;

    if (product->mashs.at(product->mashs_row).step_ph == 0) {
	if (product->mashs_row == 0) {
	    /*
	     * Jump start at 5.0pH
	     */
	    product->mashs[product->mashs_row].step_ph = 5.0;
	} else {
	    /*
	     * Start with pH from previous step.
	     */
	    product->mashs[product->mashs_row].step_ph = product->mashs.at(product->mashs_row - 1).step_ph;
	}
	const QSignalBlocker blocker1(stepphEdit);
	stepphEdit->setValue(product->mashs.at(product->mashs_row).step_ph);
    } else {
	product->mashs[product->mashs_row].step_ph = val;
    }

    QTableWidgetItem *item = new QTableWidgetItem(QString("%1").arg(product->mashs.at(product->mashs_row).step_ph, 3, 'f', 2, '0'));
    item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
    ui->mashsTable->setItem(product->mashs_row, 11, item);
    is_changed();
}


void EditProduct::stepsg_changed(double val)
{
    qDebug() << "stepsg_changed" << product->mashs_row << val;

    if (product->mashs.at(product->mashs_row).step_sg == 0) {
	if (product->mashs_row > 0) {
	    /* Start with value from previous step. */
	    product->mashs[product->mashs_row].step_sg = product->mashs.at(product->mashs_row - 1).step_sg;
	} else {
	    /* Start at 1.001 */
	    product->mashs[product->mashs_row].step_sg = 1.001;
	}
	const QSignalBlocker blocker1(stepsgEdit);
	stepsgEdit->setValue(product->mashs.at(product->mashs_row).step_sg);
    } else {
	product->mashs[product->mashs_row].step_sg = val;
    }
    const QSignalBlocker blocker2(brixEdit);
    if (product->mashs.at(product->mashs_row).step_sg >= 1.000)
    	brixEdit->setValue(round(Utils::sg_to_brix(product->mashs.at(product->mashs_row).step_sg) * 10.0) / 10.0);
    else
	brixEdit->setValue(0);
    qDebug() << "brix" << brixEdit->value();

    QTableWidgetItem *item = new QTableWidgetItem(QString("%1").arg(product->mashs.at(product->mashs_row).step_sg, 4, 'f', 3, '0'));
    item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
    ui->mashsTable->setItem(product->mashs_row, 10, item);
    is_changed();
}


void EditProduct::stepbrix_changed(double val)
{
    qDebug() << "stepbrix_changed" << product->mashs_row << val << "SG" << Utils::brix_to_sg(val);

    if ((product->mashs.at(product->mashs_row).step_sg == 0) && (product->mashs_row > 0)) {
	/* If not the first step, and SG was not set, pickup previous step. */
	val = Utils::sg_to_brix(product->mashs.at(product->mashs_row - 1).step_sg);
	const QSignalBlocker blocker2(brixEdit);
	brixEdit->setValue(val);
    }
    product->mashs[product->mashs_row].step_sg = Utils::brix_to_sg(val);
    const QSignalBlocker blocker1(stepsgEdit);
    stepsgEdit->setValue(product->mashs.at(product->mashs_row).step_sg);

    QTableWidgetItem *item = new QTableWidgetItem(QString("%1").arg(product->mashs.at(product->mashs_row).step_sg, 4, 'f', 3, '0'));
    item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
    ui->mashsTable->setItem(product->mashs_row, 10, item);
    is_changed();
}


void EditProduct::editMashRow_clicked()
{
    QSqlQuery query;

    if (product->locked)
	return;

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

    QDialog* dialog = new QDialog(this);
    dialog->resize(738, 290);
    QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog);
    buttonBox->setObjectName(QString::fromUtf8("buttonBox"));
    buttonBox->setGeometry(QRect(30, 240, 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(product->mashs.at(product->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(product->mashs.at(product->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(product->mashs.at(product->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(product->mashs.at(product->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(product->mashs.at(product->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(product->mashs.at(product->mashs_row).ramp_time);

    QLabel *phLabel = new QLabel(dialog);
    phLabel->setObjectName(QString::fromUtf8("phLabel"));
    phLabel->setText(tr("Measured pH:"));
    phLabel->setGeometry(QRect(10, 130, 141, 20));
    phLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    stepphEdit = new QDoubleSpinBox(dialog);
    stepphEdit->setObjectName(QString::fromUtf8("phEdit"));
    stepphEdit->setGeometry(QRect(160, 130, 121, 24));
    stepphEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    stepphEdit->setDecimals(2);
    stepphEdit->setMaximum(14.0);
    stepphEdit->setAccelerated(true);
    stepphEdit->setSingleStep(0.1);
    stepphEdit->setValue(product->mashs.at(product->mashs_row).step_ph);

    /*
     * SG or Brix, both can be used to enter the SG.
     */
    QLabel *brixLabel = new QLabel(dialog);
    brixLabel->setObjectName(QString::fromUtf8("brixLabel"));
    brixLabel->setText(tr("Measured Brix:"));
    brixLabel->setGeometry(QRect(360, 130, 141, 20));
    brixLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    brixEdit = new QDoubleSpinBox(dialog);
    brixEdit->setObjectName(QString::fromUtf8("brixEdit"));
    brixEdit->setGeometry(QRect(510, 130, 121, 24));
    brixEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    brixEdit->setDecimals(1);
    brixEdit->setMaximum(32);
    brixEdit->setAccelerated(true);
    brixEdit->setSingleStep(0.1);

    QLabel *sgLabel = new QLabel(dialog);
    sgLabel->setObjectName(QString::fromUtf8("sgLabel"));
    sgLabel->setText(tr("Measured SG:"));
    sgLabel->setGeometry(QRect(360, 160, 141, 20));
    sgLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    stepsgEdit = new QDoubleSpinBox(dialog);
    stepsgEdit->setObjectName(QString::fromUtf8("sgEdit"));
    stepsgEdit->setGeometry(QRect(510, 160, 121, 24));
    stepsgEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    stepsgEdit->setDecimals(3);
    stepsgEdit->setMaximum(1.5);
    stepsgEdit->setAccelerated(true);
    stepsgEdit->setSingleStep(0.001);
    /* Make it smart, if zero then start with the previous step value. */
    stepsgEdit->setValue(product->mashs.at(product->mashs_row).step_sg);
    brixEdit->setValue(Utils::sg_to_brix(product->mashs.at(product->mashs_row).step_sg));

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

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

    connect(stepnameEdit, &QLineEdit::textEdited, this, &EditProduct::step_name_changed);
    connect(typeEdit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &EditProduct::step_type_changed);
    connect(steptempEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::step_temp_changed);
    connect(endtempEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::end_temp_changed);
    connect(steptimeEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::step_time_changed);
    connect(ramptimeEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::ramp_time_changed);
    connect(stepivolEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::infuse_changed);
    connect(stepphEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::stepph_changed);
    connect(stepsgEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::stepsg_changed);
    connect(brixEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::stepbrix_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";
        product->mashs[product->mashs_row] = backup;
	/* Rollback water volumes too */
	double volume = 0;
    	for (int i = 0; i < product->mashs.size(); i++) {
            if (product->mashs.at(i).step_type == 0) {
            	volume += product->mashs.at(i).step_infuse_amount;
            }
            product->mashs[i].step_volume = volume;
    	}
    	product->w1_amount = volume - product->w2_amount;
    	product->wg_amount = volume;
    	ui->w1_volEdit->setValue(product->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(stepphEdit, nullptr, nullptr, nullptr);
    disconnect(stepsgEdit, nullptr, nullptr, nullptr);
    disconnect(brixEdit, nullptr, nullptr, nullptr);
    disconnect(buttonBox, nullptr, nullptr, nullptr);

    emit refreshAll();
}


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


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

    product->mash_name = query.value(0).toString();
    ui->mash_nameEdit->setText(product->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) {
	    qWarning() << "Parse error: " << parseError.errorString() << "at" << parseError.offset;
	} else {
	    /*
	     * Got the json data in the steps array, replace the product steps.
	     */
	    double infuse = 0;
	    if (product->mashs.size()) {
	    	infuse = product->mashs.at(0).step_infuse_amount;
	    	product->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;
		    product->mashs.append(m);
		}
	    }
	    if (product->mashs.at(0).step_type == 0)
		product->mashs[0].step_infuse_amount = infuse;	// Restore saved initial infusion
	}
    }
    is_changed();
    emit refreshAll();
}

mercurial