--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/EditProductTab3.cpp Thu Apr 28 22:49:13 2022 +0200 @@ -0,0 +1,967 @@ +/** + * EditProduct.cpp is part of bmsapp. + * + * Tab 3, fermentables + * + * 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/>. + */ + + + +bool EditProduct::ferment_sort_test(const Fermentables &D1, const Fermentables &D2) +{ + if (D1.f_added > D2.f_added) + return false; + if (D1.f_added < D2.f_added) + return true; + return (D1.f_amount >= D2.f_amount) && (D1.f_color < D2.f_color); +} + + +void EditProduct::to100Fermentables(int row) +{ + if (product->fermentables.at(row).f_adjust_to_total_100) { + QWidget *pWidget = new QWidget(); + QLabel *label = new QLabel; + label->setPixmap(QPixmap(":icons/silk/tick.png")); + QHBoxLayout *pLayout = new QHBoxLayout(pWidget); + pLayout->addWidget(label); + pLayout->setAlignment(Qt::AlignCenter); + pLayout->setContentsMargins(0, 0, 0, 0); + pWidget->setLayout(pLayout); + ui->fermentablesTable->setCellWidget(row, 9, pWidget); + } else { + ui->fermentablesTable->removeCellWidget(row, 9); + } +} + + +void EditProduct::refreshFermentables() +{ + QString w; + QWidget* pWidget; + QHBoxLayout* pLayout; + QTableWidgetItem *item; + + qDebug() << "refreshFermentables" << product->fermentables.size(); + std::sort(product->fermentables.begin(), product->fermentables.end(), ferment_sort_test); + + const QStringList labels({tr("Supplier"), tr("Fermentable"), tr("EBC"), tr("Type"), tr("Graintype"), tr("When"), tr("Yield"), + tr("Amount"), tr("Procent"), tr("100%"), tr("Delete"), tr("Edit") }); + ui->fermentablesTable->setColumnCount(12); + ui->fermentablesTable->setColumnWidth(0, 150); /* Supplier */ + ui->fermentablesTable->setColumnWidth(1, 225); /* Fermentable */ + ui->fermentablesTable->setColumnWidth(2, 50); /* Color */ + ui->fermentablesTable->setColumnWidth(3, 75); /* Type */ + ui->fermentablesTable->setColumnWidth(4, 75); /* Graintype */ + ui->fermentablesTable->setColumnWidth(5, 82); /* Added */ + ui->fermentablesTable->setColumnWidth(6, 60); /* Yield */ + ui->fermentablesTable->setColumnWidth(7, 90); /* Amount */ + ui->fermentablesTable->setColumnWidth(8, 60); /* Procent */ + ui->fermentablesTable->setColumnWidth(9, 50); /* 100% */ + ui->fermentablesTable->setColumnWidth(10, 80); /* Delete */ + ui->fermentablesTable->setColumnWidth(11, 80); /* Edit */ + ui->fermentablesTable->setHorizontalHeaderLabels(labels); + ui->fermentablesTable->verticalHeader()->hide(); + ui->fermentablesTable->setRowCount(product->fermentables.size()); + + for (int i = 0; i < product->fermentables.size(); i++) { + + ui->fermentablesTable->setItem(i, 0, new QTableWidgetItem(product->fermentables.at(i).f_supplier)); + ui->fermentablesTable->setItem(i, 1, new QTableWidgetItem(product->fermentables.at(i).f_name)); + + w = QString("%1").arg(product->fermentables.at(i).f_color, 1, 'f', 0, '0'); + item = new QTableWidgetItem(w); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->fermentablesTable->setItem(i, 2, item); + + item = new QTableWidgetItem(fermentable_types[product->fermentables.at(i).f_type]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->fermentablesTable->setItem(i, 3, item); + + item = new QTableWidgetItem(fermentable_graintypes[product->fermentables.at(i).f_graintype]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->fermentablesTable->setItem(i, 4, item); + + item = new QTableWidgetItem(fermentable_added[product->fermentables.at(i).f_added]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->fermentablesTable->setItem(i, 5, item); + + item = new QTableWidgetItem(QString("%1%").arg(product->fermentables.at(i).f_yield, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->fermentablesTable->setItem(i, 6, item); + + item = new QTableWidgetItem(QString("%1 Kg").arg(product->fermentables.at(i).f_amount, 4, 'f', 3, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->fermentablesTable->setItem(i, 7, item); + + if (product->fermentables.at(i).f_added < 4) { + item = new QTableWidgetItem(QString("%1%").arg(product->fermentables.at(i).f_percentage, 2, 'f', 1, '0')); + } else { + item = new QTableWidgetItem(QString("")); // Blank for bottling and kegging. + } + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->fermentablesTable->setItem(i, 8, item); + + to100Fermentables(i); + + /* Add the Delete row button */ + 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(deleteFermentRow_clicked())); + pLayout = new QHBoxLayout(pWidget); + pLayout->addWidget(btn_dele); + pLayout->setContentsMargins(5, 0, 5, 0); + pWidget->setLayout(pLayout); + ui->fermentablesTable->setCellWidget(i, 10, 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(editFermentRow_clicked())); + pLayout = new QHBoxLayout(pWidget); + pLayout->addWidget(btn_edit); + pLayout->setContentsMargins(5, 0, 5, 0); + pWidget->setLayout(pLayout); + ui->fermentablesTable->setCellWidget(i, 11, pWidget); + } +} + + +void EditProduct::calcFermentables() +{ + int i; + double psugar = 0, pcara = 0, d, s = 0, x, color; + double vol = 0; // Volume sugars after boil + double addedS = 0; // Added sugars after boil + double addedmass = 0; // Added mass after boil + double mvol = 0; // Mash volume + double lintner = 0; // Total product lintner + double sugarsf = 0; // fermentable sugars mash + boil + double sugarsm = 0; // fermentable sugars in mash + double sugardensity = 1.611; // kg/l in solution + double mashtime = 0; // Total mash time + double mashtemp = 0; // Average mash temperature + double mashinfuse = 0; // Mash infuse amount + double colort = 0; // Colors srm * vol totals + double colorh = 0; // Colors ebc * vol * kt + double colorn = 0; // Colors ebc * pt * pct + + qDebug() << "calcFermentables()"; + + /* + * Get average mashtemp and mashtime from the Mash schedule. + * It is possible that the schedule is not (yet) present. + */ + if (product->mashs.size() > 0) { + for (i = 0; i < product->mashs.size(); i++) { + if (product->mashs.at(i).step_type == 0) // Infusion + mashinfuse += product->mashs.at(i).step_infuse_amount; + if (product->mashs.at(i).step_temp < 75) { // Ignore mashout + mashtime += product->mashs.at(i).step_time; + mashtemp += product->mashs.at(i).step_time * product->mashs.at(i).step_temp; + } + } + mashtemp = mashtemp / mashtime; + mvol = mashinfuse; + qDebug() << " mash time" << mashtime << "temp" << mashtemp << "infuse" << mashinfuse; + } else { + qDebug() << " no mash schedule"; + } + + const QSignalBlocker blocker1(ui->est_ogEdit); + const QSignalBlocker blocker2(ui->est_og2Edit); + + if (product->fermentables.size() < 1) { + qDebug() << " no fermentables, return."; + product->est_og = 0.980; + ui->est_ogEdit->setValue(0.980); + ui->est_og2Edit->setValue(0.980); + ui->est_og3Edit->setValue(0.980); + ui->est_ogShow->setValue(0.980); + product->est_color = 0; + ui->est_colorEdit->setValue(0); + ui->est_colorEdit->setStyleSheet(Utils::ebc_to_style(0)); + ui->est_color2Edit->setValue(0); + ui->est_color2Edit->setStyleSheet(Utils::ebc_to_style(0)); + ui->est_colorShow->setValue(0); + ui->perc_mashShow->setValue(0); + ui->perc_sugarsShow->setValue(0); + ui->perc_caraShow->setValue(0); + ui->lintnerShow->setValue(0); + product->est_fg = 0.980; + ui->est_fgEdit->setValue(0.980); + ui->est_fg3Edit->setValue(0.980); + ui->est_fgShow->setValue(0.980); + ui->est_abvEdit->setValue(0); + ui->est_abv2Edit->setValue(0); + ui->est_abvShow->setValue(0); + product->est_abv = 0; + ui->calEdit->setValue(0); + product->mashs_kg = 0; + return; + } + qDebug() << " adjust to 100" << product->fermentables_use100; + + product->mashs_kg = 0; + for (i = 0; i < product->fermentables.size(); i++) { + if (product->fermentables.at(i).f_type == 1 && product->fermentables.at(i).f_added < 4) // Sugars + psugar += product->fermentables.at(i).f_percentage; + if (product->fermentables.at(i).f_graintype == 2 && product->fermentables.at(i).f_added < 4) // Crystal/Cara + pcara += product->fermentables.at(i).f_percentage; + d = product->fermentables.at(i).f_amount * (product->fermentables.at(i).f_yield / 100) * (1 - product->fermentables.at(i).f_moisture / 100); + if (product->fermentables.at(i).f_added == 0) { // Mash + if (mvol > 0) { // If mash volume is known + mvol += product->fermentables.at(i).f_amount * product->fermentables.at(i).f_moisture / 100; + s += d; + } + d = ui->efficiencyEdit->value() / 100 * d; + sugarsm += d; + product->mashs_kg += product->fermentables.at(i).f_amount; + } + if (product->fermentables.at(i).f_added == 0 || product->fermentables.at(i).f_added == 1) // Mash or boil + sugarsf += d; + if (product->fermentables.at(i).f_added == 2 || product->fermentables.at(i).f_added == 3) { // Fermentation or lagering + x = (product->fermentables.at(i).f_yield / 100) * (1 - product->fermentables.at(i).f_moisture / 100); + addedS += product->fermentables.at(i).f_amount * x; + addedmass += product->fermentables.at(i).f_amount; + vol += (x * sugardensity + (1 - x) * 1) * product->fermentables.at(i).f_amount; + } + if (product->fermentables.at(i).f_added == 0 && + (product->fermentables.at(i).f_type == 0 || product->fermentables.at(i).f_type == 4) && + product->fermentables.at(i).f_color < 50) { + lintner += product->fermentables.at(i).f_diastatic_power * product->fermentables.at(i).f_amount; + } + if (product->fermentables.at(i).f_added < 4) { + colort += product->fermentables.at(i).f_amount * Utils::ebc_to_srm(product->fermentables.at(i).f_color); + colorh += product->fermentables.at(i).f_amount * product->fermentables.at(i).f_color * Utils::get_kt(product->fermentables.at(i).f_color); + colorn += (product->fermentables.at(i).f_percentage / 100) * product->fermentables.at(i).f_color; // For 8.6 Pt wort. + } + } + qDebug() << " colort" << colort << "colorh" << colorh << "colorn" << colorn; + qDebug() << " psugar" << psugar << "pcara" << pcara << "mvol" << mvol; + qDebug() << " sugarsf" << sugarsf << "sugarsm" << sugarsm; + + double og = Utils::estimate_sg(sugarsf + addedS, product->batch_size); + qDebug() << " OG" << ui->est_ogEdit->value() << og; + product->est_og = og; + ui->est_ogEdit->setValue(og); + ui->est_og2Edit->setValue(og); + ui->est_og3Edit->setValue(og); + ui->est_ogShow->setValue(og); + + product->preboil_sg = Utils::estimate_sg(sugarsm, product->boil_size); + qDebug() << " preboil SG" << product->preboil_sg; + + /* + * Color of the wort + */ + if (product->color_method == 4) { // Naudts + color = round(((Utils::sg_to_plato(og) / 8.6) * colorn) + (product->boil_time / 60)); + } else if (product->color_method == 3) { // Hans Halberstadt + double bv = 0.925; // Beer loss efficiency + double sr = 0.95; // Mash and sparge efficiency + color = round((4.46 * bv * sr) / product->batch_size * colorh); + } else { + double cw = colort / product->batch_size * 8.34436; + color = Utils::kw_to_ebc(product->color_method, cw); + //qDebug() << " oud EBC" << color << "new EBC" << Utils::kw_to_newebc(product->color_method, cw) << "SRM" << Utils::kw_to_srm(product->color_method, cw); + } + qDebug() << " color" << ui->est_colorEdit->value() << color << product->est_color; + product->est_color = color; + ui->est_colorEdit->setValue(color); + ui->est_colorEdit->setStyleSheet(Utils::ebc_to_style(color)); + ui->est_color2Edit->setValue(color); + ui->est_color2Edit->setStyleSheet(Utils::ebc_to_style(color)); + ui->est_colorShow->setValue(color); + + /* + * We don't have a equipment profile in products, + * so we assume a certain guessed mashtun size. + */ + ui->perc_mashShow->setValue(round(product->mashs_kg / (product->boil_size / 3) * 100)); + ui->perc_sugarsShow->setValue(round(psugar)); + ui->perc_caraShow->setValue(round(pcara)); + if (product->mashs_kg > 0) { + qDebug() << " lintner" << lintner << " mashkg" << product->mashs_kg << "final" << round(lintner / product->mashs_kg); + ui->lintnerShow->setValue(round(lintner / product->mashs_kg)); + } else { + qDebug() << " lintner N/A"; + ui->lintnerShow->setValue(0); + } + + /* + * Calculate the apparant attenuation. + */ + double svg = 0; + if (product->yeasts.size() > 0) { + for (i = 0; i < product->yeasts.size(); i++) { + if (product->yeasts.at(i).y_use == 0) { // Used in primary + if (product->yeasts.at(i).y_attenuation > svg) + svg = product->yeasts.at(i).y_attenuation; // Take the highest if multiple yeasts. + } + // TODO: brett or others in secondary. + } + qDebug() << " SVG" << svg; + } + if (svg == 0) + svg = 77.0; + ui->est_svgEdit->setValue(svg); + + double fg; + if (product->mashs_kg > 0 && mashinfuse > 0 && mashtime > 0 && mashtemp > 0) + fg = Utils::estimate_fg(psugar, pcara, mashinfuse / product->mashs_kg, mashtime, mashtemp, svg, og); + else + fg = Utils::estimate_fg(psugar, pcara, 0, 0, 0, svg, og); + qDebug() << " FG" << ui->est_fgEdit->value() << fg; + product->est_fg = fg; + ui->est_fgEdit->setValue(fg); + ui->est_fg3Edit->setValue(fg); + ui->est_fgShow->setValue(fg); + + double abv = Utils::abvol(og, fg); + qDebug() << " ABV" << ui->est_abvEdit->value() << abv; + ui->est_abvEdit->setValue(abv); + ui->est_abv2Edit->setValue(abv); + ui->est_abvShow->setValue(abv); + product->est_abv = abv; + + /* + * Calculate kilocalories/liter. Formula from brouwhulp. + * Take the alcohol and sugar parts and then combine. + */ + double alc = 1881.22 * fg * (og - fg) / (1.775 - og); + double sug = 3550 * fg * (0.1808 * og + 0.8192 * fg - 1.0004); + ui->calEdit->setValue(round((alc + sug) / (12 * 0.0295735296))); + + // Bottle priming + double priming_total = 0; + for (i = 0; i < product->fermentables.size(); i++) { + if (product->fermentables.at(i).f_added == 4) { + priming_total += ((product->fermentables.at(i).f_yield / 100) * (1 - product->fermentables.at(i).f_moisture / 100)) * + product->fermentables.at(i).f_amount; + qDebug() << " priming" << product->fermentables.at(i).f_amount << "total" << priming_total; + } + } + double grl = priming_total * 1000.0 * (1 / product->batch_size); + double volco2 = grl * 0.510; + qDebug() << " priming gr/l" << grl << "volco2" << volco2; + + if (volco2 > 0) { + product->est_carb = volco2; + ui->est_carbEdit->setValue(product->est_carb); + ui->est_carbShow->setValue(product->est_carb); + } +} + + +void EditProduct::calcFermentablesFromOG(double og) +{ + qDebug() << "calcFermentablesFromOG" << og; + + int i; + double totmass = 0; + double tot = 0; + double d, amount; + double efficiency = product->efficiency; + double sug = Utils::sg_to_plato(og) * product->batch_size * og / 100.0; // total amount of sugars in kg. + + for (i = 0; i < product->fermentables.size(); i++) { + if (product->fermentables.at(i).f_added < 4) { + d = product->fermentables.at(i).f_percentage / 100.0 * + (product->fermentables.at(i).f_yield / 100.0) * + (1 - product->fermentables.at(i).f_moisture / 100.0); + if (product->fermentables.at(i).f_added == 0) // Mash + d = efficiency / 100.0 * d; + tot += d; + } + } + if (tot) + totmass = round((sug / tot) * 1000.0) / 1000.0; + + if (totmass) { + for (i = 0; i < product->fermentables.size(); i++) { + amount = round(product->fermentables.at(i).f_percentage * 10.0 * totmass) / 1000.0; + product->fermentables[i].f_amount = amount; + } + } +} + + +void EditProduct::ferment_perc_mash_valueChanged(int value) +{ + if (value < 90) + ui->perc_mashShow->setStyleSheet(bar_green); + else if (value < 100) + ui->perc_mashShow->setStyleSheet(bar_orange); + else + ui->perc_mashShow->setStyleSheet(bar_red); +} + + +void EditProduct::ferment_perc_sugars_valueChanged(int value) +{ + if (value < 20) + ui->perc_sugarsShow->setStyleSheet(bar_green); + else + ui->perc_sugarsShow->setStyleSheet(bar_red); +} + + +void EditProduct::ferment_perc_cara_valueChanged(int value) +{ + if (value < 25) + ui->perc_caraShow->setStyleSheet(bar_green); + else + ui->perc_caraShow->setStyleSheet(bar_red); +} + + +void EditProduct::ferment_lintner_valueChanged(int value) +{ + if (value < 30) + ui->lintnerShow->setStyleSheet(bar_red); + else if (value < 40) + ui->lintnerShow->setStyleSheet(bar_orange); + else + ui->lintnerShow->setStyleSheet(bar_green); +} + + +void EditProduct::addFermentRow_clicked() +{ + Fermentables newf; + + qDebug() << "Add fermentable row"; + + for (int i = 0; i < product->fermentables.size(); i++) { + if (product->fermentables.at(i).f_amount == 0 && product->fermentables.at(i).f_color == 0) + return; // Add only one at a time. + } + + newf.f_name = "Select one"; + newf.f_origin = ""; + newf.f_supplier = ""; + newf.f_amount = 0; + newf.f_cost = 0; + newf.f_type = 0; + newf.f_yield = 0; + newf.f_color = 0; + newf.f_coarse_fine_diff = 0; + newf.f_moisture = 0; + newf.f_diastatic_power = 0; + newf.f_protein = 0; + newf.f_dissolved_protein = 0; + newf.f_max_in_batch = 100; + newf.f_graintype = 0; + newf.f_added = 0; + newf.f_recommend_mash = true; + newf.f_add_after_boil = false; + newf.f_adjust_to_total_100 = false; + newf.f_percentage = 0; + newf.f_di_ph = 0; + newf.f_acid_to_ph_57 = 0; + + product->fermentables.append(newf); + emit refreshAll(); +} + + +void EditProduct::deleteFermentRow_clicked() +{ + if (product->locked) + return; + + QPushButton *pb = qobject_cast<QPushButton *>(QObject::sender()); + int row = pb->objectName().toInt(); + qDebug() << "Delete fermentable row" << row << product->fermentables.size(); + + if (product->fermentables.size() < 1) + return; + + int rc = QMessageBox::warning(this, tr("Delete fermentable"), tr("Delete %1").arg(product->fermentables.at(row).f_name), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + if (rc == QMessageBox::No) + return; + + product->fermentables.removeAt(row); + + /* + * Recalculate the percentages on the rows left. + */ + double total = 0; + for (int i = 0; i < product->fermentables.size(); i++) + if (product->fermentables.at(i).f_added < 4) // Only before bottle/kegging + total += product->fermentables.at(i).f_amount; + for (int i = 0; i < product->fermentables.size(); i++) + if (product->fermentables.at(i).f_added < 4) + product->fermentables[i].f_percentage = product->fermentables.at(i).f_amount / total * 100; + + is_changed(); + emit refreshAll(); +} + + +void EditProduct::ferment_amount_changed(double val) +{ + QTableWidgetItem *item; + double total = 0, perc; + + if (product->fermentables_use100 && product->fermentables.at(product->fermentables_row).f_added < 4) + return; + + qDebug() << "ferment_amount_changed()" << product->fermentables_row << val; + + product->fermentables[product->fermentables_row].f_amount = val; + item = new QTableWidgetItem(QString("%1 Kg").arg(val, 4, 'f', 3, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->fermentablesTable->setItem(product->fermentables_row, 7, item); + + for (int i = 0; i < product->fermentables.size(); i++) + if (product->fermentables.at(i).f_added < 4) // Only before bottle/kegging + total += product->fermentables.at(i).f_amount; + /* + * Recalculate the percentages + */ + for (int i = 0; i < product->fermentables.size(); i++) { + if (product->fermentables.at(i).f_added < 4) { + perc = product->fermentables.at(i).f_amount / total * 100; + product->fermentables[i].f_percentage = perc; + item = new QTableWidgetItem(QString("%1%").arg(perc, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->fermentablesTable->setItem(i, 8, item); + if (i == product->fermentables_row) + this->pctEdit->setValue(perc); + } + } + is_changed(); +} + + +void EditProduct::ferment_pct_changed(double val) +{ + QTableWidgetItem *item; + double total = 0, row100 = -1; + + if (! product->fermentables_use100) + return; + + qDebug() << "ferment_pct_changed()" << product->fermentables_row << val; + /* + * Since we have arrived here, adjust_to_100 is active and + * this is not the entry to be adjusted to 100. + */ + for (int i = 0; i < product->fermentables.size(); i++) { + if (product->fermentables.at(i).f_added < 4) // Only before bottle/kegging + total += product->fermentables.at(i).f_amount; + if (product->fermentables.at(i).f_adjust_to_total_100) + row100 = i; + } + double oldperc = product->fermentables.at(product->fermentables_row).f_percentage; + double diffp = val - oldperc; + double diffw = (diffp / 100) * total; + qDebug() << "row100" << row100 << "total" << total << "diff kg" << diffw << "diff %" << diffp; + + product->fermentables[product->fermentables_row].f_percentage += diffp; + product->fermentables[product->fermentables_row].f_amount += diffw; + product->fermentables[row100].f_percentage -= diffp; + product->fermentables[row100].f_amount -= diffw; + + item = new QTableWidgetItem(QString("%1 Kg").arg(product->fermentables[product->fermentables_row].f_amount, 4, 'f', 3, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->fermentablesTable->setItem(product->fermentables_row, 7, item); + this->famountEdit->setValue(product->fermentables[product->fermentables_row].f_amount); + + item = new QTableWidgetItem(QString("%1%").arg(product->fermentables[product->fermentables_row].f_percentage, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->fermentablesTable->setItem(product->fermentables_row, 8, item); + + item = new QTableWidgetItem(QString("%1 Kg").arg(product->fermentables[row100].f_amount, 4, 'f', 3, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->fermentablesTable->setItem(row100, 7, item); + + item = new QTableWidgetItem(QString("%1%").arg(product->fermentables[row100].f_percentage, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->fermentablesTable->setItem(row100, 8, item); + + is_changed(); +} + + +void EditProduct::ferment_to100_changed(bool val) +{ + qDebug() << "ferment_to100_changed()" << product->fermentables_row << val << product->fermentables_use100; + + if (product->fermentables.at(product->fermentables_row).f_added >= 4) { + const QSignalBlocker blocker1(to100Edit); + product->fermentables[product->fermentables_row].f_adjust_to_total_100 = false; + to100Edit->setChecked(false); + return; + } + + /* + * Three scenario's. + * 1. There is no fermentable selected yet, just mark it. + * 2. There is current one is selected, deselect it. + * 3. There is another one selected, deselect and select this one. + */ + if (! product->fermentables_use100 && val) { + /* Scenario 1. */ + product->fermentables_use100 = true; + product->fermentables[product->fermentables_row].f_adjust_to_total_100 = true; + pctEdit->setReadOnly(false); + famountEdit->setReadOnly(true); + } else if (product->fermentables_use100 && product->fermentables[product->fermentables_row].f_adjust_to_total_100 && ! val) { + /* Scenario 2. */ + product->fermentables[product->fermentables_row].f_adjust_to_total_100 = false; + product->fermentables_use100 = false; + pctEdit->setReadOnly(true); + famountEdit->setReadOnly(false); + } else if (product->fermentables_use100 && ! product->fermentables[product->fermentables_row].f_adjust_to_total_100 && val) { + /* Scenario 3. */ + for (int i = 0; i < product->fermentables.size(); i++) { + product->fermentables[i].f_adjust_to_total_100 = false; + } + product->fermentables[product->fermentables_row].f_adjust_to_total_100 = true; + } else { + qDebug() << "bug"; + return; + } + + for (int i = 0; i < product->fermentables.size(); i++) { + to100Fermentables(i); + } + is_changed(); +} + + +void EditProduct::ferment_select_changed(int val) +{ + QSqlQuery query; + bool instock = finstockEdit->isChecked(); + QString w; + QTableWidgetItem *item; + + if (val < 1) + return; + + qDebug() << "ferment_select_changed()" << product->fermentables_row << val << instock; + + /* + * Search the fermentable pointed by the index and instock flag. + */ + QString sql = "SELECT name,origin,supplier,cost,type,yield,color,coarse_fine_diff,moisture,diastatic_power,protein,dissolved_protein,max_in_batch," + "graintype,recommend_mash,add_after_boil,di_ph,acid_to_ph_57 FROM inventory_fermentables "; + if (instock) + sql.append("WHERE inventory > 0 "); + sql.append("ORDER BY supplier,name"); + query.prepare(sql); + query.exec(); + query.first(); + for (int i = 0; i < (val - 1); i++) { + query.next(); + } + qDebug() << "found" << query.value(2).toString() << query.value(0).toString(); + + /* + * Replace the fermentable record contents + */ + product->fermentables[product->fermentables_row].f_name = query.value(0).toString(); + product->fermentables[product->fermentables_row].f_origin = query.value(1).toString(); + product->fermentables[product->fermentables_row].f_supplier = query.value(2).toString(); + product->fermentables[product->fermentables_row].f_cost = query.value(3).toDouble(); + product->fermentables[product->fermentables_row].f_type = query.value(4).toInt(); + product->fermentables[product->fermentables_row].f_yield = query.value(5).toDouble(); + product->fermentables[product->fermentables_row].f_color = query.value(6).toDouble(); + product->fermentables[product->fermentables_row].f_coarse_fine_diff = query.value(7).toDouble(); + product->fermentables[product->fermentables_row].f_moisture = query.value(8).toDouble(); + product->fermentables[product->fermentables_row].f_diastatic_power = query.value(9).toDouble(); + product->fermentables[product->fermentables_row].f_protein = query.value(10).toDouble(); + product->fermentables[product->fermentables_row].f_dissolved_protein = query.value(11).toDouble(); + product->fermentables[product->fermentables_row].f_max_in_batch = query.value(12).toDouble(); + product->fermentables[product->fermentables_row].f_graintype = query.value(13).toInt(); + product->fermentables[product->fermentables_row].f_recommend_mash = query.value(14).toInt() ? true:false; + product->fermentables[product->fermentables_row].f_add_after_boil = query.value(15).toInt() ? true:false; + product->fermentables[product->fermentables_row].f_di_ph = query.value(16).toDouble(); + product->fermentables[product->fermentables_row].f_acid_to_ph_57 = query.value(17).toDouble(); + + /* + * Update the visible fields + */ + fnameEdit->setText(product->fermentables.at(product->fermentables_row).f_name); + fsupplierEdit->setText(product->fermentables.at(product->fermentables_row).f_supplier); + fmaxEdit->setValue(product->fermentables.at(product->fermentables_row).f_max_in_batch); + + ui->fermentablesTable->setItem(product->fermentables_row, 0, new QTableWidgetItem(product->fermentables.at(product->fermentables_row).f_supplier)); + ui->fermentablesTable->setItem(product->fermentables_row, 1, new QTableWidgetItem(product->fermentables.at(product->fermentables_row).f_name)); + + w = QString("%1").arg(product->fermentables.at(product->fermentables_row).f_color, 1, 'f', 0, '0'); + item = new QTableWidgetItem(w); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->fermentablesTable->setItem(product->fermentables_row, 2, item); + + item = new QTableWidgetItem(fermentable_types[product->fermentables.at(product->fermentables_row).f_type]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->fermentablesTable->setItem(product->fermentables_row, 3, item); + + item = new QTableWidgetItem(fermentable_graintypes[product->fermentables.at(product->fermentables_row).f_graintype]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->fermentablesTable->setItem(product->fermentables_row, 4, item); + + item = new QTableWidgetItem(QString("%1%").arg(product->fermentables.at(product->fermentables_row).f_yield, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->fermentablesTable->setItem(product->fermentables_row, 6, item); + + calcFermentables(); + is_changed(); +} + + +void EditProduct::ferment_instock_changed(bool val) +{ + QSqlQuery query; + + qDebug() << "ferment_instock_changed()" << product->fermentables_row << val; + + this->fselectEdit->setCurrentIndex(-1); + this->fselectEdit->clear(); + QString sql = "SELECT supplier,name,color,inventory FROM inventory_fermentables "; + if (val) + sql.append("WHERE inventory > 0 "); + sql.append("ORDER BY supplier,name"); + query.prepare(sql); + query.exec(); + query.first(); + this->fselectEdit->addItem(""); // Start with empty value + for (int i = 0; i < query.size(); i++) { + this->fselectEdit->addItem(query.value(0).toString()+" - "+query.value(1).toString()+" ("+query.value(2).toString()+" EBC) "+ + QString("%1 kg").arg(query.value(3).toDouble(), 4, 'f', 3, '0')); + query.next(); + } +} + + +void EditProduct::ferment_added_changed(int val) +{ + qDebug() << "ferment_added_changed()" << product->fermentables_row << val; + + product->fermentables[product->fermentables_row].f_added = val; + QTableWidgetItem *item = new QTableWidgetItem(fermentable_added[val]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->fermentablesTable->setItem(product->fermentables_row, 5, item); + + famountEdit->setReadOnly(product->fermentables_use100 && product->fermentables.at(product->fermentables_row).f_added < 4); + pctEdit->setReadOnly(! (product->fermentables_use100 && product->fermentables.at(product->fermentables_row).f_added < 4)); + + double total = 0; + for (int i = 0; i < product->fermentables.size(); i++) + if (product->fermentables.at(i).f_added < 4) // Only before bottle/kegging + total += product->fermentables.at(i).f_amount; + for (int i = 0; i < product->fermentables.size(); i++) + if (product->fermentables.at(i).f_added < 4) + product->fermentables[i].f_percentage = product->fermentables.at(i).f_amount / total * 100; + + is_changed(); + emit refreshAll(); +} + + +void EditProduct::editFermentRow_clicked() +{ + QSqlQuery query; + + if (product->locked) + return; + + QPushButton *pb = qobject_cast<QPushButton *>(QObject::sender()); + product->fermentables_row = pb->objectName().toInt(); + qDebug() << "Edit fermentable row" << product->fermentables_row; + Fermentables backup = product->fermentables.at(product->fermentables_row); + + QDialog* dialog = new QDialog(this); + dialog->resize(738, 287); + 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("Current ingredient:")); + nameLabel->setGeometry(QRect(10, 10, 141, 20)); + nameLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + QLabel *supplierLabel = new QLabel(dialog); + supplierLabel->setObjectName(QString::fromUtf8("supplierLabel")); + supplierLabel->setText(tr("Supplier:")); + supplierLabel->setGeometry(QRect(10, 40, 141, 20)); + supplierLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + QLabel *amountLabel = new QLabel(dialog); + amountLabel->setObjectName(QString::fromUtf8("amountLabel")); + amountLabel->setText(tr("Amount in kg:")); + amountLabel->setGeometry(QRect(10, 100, 141, 20)); + amountLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + QLabel *pctLabel = new QLabel(dialog); + pctLabel->setObjectName(QString::fromUtf8("pctLabel")); + pctLabel->setText(tr("Percentage in batch:")); + pctLabel->setGeometry(QRect(10, 130, 141, 20)); + pctLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + QLabel *to100Label = new QLabel(dialog); + to100Label->setObjectName(QString::fromUtf8("to100Label")); + to100Label->setText(tr("Auto fill to 100%:")); + to100Label->setGeometry(QRect(10, 160, 141, 20)); + to100Label->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + QLabel *addedLabel = new QLabel(dialog); + addedLabel->setObjectName(QString::fromUtf8("addedLabel")); + addedLabel->setText(tr("Use at:")); + addedLabel->setGeometry(QRect(10, 190, 141, 20)); + addedLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + QLabel *selectLabel = new QLabel(dialog); + selectLabel->setObjectName(QString::fromUtf8("selectLabel")); + selectLabel->setText(tr("Select ingredient:")); + selectLabel->setGeometry(QRect(10, 70, 141, 20)); + selectLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + QLabel *instockLabel = new QLabel(dialog); + instockLabel->setObjectName(QString::fromUtf8("instockLabel")); + instockLabel->setText(tr("In stock:")); + instockLabel->setGeometry(QRect(525, 70, 121, 20)); + instockLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + QLabel *maxLabel = new QLabel(dialog); + maxLabel->setObjectName(QString::fromUtf8("maxLabel")); + maxLabel->setText(tr("Max in batch:")); + maxLabel->setGeometry(QRect(420, 130, 121, 20)); + maxLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + + fselectEdit = new QComboBox(dialog); + fselectEdit->setObjectName(QString::fromUtf8("fselectEdit")); + fselectEdit->setGeometry(QRect(160, 70, 371, 23)); + + fnameEdit = new QLineEdit(dialog); + fnameEdit->setObjectName(QString::fromUtf8("fnameEdit")); + fnameEdit->setText(product->fermentables.at(product->fermentables_row).f_name); + fnameEdit->setGeometry(QRect(160, 10, 511, 23)); + fnameEdit->setReadOnly(true); + fsupplierEdit = new QLineEdit(dialog); + fsupplierEdit->setObjectName(QString::fromUtf8("fsupplierEdit")); + fsupplierEdit->setText(product->fermentables.at(product->fermentables_row).f_supplier); + fsupplierEdit->setGeometry(QRect(160, 40, 511, 23)); + fsupplierEdit->setReadOnly(true); + famountEdit = new QDoubleSpinBox(dialog); + famountEdit->setObjectName(QString::fromUtf8("famountEdit")); + famountEdit->setGeometry(QRect(160, 100, 121, 24)); + famountEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + famountEdit->setAccelerated(true); + famountEdit->setDecimals(3); + famountEdit->setReadOnly(product->fermentables_use100 && product->fermentables.at(product->fermentables_row).f_added < 4); + famountEdit->setMaximum(100000.0); + famountEdit->setSingleStep(0.0010); + famountEdit->setValue(product->fermentables.at(product->fermentables_row).f_amount); + + pctEdit = new QDoubleSpinBox(dialog); + pctEdit->setObjectName(QString::fromUtf8("pctEdit")); + pctEdit->setGeometry(QRect(160, 130, 121, 24)); + pctEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + pctEdit->setAccelerated(true); + pctEdit->setDecimals(1); + if (product->fermentables_use100 && product->fermentables.at(product->fermentables_row).f_added < 4) { + if (product->fermentables.at(product->fermentables_row).f_adjust_to_total_100) + pctEdit->setReadOnly(true); + else + pctEdit->setReadOnly(false); + } else { + pctEdit->setReadOnly(true); + } + pctEdit->setMaximum(100.0); + pctEdit->setSingleStep(0.1); + pctEdit->setValue(product->fermentables.at(product->fermentables_row).f_percentage); + + faddedEdit = new QComboBox(dialog); + faddedEdit->setObjectName(QString::fromUtf8("faddedEdit")); + faddedEdit->setGeometry(QRect(160, 190, 161, 23)); + faddedEdit->addItem(tr("Mash")); + faddedEdit->addItem(tr("Boil")); + faddedEdit->addItem(tr("Fermentation")); + faddedEdit->addItem(tr("Lagering")); + faddedEdit->addItem(tr("Bottle")); + faddedEdit->addItem(tr("Kegs")); + faddedEdit->setCurrentIndex(product->fermentables.at(product->fermentables_row).f_added); + + to100Edit = new QCheckBox(dialog); + to100Edit->setObjectName(QString::fromUtf8("to100Edit")); + to100Edit->setGeometry(QRect(160, 160, 85, 21)); + to100Edit->setChecked(product->fermentables.at(product->fermentables_row).f_adjust_to_total_100); + + finstockEdit = new QCheckBox(dialog); + finstockEdit->setObjectName(QString::fromUtf8("instockEdit")); + finstockEdit->setGeometry(QRect(655, 70, 85, 21)); + finstockEdit->setChecked(true); + + fmaxEdit = new QDoubleSpinBox(dialog); + fmaxEdit->setObjectName(QString::fromUtf8("fmaxEdit")); + fmaxEdit->setGeometry(QRect(550, 130, 121, 24)); + fmaxEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + fmaxEdit->setReadOnly(true); + fmaxEdit->setButtonSymbols(QAbstractSpinBox::NoButtons); + fmaxEdit->setDecimals(1); + fmaxEdit->setValue(product->fermentables.at(product->fermentables_row).f_max_in_batch); + + ferment_instock_changed(true); + + connect(fselectEdit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &EditProduct::ferment_select_changed); + connect(famountEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::ferment_amount_changed); + connect(pctEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::ferment_pct_changed); + connect(faddedEdit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &EditProduct::ferment_added_changed); + connect(to100Edit, &QCheckBox::stateChanged, this, &EditProduct::ferment_to100_changed); + connect(finstockEdit, &QCheckBox::stateChanged, this, &EditProduct::ferment_instock_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->fermentables[product->fermentables_row] = backup; + /* + * Recalculate the percentages + */ + double total = 0; + for (int i = 0; i < product->fermentables.size(); i++) + if (product->fermentables.at(i).f_added < 4) // Only before bottle/kegging + total += product->fermentables.at(i).f_amount; + product->fermentables_use100 = false; + for (int i = 0; i < product->fermentables.size(); i++) { + if (product->fermentables.at(i).f_adjust_to_total_100) + product->fermentables_use100 = true; + if (product->fermentables.at(i).f_added < 4) { + product->fermentables[i].f_percentage = product->fermentables.at(i).f_amount / total * 100; + } + } + } + + disconnect(fselectEdit, nullptr, nullptr, nullptr); + disconnect(famountEdit, nullptr, nullptr, nullptr); + disconnect(pctEdit, nullptr, nullptr, nullptr); + disconnect(faddedEdit, nullptr, nullptr, nullptr); + disconnect(to100Edit, nullptr, nullptr, nullptr); + disconnect(finstockEdit, nullptr, nullptr, nullptr); + disconnect(buttonBox, nullptr, nullptr, nullptr); + + emit refreshAll(); +} + +