src/EditProductTab7.cpp

Fri, 20 Jan 2023 16:44:08 +0100

author
Michiel Broek <mbroek@mbse.eu>
date
Fri, 20 Jan 2023 16:44:08 +0100
changeset 467
c5f6f3f1b714
parent 464
1fed3ff9a64e
child 525
e6a4a3a29584
permissions
-rw-r--r--

Added more buttons to the images tab. Load images from the database and display thumbnails added. Added support for jpeg files. Rename pictures in the database to .png. Added temporary images_list, images_count and images_current variables to the product record.

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

    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, 110);	/* 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, 60);	/* SG		*/
    ui->mashsTable->setColumnWidth(11, 60);	/* 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 && product->stage <= PROD_STAGE_BREW) {
	    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) && product->stage <= PROD_STAGE_BREW) {
	    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);
	}

	if (product->stage <= PROD_STAGE_BREW) {
	    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);
	} else {
	    ui->mashsTable->removeCellWidget(i, 14);
	    ui->mashsTable->removeCellWidget(i, 15);
	}
    }
}


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