# HG changeset patch # User Michiel Broek # Date 1651178953 -7200 # Node ID f1ed3a2a94e9d963bba1b68c382790dd1b004c6b # Parent ceb8aa4ebd254e3a8cb9449a858fcc377bcaa71b Initial import of EditProduct, the part from EditRecipe is ported. diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 CMakeLists.txt --- a/CMakeLists.txt Thu Apr 28 16:59:58 2022 +0200 +++ b/CMakeLists.txt Thu Apr 28 22:49:13 2022 +0200 @@ -8,8 +8,8 @@ # ===== Set application version ===== SET( bmsapp_VERSION_MAJOR 0 ) -SET( bmsapp_VERSION_MINOR 1 ) -SET( bmsapp_VERSION_PATCH 6 ) +SET( bmsapp_VERSION_MINOR 2 ) +SET( bmsapp_VERSION_PATCH 0 ) # Compile flags @@ -180,6 +180,7 @@ ${SRCDIR}/EditProfileFerment.cpp ${SRCDIR}/EditRecipe.cpp ${SRCDIR}/ProdInprod.cpp + ${SRCDIR}/EditProduct.cpp ${SRCDIR}/Setup.cpp ${SRCDIR}/Utils.cpp ${SRCDIR}/PrinterDialog.cpp @@ -217,6 +218,7 @@ ${SRCDIR}/EditProfileFerment.h ${SRCDIR}/EditRecipe.h ${SRCDIR}/ProdInprod.h + ${SRCDIR}/EditProduct.h ${SRCDIR}/Setup.h ${SRCDIR}/Utils.h ${SRCDIR}/PrinterDialog.h @@ -241,6 +243,7 @@ ${UIDIR}/EditProfileStyle.ui ${UIDIR}/EditProfileFerment.ui ${UIDIR}/EditRecipe.ui + ${UIDIR}/EditProduct.ui ${UIDIR}/MainWindow.ui ) diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 src/EditProduct.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/EditProduct.cpp Thu Apr 28 22:49:13 2022 +0200 @@ -0,0 +1,1342 @@ +/** + * EditProduct.cpp is part of bmsapp. + * + * 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 . + */ +#include "MainWindow.h" +#include "EditProduct.h" +#include "PrinterDialog.h" +#include "../ui/ui_EditProduct.h" +#include "Utils.h" +#include "global.h" + + + +EditProduct::EditProduct(int id, QWidget *parent) : QDialog(parent), ui(new Ui::EditProduct) +{ + QSqlQuery query, wquery, yquery; + + qDebug() << "EditProduct record:" << id; + product = new Product; + ui->setupUi(this); + product->fermentables_row = product->hops_row = product->miscs_row = product->yeasts_row = product->mashs_row = -1; + product->fermentables_use100 = false; + this->recno = id; + + WindowTitle(); + + ui->typeEdit->addItem(tr("Extract")); + ui->typeEdit->addItem(tr("Partial Mash")); + ui->typeEdit->addItem(tr("All Grain")); + + ui->color_methodEdit->addItem("Morey"); + ui->color_methodEdit->addItem("Mosher"); + ui->color_methodEdit->addItem("Daniels"); + ui->color_methodEdit->addItem("Halberstadt"); + ui->color_methodEdit->addItem("Naudts"); + + ui->ibu_methodEdit->addItem("Tinseth"); + ui->ibu_methodEdit->addItem("Rager"); + ui->ibu_methodEdit->addItem("Daniels"); + + for (int i = 0; i < my_acids.size(); i++) { + ui->mw_acidPick->addItem(my_acids.at(i).name_en); + ui->sp_acidtypeEdit->addItem(my_acids.at(i).name_en); + } + + ui->sp_sourceEdit->addItem(tr("Source 1")); + ui->sp_sourceEdit->addItem(tr("Source 2")); + ui->sp_sourceEdit->addItem(tr("Mixed")); + + query.prepare("SELECT name FROM inventory_waters ORDER BY record"); + query.exec(); + query.first(); + ui->w1_nameEdit->addItem(""); + ui->w2_nameEdit->addItem(""); + for (int i = 0; i < query.size(); i++) { + ui->w1_nameEdit->addItem(query.value(0).toString()); + ui->w2_nameEdit->addItem(query.value(0).toString()); + query.next(); + } + + query.prepare("SELECT name FROM profile_water ORDER BY name"); + query.exec(); + ui->wt_sourceEdit->addItem(""); + while (query.next()) { + ui->wt_sourceEdit->addItem(query.value(0).toString()); + } + + query.prepare("SELECT name FROM profile_mash ORDER BY name"); + query.exec(); + ui->mash_pickEdit->addItem(""); + while (query.next()) { + ui->mash_pickEdit->addItem(query.value(0).toString()); + } + + ui->beerstyleEdit->addItem(""); // First add a dummy + query.prepare("SELECT style_guide,style_letter,name FROM profile_styles ORDER BY style_guide,style_letter,name"); + query.exec(); + query.first(); + for (int i = 0; i < query.size(); i++) { + ui->beerstyleEdit->addItem(query.value(0).toString()+" "+query.value(1).toString()+" "+query.value(2).toString()); + query.next(); + } + + if (id >= 0) { + query.prepare("SELECT * FROM products WHERE record = :recno"); + query.bindValue(":recno", id); + query.exec(); + if (! query.first()) { + qDebug() << "EditProduct seek error record" << id; + QMessageBox::warning(this, tr("Database error"), tr("MySQL error: record %1 not found").arg(id)); + this->done(QDialog::Rejected); // At this stage, this doesn't work because the dialog is not yet visible. + return; + } + + QSqlRecord rec = query.record(); + for (int i = 0; i < rec.count(); i++) + qDebug() << i << rec.fieldName(i) << query.value(i); + + product->record = query.value("record").toInt(); + product->uuid = query.value("uuid").toString(); + product->name = query.value("name").toString(); + product->code = query.value("code").toString(); + product->birth = query.value("birth").toDate(); + product->stage = query.value("stage").toInt(); + product->notes = query.value("notes").toString(); + product->log_brew = query.value("log_brew").toInt() ? true:false; + product->log_fermentation = query.value("log_fermentation").toInt() ? true:false; + product->log_ispindel = query.value("log_ispindel").toInt() ? true:false; + product->log_co2pressure = query.value("log_co2pressure").toInt() ? true:false; + product->inventory_reduced = query.value("inventory_reduced").toInt(); + product->locked = query.value("locked").toInt() ? true:false; + + product->eq_name = query.value("eq_name").toString(); + product->eq_notes = query.value("eq_notes").toString(); + product->eq_boil_size = query.value("eq_boil_size").toDouble(); + product->eq_batch_size = query.value("eq_batch_size").toDouble(); + product->eq_tun_volume = query.value("eq_tun_volume").toDouble(); + product->eq_tun_weight = query.value("eq_tun_weight").toDouble(); + product->eq_tun_specific_heat = query.value("eq_tun_specific_heat").toDouble(); + product->eq_tun_material = query.value("eq_tun_material").toInt(); + product->eq_tun_height = query.value("eq_tun_height").toDouble(); + product->eq_top_up_water = query.value("eq_top_up_water").toDouble(); + product->eq_trub_chiller_loss = query.value("eq_trub_chiller_loss").toDouble(); + product->eq_evap_rate = query.value("eq_evap_rate").toDouble(); + product->eq_boil_time = query.value("eq_boil_time").toDouble(); + product->eq_calc_boil_volume = query.value("eq_calc_boil_volume").toInt() ? true:false; + product->eq_top_up_kettle = query.value("eq_top_up_kettle").toDouble(); + product->eq_hop_utilization = query.value("eq_hop_utilization").toDouble(); + product->eq_lauter_volume = query.value("eq_lauter_volume").toDouble(); + product->eq_lauter_height = query.value("eq_lauter_height").toDouble(); + product->eq_lauter_deadspace = query.value("eq_lauter_deadspace").toDouble(); + product->eq_kettle_volume = query.value("eq_kettle_volume").toDouble(); + product->eq_kettle_height = query.value("eq_kettle_height").toDouble(); + product->eq_mash_volume = query.value("eq_mash_volume").toDouble(); + product->eq_mash_max = query.value("eq_mash_max").toDouble(); + product->eq_efficiency = query.value("eq_efficiency").toDouble(); + + product->brew_date_start = query.value("brew_date_start").toDateTime(); + product->brew_mash_ph = query.value("brew_mash_ph").toDouble(); + product->brew_mash_sg = query.value("brew_mash_sg").toDouble(); + product->brew_mash_efficiency = query.value("brew_mash_efficiency").toDouble(); + product->brew_sparge_temperature = query.value("brew_sparge_temperature").toDouble(); + product->brew_sparge_volume = query.value("brew_sparge_volume").toDouble(); + product->brew_sparge_est = query.value("brew_sparge_est").toDouble(); + product->brew_sparge_ph = query.value("brew_sparge_ph").toDouble(); + product->brew_preboil_volume = query.value("brew_preboil_volume").toDouble(); + product->brew_preboil_sg = query.value("brew_preboil_sg").toDouble(); + product->brew_preboil_ph = query.value("brew_preboil_ph").toDouble(); + product->brew_preboil_efficiency = query.value("brew_preboil_efficiency").toDouble(); + product->brew_aboil_volume = query.value("brew_aboil_volume").toDouble(); + product->brew_aboil_sg = query.value("brew_aboil_sg").toDouble(); + product->brew_aboil_ph = query.value("brew_aboil_ph").toDouble(); + product->brew_aboil_efficiency = query.value("brew_aboil_efficiency").toDouble(); + product->brew_cooling_method = query.value("brew_cooling_method").toInt(); + product->brew_cooling_time = query.value("brew_cooling_time").toDouble(); + product->brew_cooling_to = query.value("brew_cooling_to").toDouble(); + product->brew_whirlpool9 = query.value("brew_whirlpool9").toDouble(); + product->brew_whirlpool7 = query.value("brew_whirlpool7").toDouble(); + product->brew_whirlpool6 = query.value("brew_whirlpool6").toDouble(); + product->brew_whirlpool2 = query.value("brew_whirlpool2").toDouble(); + product->brew_fermenter_volume = query.value("brew_fermenter_volume").toDouble(); + product->brew_fermenter_extrawater = query.value("brew_fermenter_extrawater").toDouble(); + product->brew_fermenter_tcloss = query.value("brew_fermenter_tcloss").toDouble(); + product->brew_aeration_time = query.value("brew_aeration_time").toDouble(); + product->brew_aeration_speed = query.value("brew_aeration_speed").toDouble(); + product->brew_aeration_type = query.value("brew_aeration_type").toInt(); + product->brew_fermenter_sg = query.value("brew_fermenter_sg").toDouble(); + product->brew_fermenter_ibu = query.value("brew_fermenter_ibu").toDouble(); + product->brew_fermenter_color = query.value("brew_fermenter_color").toDouble(); + product->brew_date_end = query.value("brew_date_end").toDateTime(); + + product->og = query.value("og").toDouble(); + product->fg = query.value("fg").toDouble(); + product->primary_start_temp = query.value("primary_start_temp").toDouble(); + product->primary_max_temp = query.value("primary_max_temp").toDouble(); + product->primary_end_temp = query.value("primary_end_temp").toDouble(); + product->primary_end_sg = query.value("primary_end_sg").toDouble(); + product->primary_end_date = query.value("primary_end_date").toDate(); + product->secondary_temp = query.value("secondary_temp").toDouble(); + product->secondary_end_sg = query.value("secondary_end_sg").toDouble(); + product->secondary_end_date = query.value("secondary_end_date").toDate(); + product->tertiary_temp = query.value("tertiary_temp").toDouble(); + + product->package_date = query.value("package_date").toDate(); + product->package_volume = query.value("package_volume").toDouble(); + product->package_infuse_amount = query.value("package_infuse_amount").toDouble(); + product->package_infuse_abv = query.value("package_infuse_abv").toDouble(); + product->package_infuse_notes = query.value("package_infuse_notes").toString(); + product->package_abv = query.value("package_abv").toDouble(); + product->package_ph = query.value("package_ph").toDouble(); + + product->bottle_amount = query.value("bottle_amount").toDouble(); + product->bottle_carbonation = query.value("bottle_carbonation").toDouble(); + product->bottle_priming_sugar = query.value("bottle_priming_sugar").toInt(); + product->bottle_priming_amount = query.value("bottle_priming_amount").toDouble(); + product->bottle_priming_water = query.value("bottle_priming_water").toDouble(); + product->bottle_carbonation_temp = query.value("bottle_carbonation_temp").toDouble(); + + product->keg_amount = query.value("keg_amount").toDouble(); + product->keg_carbonation = query.value("keg_carbonation").toDouble(); + product->keg_priming_sugar = query.value("keg_priming_sugar").toInt(); + product->keg_priming_amount = query.value("keg_priming_amount").toDouble(); + product->keg_priming_water = query.value("keg_priming_water").toDouble(); + product->keg_carbonation_temp = query.value("keg_carbonation_temp").toDouble(); + product->keg_forced_carb = query.value("keg_forced_carb").toInt() ? true:false; + product->keg_pressure = query.value("keg_pressure").toDouble(); + + product->taste_notes = query.value("taste_notes").toString(); + product->taste_rate = query.value("taste_rate").toDouble(); + product->taste_date = query.value("taste_date").toDate(); + product->taste_color = query.value("taste_color").toString(); + product->taste_transparency = query.value("taste_transparency").toString(); + product->taste_head = query.value("taste_head").toString(); + product->taste_aroma = query.value("taste_aroma").toString(); + product->taste_taste = query.value("taste_taste").toString(); + product->taste_mouthfeel = query.value("taste_mouthfeel").toString(); + product->taste_aftertaste = query.value("taste_aftertaste").toString(); + + product->st_name = query.value("st_name").toString(); + product->st_letter = query.value("st_letter").toString(); + product->st_guide = query.value("st_guide").toString(); + product->st_category = query.value("st_category").toString(); + product->st_category_number = query.value("st_category_number").toInt(); + product->st_type = query.value("st_type").toInt(); + product->st_og_min = query.value("st_og_min").toDouble(); + product->st_og_max = query.value("st_og_max").toDouble(); + product->st_fg_min = query.value("st_fg_min").toDouble(); + product->st_fg_max = query.value("st_fg_max").toDouble(); + product->st_ibu_min = query.value("st_ibu_min").toDouble(); + product->st_ibu_max = query.value("st_ibu_max").toDouble(); + product->st_color_min = query.value("st_color_min").toDouble(); + product->st_color_max = query.value("st_color_max").toDouble(); + product->st_carb_min = query.value("st_carb_min").toDouble(); + product->st_carb_max = query.value("st_carb_max").toDouble(); + product->st_abv_min = query.value("st_abv_min").toDouble(); + product->st_abv_max = query.value("st_abv_max").toDouble(); + + product->type = query.value("type").toInt(); + product->batch_size = query.value("batch_size").toDouble(); + product->boil_size = query.value("boil_size").toDouble(); + product->boil_time = query.value("boil_time").toDouble(); + product->efficiency = query.value("efficiency").toDouble(); + product->est_og = query.value("est_og").toDouble(); + product->est_fg = query.value("est_fg").toDouble(); + product->est_abv = query.value("est_abv").toDouble(); + product->est_color = query.value("est_color").toDouble(); + product->color_method = query.value("color_method").toInt(); + product->est_ibu = query.value("est_ibu").toDouble(); + product->ibu_method = query.value("ibu_method").toInt(); + product->est_carb = query.value("est_carb").toDouble(); + + product->sparge_temp = query.value("sparge_temp").toDouble(); + product->sparge_ph = query.value("sparge_ph").toDouble(); + product->sparge_volume = query.value("sparge_volume").toDouble(); + product->sparge_source = query.value("sparge_source").toInt(); + product->sparge_acid_type = query.value("sparge_acid_type").toInt(); + product->sparge_acid_perc = query.value("sparge_acid_perc").toDouble(); + product->sparge_acid_amount = query.value("sparge_acid_amount").toDouble(); + product->mash_ph = query.value("mash_ph").toDouble(); + product->mash_name = query.value("mash_name").toString(); + product->calc_acid = query.value("calc_acid").toInt() ? true:false; + + product->w1_name = query.value("w1_name").toString(); + product->w1_amount = query.value("w1_amount").toDouble(); + product->w1_calcium = query.value("w1_calcium").toDouble(); + product->w1_sulfate = query.value("w1_sulfate").toDouble(); + product->w1_chloride = query.value("w1_chloride").toDouble(); + product->w1_sodium = query.value("w1_sodium").toDouble(); + product->w1_magnesium = query.value("w1_magnesium").toDouble(); + product->w1_total_alkalinity = query.value("w1_total_alkalinity").toDouble(); + product->w1_ph = query.value("w1_ph").toDouble(); + product->w1_cost = query.value("w1_cost").toDouble(); + product->w2_name = query.value("w2_name").toString(); + product->w2_amount = query.value("w2_amount").toDouble(); + product->w2_calcium = query.value("w2_calcium").toDouble(); + product->w2_sulfate = query.value("w2_sulfate").toDouble(); + product->w2_chloride = query.value("w2_chloride").toDouble(); + product->w2_sodium = query.value("w2_sodium").toDouble(); + product->w2_magnesium = query.value("w2_magnesium").toDouble(); + product->w2_total_alkalinity = query.value("w2_total_alkalinity").toDouble(); + product->w2_ph = query.value("w2_ph").toDouble(); + product->w2_cost = query.value("w2_cost").toDouble(); + product->wg_amount = query.value("wg_amount").toDouble(); + product->wg_calcium = query.value("wg_calcium").toDouble(); + product->wg_sulfate = query.value("wg_sulfate").toDouble(); + product->wg_chloride = query.value("wg_chloride").toDouble(); + product->wg_sodium = query.value("wg_sodium").toDouble(); + product->wg_magnesium = query.value("wg_magnesium").toDouble(); + product->wg_total_alkalinity = query.value("wg_total_alkalinity").toDouble(); + product->wg_ph = query.value("wg_ph").toDouble(); + product->wb_calcium = query.value("wb_calcium").toDouble(); + product->wb_sulfate = query.value("wb_sulfate").toDouble(); + product->wb_chloride = query.value("wb_chloride").toDouble(); + product->wb_sodium = query.value("wb_sodium").toDouble(); + product->wb_magnesium = query.value("wb_magnesium").toDouble(); + product->wb_total_alkalinity = query.value("wb_total_alkalinity").toDouble(); + product->wb_ph = query.value("wb_ph").toDouble(); + product->wa_acid_name = query.value("wa_acid_name").toInt(); + product->wa_acid_perc = query.value("wa_acid_perc").toDouble(); + product->wa_base_name = query.value("wa_base_name").toInt(); + + product->starter_enable = query.value("starter_enable").toInt() ? true:false; + product->starter_type = query.value("starter_type").toInt(); + product->starter_sg = query.value("starter_sg").toDouble(); + product->starter_viability = query.value("starter_viability").toInt(); + product->yeast_prod_date = query.value("yeast_prod_date").toDate(); + product->yeast_pitchrate = query.value("yeast_pitchrate").toDouble(); + product->prop1_type = query.value("prop1_type").toInt(); + product->prop1_volume = query.value("prop1_volume").toDouble(); + product->prop2_type = query.value("prop2_type").toInt(); + product->prop2_volume = query.value("prop2_volume").toDouble(); + product->prop3_type = query.value("prop3_type").toInt(); + product->prop3_volume = query.value("prop3_volume").toDouble(); + product->prop4_type = query.value("prop4_type").toInt(); + product->prop4_volume = query.value("prop4_volume").toDouble(); + + product->divide_type = query.value("divide_type").toInt(); + product->divide_size = query.value("divide_size").toDouble(); + product->divide_factor = query.value("divide_factor").toDouble(); + product->divide_parts = query.value("divide_parts").toInt(); + product->divide_part = query.value("divide_part").toInt(); + + QJsonParseError parseError; + const auto& f_json = query.value("json_fermentables").toString(); + if (!f_json.trimmed().isEmpty()) { + const auto& formattedJson = QString("%1").arg(f_json); + QJsonDocument fermentables = QJsonDocument::fromJson(formattedJson.toUtf8(), &parseError); + if (parseError.error != QJsonParseError::NoError) { + qDebug() << "Parse error: " << parseError.errorString() << "at" << parseError.offset ; + } else if (fermentables.isArray()) { + double percentcheck = 0; + for (int i = 0; i < fermentables.array().size(); i++) { + QJsonObject obj = fermentables.array().at(i).toObject(); + //qDebug() << i << obj; + Fermentables f; + f.f_name = obj["f_name"].toString(); + f.f_origin = obj["f_origin"].toString(); + f.f_supplier = obj["f_supplier"].toString(); + f.f_amount = obj["f_amount"].toDouble(); + f.f_cost = obj["f_cost"].toDouble(); + f.f_type = obj["f_type"].toInt(); + f.f_yield = obj["f_yield"].toDouble(); + f.f_color = obj["f_color"].toDouble(); + f.f_coarse_fine_diff = obj["f_coarse_fine_diff"].toDouble(); + f.f_moisture = obj["f_moisture"].toDouble(); + f.f_diastatic_power = obj["f_diastatic_power"].toDouble(); + f.f_protein = obj["f_protein"].toDouble(); + f.f_dissolved_protein = obj["f_dissolved_protein"].toDouble(); + f.f_max_in_batch = obj["f_max_in_batch"].toDouble(); + f.f_graintype = obj["f_graintype"].toInt(); + f.f_added = obj["f_added"].toInt(); + f.f_recommend_mash = obj["f_recommend_mash"].toInt() ? true:false; + f.f_add_after_boil = obj["f_add_after_boil"].toInt() ? true:false; + f.f_adjust_to_total_100 = obj["f_adjust_to_total_100"].toInt() ? true:false; + f.f_percentage = obj["f_percentage"].toDouble(); + f.f_di_ph = obj["f_di_ph"].toDouble(); + f.f_acid_to_ph_57 = obj["f_acid_to_ph_57"].toDouble(); + if (f.f_adjust_to_total_100) + product->fermentables_use100 = true; + percentcheck += f.f_percentage; + product->fermentables.append(f); + } + qDebug() << "fermentables" << product->fermentables.size() << percentcheck; + if (percentcheck == 0) { + /* Missing percentages, fix it. */ + double total = 0; + for (int i = 0; i < product->fermentables.size(); i++) { + if (product->fermentables.at(i).f_added < 4) + 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 = round((product->fermentables.at(i).f_amount / total) * 10000.0) / 100.0; + } + qDebug() << " fixed missing percentages"; + is_changed(); + } + } + } else { + qDebug() << "empty fermentables"; + } + + const auto& h_json = query.value("json_hops").toString(); + if (!h_json.trimmed().isEmpty()) { + const auto& formattedJson = QString("%1").arg(h_json); + QJsonDocument hops = QJsonDocument::fromJson(formattedJson.toUtf8(), &parseError); + if (parseError.error != QJsonParseError::NoError) { + qDebug() << "Parse error: " << parseError.errorString() << "at" << parseError.offset; + } else if (hops.isArray()) { + for (int i = 0; i < hops.array().size(); i++) { + QJsonObject obj = hops.array().at(i).toObject(); + //qDebug() << i << obj; + Hops h; + h.h_name = obj["h_name"].toString(); + h.h_origin = obj["h_origin"].toString(); + h.h_amount = obj["h_amount"].toDouble(); + h.h_cost = obj["h_cost"].toDouble(); + h.h_type = obj["h_type"].toInt(); + h.h_form = obj["h_form"].toInt(); + h.h_useat = obj["h_useat"].toInt(); + h.h_time = obj["h_time"].toInt(); + h.h_alpha = obj["h_alpha"].toDouble(); + h.h_beta = obj["h_beta"].toDouble(); + h.h_hsi = obj["h_hsi"].toDouble(); + h.h_humulene = obj["h_humulene"].toDouble(); + h.h_caryophyllene = obj["h_caryophyllene"].toDouble(); + h.h_cohumulone = obj["h_cohumulone"].toDouble(); + h.h_myrcene = obj["h_myrcene"].toDouble(); + h.h_total_oil = obj["h_total_oil"].toDouble(); + product->hops.append(h); + } + qDebug() << "hops" << product->hops.size(); + } + } else { + qDebug() << "empty hops"; + } + + const auto& m_json = query.value("json_miscs").toString(); + if (!m_json.trimmed().isEmpty()) { + const auto& formattedJson = QString("%1").arg(m_json); + QJsonDocument miscs = QJsonDocument::fromJson(formattedJson.toUtf8(), &parseError); + if (parseError.error != QJsonParseError::NoError) { + qDebug() << "Parse error: " << parseError.errorString() << "at" << parseError.offset; + } else if (miscs.isArray()) { + for (int i = 0; i < miscs.array().size(); i++) { + QJsonObject obj = miscs.array().at(i).toObject(); + //qDebug() << i << obj; + Miscs m; + m.m_name = obj["m_name"].toString(); + m.m_amount = obj["m_amount"].toDouble(); + m.m_type = obj["m_type"].toInt(); + m.m_use_use = obj["m_use_use"].toInt(); + m.m_time = obj["m_time"].toDouble(); + m.m_amount_is_weight = obj["m_amount_is_weight"].toInt() ? true:false; + m.m_cost = obj["m_cost"].toDouble(); + product->miscs.append(m); + } + qDebug() << "miscs" << product->miscs.size(); + } + } else { + qDebug() << "empty miscs"; + } + + const auto& y_json = query.value("json_yeasts").toString(); + if (!y_json.trimmed().isEmpty()) { + const auto& formattedJson = QString("%1").arg(y_json); + QJsonDocument yeasts = QJsonDocument::fromJson(formattedJson.toUtf8(), &parseError); + if (parseError.error != QJsonParseError::NoError) { + qDebug() << "Parse error: " << parseError.errorString() << "at" << parseError.offset ; + } else if (yeasts.isArray()) { + for (int i = 0; i < yeasts.array().size(); i++) { + QJsonObject obj = yeasts.array().at(i).toObject(); + //qDebug() << i << obj; + Yeasts y; + /* First some defaults for newer fields. */ + y.y_tolerance = y.y_harvest_time = y.y_pitch_temperature = y.y_zymocide = 0; + y.y_sta1 = y.y_bacteria = y.y_harvest_top = y.y_pofpos = false; + y.y_gr_hl_lo = 50; + y.y_sg_lo = 1.04; + y.y_gr_hl_hi = 80; + y.y_sg_hi = 1.08; + /* Now get what we have got */ + y.y_name = obj["y_name"].toString(); + y.y_laboratory = obj["y_laboratory"].toString(); + y.y_product_id = obj["y_product_id"].toString(); + y.y_amount = obj["y_amount"].toDouble(); + y.y_type = obj["y_type"].toInt(); + y.y_form = obj["y_form"].toInt(); + y.y_min_temperature = obj["y_min_temperature"].toDouble(); + y.y_max_temperature = obj["y_max_temperature"].toDouble(); + y.y_flocculation = obj["y_flocculation"].toInt(); + y.y_attenuation = obj["y_attenuation"].toDouble(); + y.y_cells = obj["y_cells"].toDouble(); + y.y_tolerance = obj["y_tolerance"].toDouble(); + y.y_inventory = obj["y_inventory"].toDouble(); + y.y_use = obj["y_use"].toInt(); + y.y_sta1 = obj["y_sta1"].toInt() ? true:false; + y.y_bacteria = obj["y_bacteria"].toInt() ? true:false; + y.y_harvest_top = obj["y_harvest_top"].toInt() ? true:false; + y.y_harvest_time = obj["y_harvest_time"].toInt(); + y.y_pitch_temperature = obj["y_pitch_temperature"].toDouble(); + y.y_pofpos = obj["y_pofpos"].toInt() ? true:false; + y.y_zymocide = obj["y_zymocide"].toInt(); + y.y_gr_hl_lo = obj["y_gr_hl_lo"].toInt(); + y.y_sg_lo = obj["y_sg_lo"].toDouble(); + y.y_gr_hl_hi = obj["y_gr_hl_hi"].toInt(); + y.y_sg_hi = obj["y_sg_hi"].toDouble(); + y.y_cost = obj["y_cost"].toDouble(); + + if (y.y_tolerance == 0 || y.y_cells == 0) { // More and better tests? + /* + * Possible data upgrade needed. + */ + yquery.prepare("SELECT tolerance,cells,sta1,bacteria,harvest_top,harvest_time,pitch_temperature," + "pofpos,zymocide,gr_hl_lo,sg_lo,gr_hl_hi,sg_hi " + "FROM inventory_yeasts WHERE name=:name AND laboratory=:laboratory AND product_id=:product_id"); + yquery.bindValue(":name", y.y_name); + yquery.bindValue(":laboratory", y.y_laboratory); + yquery.bindValue(":product_id", y.y_product_id); + yquery.exec(); + if (yquery.first()) { + y.y_tolerance = yquery.value(0).toDouble(); + y.y_cells = yquery.value(1).toDouble(); + y.y_sta1 = yquery.value(2).toInt() ? true:false; + y.y_bacteria = yquery.value(3).toInt() ? true:false; + y.y_harvest_top = yquery.value(4).toInt() ? true:false; + y.y_harvest_time = yquery.value(5).toInt(); + y.y_pitch_temperature = yquery.value(6).toDouble(); + y.y_pofpos = yquery.value(7).toInt() ? true:false; + y.y_zymocide = yquery.value(8).toInt(); + y.y_gr_hl_lo = yquery.value(9).toInt(); + y.y_sg_lo = yquery.value(10).toDouble(); + y.y_gr_hl_hi = yquery.value(11).toInt(); + y.y_sg_hi = yquery.value(12).toDouble(); + } else { + qDebug() << y.y_name << y.y_product_id << "not found for upgrade"; + } + } + product->yeasts.append(y); + } + qDebug() << "yeasts" << product->yeasts.size(); + } + } else { + qDebug() << "empty yeasts"; + } + + const auto& ma_json = query.value("json_mashs").toString().trimmed(); + if (!ma_json.trimmed().isEmpty()) { + const auto& formattedJson = QString("%1").arg(ma_json); + QJsonDocument mashs = QJsonDocument::fromJson(formattedJson.toUtf8(), &parseError); + if (parseError.error != QJsonParseError::NoError) { + qDebug() << "Parse error: " << parseError.errorString() << "at" << parseError.offset ; + } else if (mashs.isArray()) { + for (int i = 0; i < mashs.array().size(); i++) { + QJsonObject obj = mashs.array().at(i).toObject(); + qDebug() << i << obj; + Mashs m; + m.step_name = obj["step_name"].toString(); + m.step_type = obj["step_type"].toInt(); + m.step_volume = obj["step_volume"].toDouble(); + m.step_infuse_amount = obj["step_infuse_amount"].toDouble(); + m.step_infuse_temp = obj["step_infuse_temp"].toDouble(); + m.step_temp = obj["step_temp"].toDouble(); + m.step_time = obj["step_time"].toDouble(); + m.ramp_time = obj["ramp_time"].toDouble(); + m.end_temp = obj["end_temp"].toDouble(); + m.step_wg_ratio = obj["step_wg_ratio"].toDouble(); + product->mashs.append(m); + } + } + } else { + qDebug() << "empty mashs"; + } + qDebug() << "mashs" << product->mashs.size(); + + } else { + /* Set some defaults */ + product->locked = false; + product->st_name = ""; + product->st_letter = ""; + product->st_guide = ""; + product->st_category = ""; + product->st_category_number = 0; + product->st_type = 0; + product->st_og_min = 1.025; product->st_og_max = 1.100; + product->st_fg_min = 1.000; product->st_fg_max = 1.020; + product->st_ibu_min = 5; product->st_ibu_max = 200; + product->st_color_min = 3; product->st_color_max = 100; + product->st_carb_min = 1.0; product->st_carb_max = 4.5; + product->st_abv_min = 1; product->st_abv_max = 15; + product->name = ""; + product->notes = ""; + product->efficiency = 75; + product->batch_size = 20; + product->boil_time = 60; + product->boil_size = product->batch_size + (round(product->batch_size * product->boil_time / 60.0) / 10.0); + product->type = 2; + product->est_og = product->est_fg = product->est_color = product->est_ibu = product->est_abv = 0; + product->sparge_temp = 80; + product->sparge_ph = 5.4; + product->sparge_volume = 8; + product->sparge_source = 0; + product->sparge_acid_type = 0; + product->sparge_acid_perc = 80; + product->sparge_acid_amount = 0; + product->mash_ph = 5.4; + product->mash_name = ""; + product->calc_acid = true; + product->w1_name = ""; + product->w1_amount = 0; + product->w1_calcium = 0; + product->w1_sulfate = 0; + product->w1_chloride = 0; + product->w1_sodium = 0; + product->w1_magnesium = 0; + product->w1_total_alkalinity = 0; + product->w1_ph = 7; + product->w1_cost = 0; + product->w2_name = ""; + product->w2_amount = 0; + product->w2_calcium = 0; + product->w2_sulfate = 0; + product->w2_chloride = 0; + product->w2_sodium = 0; + product->w2_magnesium = 0; + product->w2_total_alkalinity = 0; + product->w2_ph = 7; + product->w2_cost = 0; + product->wg_amount = 0; + product->wg_calcium = 0; + product->wg_sulfate = 0; + product->wg_chloride = 0; + product->wg_sodium = 0; + product->wg_magnesium = 0; + product->wg_total_alkalinity = 0; + product->wg_ph = 7; + product->wb_calcium = 0; + product->wb_sulfate = 0; + product->wb_chloride = 0; + product->wb_sodium = 0; + product->wb_magnesium = 0; + product->wb_total_alkalinity = 0; + product->wb_ph = 7; + product->wa_acid_name = 0; + product->wa_acid_perc = 80; + product->wa_base_name = 0; + } + + // Tab generic. + ui->lockedEdit->setChecked(product->locked); + ui->st_nameEdit->setText(product->st_name); + ui->st_groupEdit->setText(product->st_letter); + ui->st_guideEdit->setText(product->st_guide); + ui->st_catEdit->setText(product->st_category); + ui->st_catnrEdit->setText(QString("%1").arg(product->st_category_number)); + ui->st_typeEdit->setText(style_types[product->st_type]); + ui->nameEdit->setText(product->name); + ui->notesEdit->setPlainText(product->notes); + ui->typeEdit->setCurrentIndex(product->type); + ui->batch_sizeEdit->setValue(product->batch_size); + ui->boil_sizeEdit->setValue(product->boil_size); + ui->boil_timeEdit->setValue(product->boil_time); + ui->efficiencyEdit->setValue(product->efficiency); + ui->est_ogEdit->setValue(product->est_og); + ui->est_ogShow->setRange(product->st_og_min, product->st_og_max); + ui->est_ogShow->setPrecision(3); + ui->est_ogShow->setMarkerTextIsValue(true); + ui->est_ogShow->setValue(product->est_og); + ui->est_fgEdit->setValue(product->est_fg); + ui->est_fgShow->setRange(product->st_fg_min, product->st_fg_max); + ui->est_fgShow->setPrecision(3); + ui->est_fgShow->setMarkerTextIsValue(true); + ui->est_fgShow->setValue(product->est_fg); + ui->est_abvEdit->setValue(product->est_abv); + ui->est_abvShow->setRange(product->st_abv_min, product->st_abv_max); + ui->est_abvShow->setPrecision(1); + ui->est_abvShow->setMarkerTextIsValue(true); + ui->est_abvShow->setValue(product->est_abv); + ui->est_colorEdit->setValue(product->est_color); + ui->est_colorEdit->setStyleSheet(Utils::ebc_to_style(product->est_color)); + ui->est_colorShow->setPrecision(0); + ui->est_colorShow->setMarkerTextIsValue(true); + ui->est_colorShow->setRange(product->st_color_min, product->st_color_max); + ui->est_colorShow->setValue(product->est_color); + ui->color_methodEdit->setCurrentIndex(product->color_method); + ui->est_ibuEdit->setValue(product->est_ibu); + ui->est_ibuShow->setPrecision(0); + ui->est_ibuShow->setMarkerTextIsValue(true); + ui->est_ibuShow->setRange(product->st_ibu_min, product->st_ibu_max); + ui->est_ibuShow->setValue(product->est_ibu); + ui->ibu_methodEdit->setCurrentIndex(product->ibu_method); + ui->est_carbEdit->setValue(product->est_carb); + ui->est_carbShow->setPrecision(1); + ui->est_carbShow->setMarkerTextIsValue(true); + ui->est_carbShow->setRange(product->st_carb_min, product->st_carb_max); + ui->est_carbShow->setValue(product->est_carb); + + // Tab fermentables. + ui->est_og2Edit->setValue(product->est_og); + ui->est_color2Edit->setValue(product->est_color); + ui->est_color2Edit->setStyleSheet(Utils::ebc_to_style(product->est_color)); + + // Tab hops. + ui->est_ibu2Edit->setValue(product->est_ibu); + + // Tab yeasts. + ui->est_og3Edit->setValue(product->est_og); + ui->est_fg3Edit->setValue(product->est_fg); + ui->est_abv2Edit->setValue(product->est_abv); + + // Tab mashs. + ui->mash_nameEdit->setText(product->mash_name); + + // Tab waters. + qDebug() << "water 1" << product->w1_name << "default" << my_default_water; + if (product->w1_ph > 4.0) { + /* + * Water data seems present, use that and set the name between [] + */ + ui->w1_nameEdit->setPlaceholderText(QString("["+product->w1_name+"]")); + } else { + bool found = false; + if (product->w1_name != "") { + /* + * We have a name, but do we know it? + */ + query.prepare("SELECT * FROM inventory_waters WHERE name=:water"); + query.bindValue(":water", product->w1_name); + query.exec(); + found = query.first(); + } + if (!found) { + /* + * Try to load default water + */ + query.prepare("SELECT * FROM inventory_waters WHERE record=:record"); + query.bindValue(":record", my_default_water); + query.exec(); + found = query.first(); + } + if (found) { + product->w1_calcium = query.value(3).toDouble(); + product->w1_magnesium = query.value(8).toDouble(); + product->w1_total_alkalinity = query.value(11).toDouble(); + product->w1_sodium = query.value(7).toDouble(); + product->w1_chloride = query.value(6).toDouble(); + product->w1_sulfate = query.value(5).toDouble(); + product->w1_ph = query.value(9).toDouble(); + ui->w1_nameEdit->setCurrentIndex(query.value(0).toInt()); + } else { + product->w1_calcium = 0; + product->w1_magnesium = 0; + product->w1_total_alkalinity = 0; + product->w1_sodium = 0; + product->w1_chloride = 0; + product->w1_sulfate = 0; + product->w1_ph = 0; + } + } + ui->w1_volEdit->setValue(product->w1_amount); + ui->w1_caEdit->setValue(product->w1_calcium); + ui->w1_mgEdit->setValue(product->w1_magnesium); + ui->w1_hco3Edit->setValue(product->w1_total_alkalinity * 1.22); + ui->w1_caco3Edit->setValue(product->w1_total_alkalinity); + ui->w1_naEdit->setValue(product->w1_sodium); + ui->w1_clEdit->setValue(product->w1_chloride); + ui->w1_so4Edit->setValue(product->w1_sulfate); + ui->w1_phEdit->setValue(product->w1_ph); + + qDebug() << "water 2" << product->w2_name; + if (product->w2_ph > 4.0) { + ui->w2_nameEdit->setPlaceholderText(QString("["+product->w2_name+"]")); + } else if (product->w2_name != "") { + query.prepare("SELECT * FROM inventory_waters WHERE name=:water"); + query.bindValue(":water", product->w2_name); + query.exec(); + if (query.first()) { + product->w2_calcium = query.value(3).toDouble(); + product->w2_magnesium = query.value(8).toDouble(); + product->w2_total_alkalinity = query.value(11).toDouble(); + product->w2_sodium = query.value(7).toDouble(); + product->w2_chloride = query.value(6).toDouble(); + product->w2_sulfate = query.value(5).toDouble(); + product->w2_ph = query.value(9).toDouble(); + ui->w2_nameEdit->setCurrentIndex(query.value(0).toInt()); + } else { + product->w2_calcium = 0; + product->w2_magnesium = 0; + product->w2_total_alkalinity = 0; + product->w2_sodium = 0; + product->w2_chloride = 0; + product->w2_sulfate = 0; + product->w2_ph = 0; + } + } + ui->w2_volEdit->setValue(product->w2_amount); + ui->w2_caEdit->setValue(product->w2_calcium); + ui->w2_mgEdit->setValue(product->w2_magnesium); + ui->w2_hco3Edit->setValue(product->w2_total_alkalinity * 1.22); + ui->w2_caco3Edit->setValue(product->w2_total_alkalinity); + ui->w2_naEdit->setValue(product->w2_sodium); + ui->w2_clEdit->setValue(product->w2_chloride); + ui->w2_so4Edit->setValue(product->w2_sulfate); + ui->w2_phEdit->setValue(product->w2_ph); + ui->mw_autoEdit->setChecked(product->calc_acid); + ui->mw_phEdit->setReadOnly(! product->calc_acid); + ui->mw_phEdit->setButtonSymbols(product->calc_acid ? QAbstractSpinBox::UpDownArrows : QAbstractSpinBox::NoButtons); + ui->mw_acidvolEdit->setReadOnly(product->calc_acid); + ui->mw_acidvolEdit->setButtonSymbols(product->calc_acid ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + + ui->sp_volEdit->setValue(product->sparge_volume); + ui->sp_tempEdit->setValue(product->sparge_temp); + ui->sp_phEdit->setValue(product->sparge_ph); + ui->sp_sourceEdit->setCurrentIndex(product->sparge_source); + ui->sp_acidtypeEdit->setCurrentIndex(product->sparge_acid_type); + ui->sp_acidpercEdit->setValue(product->sparge_acid_perc); + ui->sp_acidvolEdit->setValue(product->sparge_acid_amount); + + // All signals from tab "Generic" + connect(ui->lockedEdit, &QCheckBox::stateChanged, this, &EditProduct::is_changed); + connect(ui->nameEdit, &QLineEdit::textChanged, this, &EditProduct::name_changed); + connect(ui->notesEdit, SIGNAL(textChanged()), this, SLOT(notes_changed())); + connect(ui->typeEdit, QOverload::of(&QComboBox::currentIndexChanged), this, &EditProduct::brew_type_changed); + connect(ui->batch_sizeEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::batch_size_changed); + connect(ui->boil_timeEdit, QOverload::of(&QSpinBox::valueChanged), this, &EditProduct::boil_time_changed); + connect(ui->efficiencyEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::efficiency_changed); + connect(ui->beerstyleEdit, QOverload::of(&QComboBox::currentIndexChanged), this, &EditProduct::style_changed); + connect(ui->est_ogEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::est_og_changed); + connect(ui->color_methodEdit, QOverload::of(&QComboBox::currentIndexChanged), this, &EditProduct::colormethod_changed); + connect(ui->ibu_methodEdit, QOverload::of(&QComboBox::currentIndexChanged), this, &EditProduct::ibumethod_changed); + connect(ui->lockedEdit, &QCheckBox::stateChanged, this, &EditProduct::locked_changed); + + // All signals from tab "Fermentables" + ui->fermentablesTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + connect(ui->est_og2Edit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::est_og_changed); + connect(ui->perc_mashShow, &QProgressBar::valueChanged, this, &EditProduct::ferment_perc_mash_valueChanged); + connect(ui->perc_sugarsShow, &QProgressBar::valueChanged, this, &EditProduct::ferment_perc_sugars_valueChanged); + connect(ui->perc_caraShow, &QProgressBar::valueChanged, this, &EditProduct::ferment_perc_cara_valueChanged); + connect(ui->lintnerShow, &QProgressBar::valueChanged, this, &EditProduct::ferment_lintner_valueChanged); + connect(ui->addFermentable, SIGNAL(clicked()), this, SLOT(addFermentRow_clicked())); + + // All signals from tab "Hops" + ui->hopsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + connect(ui->hop_tasteShow, &QProgressBar::valueChanged, this, &EditProduct::hop_Flavour_valueChanged); + connect(ui->hop_aromaShow, &QProgressBar::valueChanged, this, &EditProduct::hop_Aroma_valueChanged); + connect(ui->addHop, SIGNAL(clicked()), this, SLOT(addHopRow_clicked())); + + // All signals from tab "Miscs" + ui->miscsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + connect(ui->addMisc, SIGNAL(clicked()), this, SLOT(addMiscRow_clicked())); + + // All signals from tab "Yeasts" + ui->yeastsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + connect(ui->addYeast, SIGNAL(clicked()), this, SLOT(addYeastRow_clicked())); + + // All signals from tab "Mash" + ui->mashsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + connect(ui->addMash, SIGNAL(clicked()), this, SLOT(addMashRow_clicked())); + connect(ui->mash_pickEdit, QOverload::of(&QComboBox::currentIndexChanged), this, &EditProduct::mash_select_changed); + connect(ui->mash_nameEdit, &QLineEdit::textChanged, this, &EditProduct::mash_name_changed); + + // All signals from tab "Water" + connect(ui->bs_cacl2Edit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::wb_cacl2_changed); + connect(ui->bs_caso4Edit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::wb_caso4_changed); + connect(ui->bs_mgso4Edit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::wb_mgso4_changed); + connect(ui->bs_naclEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::wb_nacl_changed); + connect(ui->bs_mgcl2Edit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::wb_mgcl2_changed); + connect(ui->bs_nahco3Edit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::wb_nahco3_changed); + connect(ui->bs_caco3Edit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::wb_caco3_changed); + connect(ui->mw_autoEdit, &QCheckBox::stateChanged, this, &EditProduct::mw_calc_acid_clicked); + connect(ui->mw_phEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::mw_ph_changed); + connect(ui->mw_acidvolEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::mw_acid_changed); + connect(ui->mw_acidPick, QOverload::of(&QComboBox::currentIndexChanged), this, &EditProduct::mw_type_changed); + connect(ui->wt_sourceEdit, QOverload::of(&QComboBox::currentIndexChanged), this, &EditProduct::wt_target_changed); + connect(ui->w1_nameEdit, QOverload::of(&QComboBox::currentIndexChanged), this, &EditProduct::w1_name_changed); + connect(ui->w2_nameEdit, QOverload::of(&QComboBox::currentIndexChanged), this, &EditProduct::w2_name_changed); + connect(ui->w2_volEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::w2_volume_changed); + connect(ui->sp_sourceEdit, QOverload::of(&QComboBox::currentIndexChanged), this, &EditProduct::sp_source_changed); + connect(ui->sp_acidtypeEdit, QOverload::of(&QComboBox::currentIndexChanged), this, &EditProduct::sp_type_changed); + connect(ui->sp_phEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::sp_ph_changed); + + setLocked(product->locked); + + ui->saveButton->setEnabled(false); + ui->deleteButton->setEnabled((id >= 0 && ! product->locked) ? true:false); + + emit refreshAll(); +} + + +EditProduct::~EditProduct() +{ + qDebug() << "EditProduct done start"; + delete ui; + emit entry_changed(); + qDebug() << "EditProduct done final"; +} + + +void EditProduct::refreshAll() +{ + refreshFermentables(); + calcFermentables(); /* Must be before Hops */ + refreshHops(); + calcIBUs(); + refreshMiscs(); + refreshYeasts(); + calcYeast(); + calcMash(); + refreshMashs(); + refreshWaters(); + calcWater(); +} + + +/* + * Window header, mark any change with '**' + */ +void EditProduct::WindowTitle() +{ + QString txt; + + if (this->recno < 0) { + txt = QString(tr("BMSapp - Add new product")); + } else { + txt = QString(tr("BMSapp - Edit product %1").arg(this->recno)); + } + + if (this->textIsChanged) { + txt.append((QString(" **"))); + } + setWindowTitle(txt); +} + + +void EditProduct::on_saveButton_clicked() +{ + QSqlQuery query; + + /* If there are errors in the form, show a message and do "return;" */ + if (ui->nameEdit->text().length() < 2) { + QMessageBox::warning(this, tr("Edit Product"), tr("Name empty or too short.")); + return; + } + if (ui->st_nameEdit->text().length() < 2) { + QMessageBox::warning(this, tr("Edit Product"), tr("No beerstyle selected.")); + return; + } + + if (this->textIsChanged) { + if (this->recno == -1) { + query.prepare("INSERT INTO products SET locked=:locked, st_name=:st_name, st_letter=:st_letter, " + "st_guide=:st_guide, st_category=:st_category, st_category_number=:st_catnr, st_type=:st_type, " + "st_og_min=:st_og_min, st_og_max=:st_og_max, st_fg_min=:st_fg_min, st_fg_max=:st_fg_max, " + "st_ibu_min=:st_ibu_min, st_ibu_max=:st_ibu_max, st_color_min=:st_color_min, st_color_max=:st_color_max, " + "st_carb_min=:st_carb_min, st_carb_max=:st_carb_max, st_abv_min=:st_abv_min, st_abv_max=:st_abv_max, " + "name=:name, notes=:notes, type=:type, batch_size=:batch_size, boil_size=:boil_size, " + "boil_time=:boil_time, efficiency=:efficiency, est_og=:est_og, est_fg=:est_fg, est_abv=:est_abv, " + "est_color=:est_color, color_method=:color_method, est_ibu=:est_ibu, ibu_method=:ibu_method, " + "est_carb=:est_carb, sparge_temp=:sparge_temp, sparge_ph=:sparge_ph, " + "sparge_volume=:sparge_volume, sparge_source=:sparge_source, sparge_acid_type=:sparge_acid_type, " + "sparge_acid_perc=:sparge_acid_perc, sparge_acid_amount=:sparge_acid_amount, mash_ph=:mash_ph, " + "mash_name=:mash_name, calc_acid=:calc_acid, " + "w1_name=:w1_name, w1_amount=:w1_amount, w1_calcium=:w1_calcium, w1_sulfate=:w1_sulfate, " + "w1_chloride=:w1_chloride, w1_sodium=:w1_sodium, w1_magnesium=:w1_magnesium, " + "w1_total_alkalinity=:w1_total_alkalinity, w1_ph=:w1_ph, w1_cost=:w1_cost, " + "w2_name=:w2_name, w2_amount=:w2_amount, w2_calcium=:w2_calcium, w2_sulfate=:w2_sulfate, " + "w2_chloride=:w2_chloride, w2_sodium=:w2_sodium, w2_magnesium=:w2_magnesium, " + "w2_total_alkalinity=:w2_total_alkalinity, w2_ph=:w2_ph, w2_cost=:w2_cost, " + "wg_amount=:wg_amount, wg_calcium=:wg_calcium, wg_sulfate=:wg_sulfate, " + "wg_chloride=:wg_chloride, wg_sodium=:wg_sodium, wg_magnesium=:wg_magnesium, " + "wg_total_alkalinity=:wg_total_alkalinity, wg_ph=:wg_ph, " + "wb_calcium=:wb_calcium, wb_sulfate=:wb_sulfate, wb_chloride=:wb_chloride, wb_sodium=:wb_sodium, " + "wb_magnesium=:wb_magnesium, wb_total_alkalinity=:wb_total_alkalinity, wb_ph=:wb_ph, " + "wa_acid_name=:wa_acid_name, wa_acid_perc=:wa_acid_perc, wa_base_name=:wa_base_name, " + "json_fermentables=:json_fermentables, json_hops=:json_hops, json_miscs=:json_miscs, " + "json_yeasts=:json_yeasts, json_mashs=:json_mashs, uuid=:uuid"); + } else { + query.prepare("UPDATE products SET locked=:locked, st_name=:st_name, st_letter=:st_letter, " + "st_guide=:st_guide, st_category=:st_category, st_category_number=:st_catnr, st_type=:st_type, " + "st_og_min=:st_og_min, st_og_max=:st_og_max, st_fg_min=:st_fg_min, st_fg_max=:st_fg_max, " + "st_ibu_min=:st_ibu_min, st_ibu_max=:st_ibu_max, st_color_min=:st_color_min, st_color_max=:st_color_max, " + "st_carb_min=:st_carb_min, st_carb_max=:st_carb_max, st_abv_min=:st_abv_min, st_abv_max=:st_abv_max, " + "name=:name, notes=:notes, type=:type, batch_size=:batch_size, boil_size=:boil_size, " + "boil_time=:boil_time, efficiency=:efficiency, est_og=:est_og, est_fg=:est_fg, est_abv=:est_abv, " + "est_color=:est_color, color_method=:color_method, est_ibu=:est_ibu, ibu_method=:ibu_method, " + "est_carb=:est_carb, sparge_temp=:sparge_temp, sparge_ph=:sparge_ph, " + "sparge_volume=:sparge_volume, sparge_source=:sparge_source, sparge_acid_type=:sparge_acid_type, " + "sparge_acid_perc=:sparge_acid_perc, sparge_acid_amount=:sparge_acid_amount, mash_ph=:mash_ph, " + "mash_name=:mash_name, calc_acid=:calc_acid, " + "w1_name=:w1_name, w1_amount=:w1_amount, w1_calcium=:w1_calcium, w1_sulfate=:w1_sulfate, " + "w1_chloride=:w1_chloride, w1_sodium=:w1_sodium, w1_magnesium=:w1_magnesium, " + "w1_total_alkalinity=:w1_total_alkalinity, w1_ph=:w1_ph, w1_cost=:w1_cost, " + "w2_name=:w2_name, w2_amount=:w2_amount, w2_calcium=:w2_calcium, w2_sulfate=:w2_sulfate, " + "w2_chloride=:w2_chloride, w2_sodium=:w2_sodium, w2_magnesium=:w2_magnesium, " + "w2_total_alkalinity=:w2_total_alkalinity, w2_ph=:w2_ph, w2_cost=:w2_cost, " + "wg_amount=:wg_amount, wg_calcium=:wg_calcium, wg_sulfate=:wg_sulfate, " + "wg_chloride=:wg_chloride, wg_sodium=:wg_sodium, wg_magnesium=:wg_magnesium, " + "wg_total_alkalinity=:wg_total_alkalinity, wg_ph=:wg_ph, " + "wb_calcium=:wb_calcium, wb_sulfate=:wb_sulfate, wb_chloride=:wb_chloride, wb_sodium=:wb_sodium, " + "wb_magnesium=:wb_magnesium, wb_total_alkalinity=:wb_total_alkalinity, wb_ph=:wb_ph, " + "wa_acid_name=:wa_acid_name, wa_acid_perc=:wa_acid_perc, wa_base_name=:wa_base_name, " + "json_fermentables=:json_fermentables, json_hops=:json_hops, json_miscs=:json_miscs, " + "json_yeasts=:json_yeasts, json_mashs=:json_mashs WHERE record = :recno"); + } + query.bindValue(":locked", product->locked ? 1:0); + query.bindValue(":st_name", product->st_name); + query.bindValue(":st_letter", product->st_letter); + query.bindValue(":st_guide", product->st_guide); + query.bindValue(":st_category", product->st_category); + query.bindValue(":st_catnr", product->st_category_number); + query.bindValue(":st_type", product->st_type); + query.bindValue(":st_og_min", round(product->st_og_min * 1000) / 1000); + query.bindValue(":st_og_max", round(product->st_og_max * 1000) / 1000); + query.bindValue(":st_fg_min", round(product->st_fg_min * 1000) / 1000); + query.bindValue(":st_fg_max", round(product->st_fg_max * 1000) / 1000); + query.bindValue(":st_ibu_min", round(product->st_ibu_min * 10) / 10); + query.bindValue(":st_ibu_max", round(product->st_ibu_max * 10) / 10); + query.bindValue(":st_color_min", round(product->st_color_min * 10) / 10); + query.bindValue(":st_color_max", round(product->st_color_max * 10) / 10); + query.bindValue(":st_carb_min", round(product->st_carb_min * 10) / 10); + query.bindValue(":st_carb_max", round(product->st_carb_max * 10) / 10); + query.bindValue(":st_abv_min", round(product->st_abv_min * 10) / 10); + query.bindValue(":st_abv_max", round(product->st_abv_max * 10) / 10); + query.bindValue(":name", product->name); + query.bindValue(":notes", product->notes); + query.bindValue(":type", product->type); + query.bindValue(":batch_size", round(product->batch_size * 10) / 10); + query.bindValue(":boil_size", round(product->boil_size * 10) / 10); + query.bindValue(":boil_time", round(product->boil_time * 10) / 10); + query.bindValue(":efficiency", round(product->efficiency * 10) / 10); + query.bindValue(":est_og", round(product->est_og * 1000) / 1000); + query.bindValue(":est_fg", round(product->est_fg * 1000) / 1000); + query.bindValue(":est_abv", round(product->est_abv * 10) / 10); + query.bindValue(":est_color", round(product->est_color * 10) / 10); + query.bindValue(":color_method", product->color_method); + query.bindValue(":est_ibu", round(product->est_ibu * 10) / 10); + query.bindValue(":ibu_method", product->ibu_method); + query.bindValue(":est_carb", round(product->est_carb * 10) / 10); + query.bindValue(":sparge_temp", round(product->sparge_temp * 10) / 10); + query.bindValue(":sparge_ph", round(product->sparge_ph * 100) / 100); + query.bindValue(":sparge_volume", round(product->sparge_volume * 10) / 10); + query.bindValue(":sparge_source", product->sparge_source); + query.bindValue(":sparge_acid_type", product->sparge_acid_type); + query.bindValue(":sparge_acid_perc", round(product->sparge_acid_perc * 10) / 10); + query.bindValue(":sparge_acid_amount", round(product->sparge_acid_amount * 100000) / 100000); + query.bindValue(":mash_ph", round(product->mash_ph * 100) / 100); + query.bindValue(":mash_name", product->mash_name); + query.bindValue(":calc_acid", product->calc_acid ?1:0); + query.bindValue(":w1_name", product->w1_name); + query.bindValue(":w1_amount", round(product->w1_amount * 10) / 10); + query.bindValue(":w1_calcium", round(product->w1_calcium * 100000) / 100000); + query.bindValue(":w1_sulfate", round(product->w1_sulfate * 100000) / 100000); + query.bindValue(":w1_chloride", round(product->w1_chloride * 100000) / 100000); + query.bindValue(":w1_sodium", round(product->w1_sodium * 100000) / 100000); + query.bindValue(":w1_magnesium", round(product->w1_magnesium * 100000) / 100000); + query.bindValue(":w1_total_alkalinity", round(product->w1_total_alkalinity * 100000) / 100000); + query.bindValue(":w1_ph", round(product->w1_ph * 100) / 100); + query.bindValue(":w1_cost", round(product->w1_cost * 100) / 100); + query.bindValue(":w2_name", product->w2_name); + query.bindValue(":w2_amount", round(product->w2_amount * 10) / 10); + query.bindValue(":w2_calcium", round(product->w2_calcium * 100000) / 100000); + query.bindValue(":w2_sulfate", round(product->w2_sulfate * 100000) / 100000); + query.bindValue(":w2_chloride", round(product->w2_chloride * 100000) / 100000); + query.bindValue(":w2_sodium", round(product->w2_sodium * 100000) / 100000); + query.bindValue(":w2_magnesium", round(product->w2_magnesium * 100000) / 100000); + query.bindValue(":w2_total_alkalinity", round(product->w2_total_alkalinity * 100000) / 100000); + query.bindValue(":w2_ph", round(product->w2_ph * 100) / 100); + query.bindValue(":w2_cost", round(product->w2_cost * 100) / 100); + query.bindValue(":wg_amount", round(product->wg_amount * 10) / 10); + query.bindValue(":wg_calcium", round(product->wg_calcium * 100000) / 100000); + query.bindValue(":wg_sulfate", round(product->wg_sulfate * 100000) / 100000); + query.bindValue(":wg_chloride", round(product->wg_chloride * 100000) / 100000); + query.bindValue(":wg_sodium", round(product->wg_sodium * 100000) / 100000); + query.bindValue(":wg_magnesium", round(product->wg_magnesium * 100000) / 100000); + query.bindValue(":wg_total_alkalinity", round(product->wg_total_alkalinity * 100000) / 100000); + query.bindValue(":wg_ph", round(product->wg_ph * 100) / 100); + query.bindValue(":wb_calcium", round(product->wb_calcium * 100000) / 100000); + query.bindValue(":wb_sulfate", round(product->wb_sulfate * 100000) / 100000); + query.bindValue(":wb_chloride", round(product->wb_chloride * 100000) / 100000); + query.bindValue(":wb_sodium", round(product->wb_sodium * 100000) / 100000); + query.bindValue(":wb_magnesium", round(product->wb_magnesium * 100000) / 100000); + query.bindValue(":wb_total_alkalinity", round(product->wb_total_alkalinity * 100000) / 100000); + query.bindValue(":wb_ph", round(product->wb_ph * 100) / 100); + query.bindValue(":wa_acid_name", product->wa_acid_name); + query.bindValue(":wa_acid_perc", round(product->wa_acid_perc * 10) / 10); + query.bindValue(":wa_base_name", product->wa_base_name); + + if (product->fermentables.size() == 0) { + query.bindValue(":json_fermentables", "[]"); + } else { + QJsonArray array; + for (int i = 0; i < product->fermentables.size(); i++) { + QJsonObject obj; + obj.insert("f_name", product->fermentables.at(i).f_name); + obj.insert("f_origin", product->fermentables.at(i).f_origin); + obj.insert("f_supplier", product->fermentables.at(i).f_supplier); + obj.insert("f_amount", round(product->fermentables.at(i).f_amount * 10000) / 10000); + obj.insert("f_cost", round(product->fermentables.at(i).f_cost * 1000) / 1000); + obj.insert("f_type", product->fermentables.at(i).f_type); + obj.insert("f_yield", round(product->fermentables.at(i).f_yield * 10) / 10); + obj.insert("f_color", round(product->fermentables.at(i).f_color * 10) / 10); + obj.insert("f_coarse_fine_diff", round(product->fermentables.at(i).f_coarse_fine_diff * 10) / 10); + obj.insert("f_moisture", round(product->fermentables.at(i).f_moisture * 10) / 10); + obj.insert("f_diastatic_power", round(product->fermentables.at(i).f_diastatic_power * 100000) / 100000); + obj.insert("f_protein", round(product->fermentables.at(i).f_protein * 10) / 10); + obj.insert("f_dissolved_protein", round(product->fermentables.at(i).f_dissolved_protein * 10) / 10); + obj.insert("f_max_in_batch", product->fermentables.at(i).f_max_in_batch); + obj.insert("f_graintype", product->fermentables.at(i).f_graintype); + obj.insert("f_added", product->fermentables.at(i).f_added); + obj.insert("f_recommend_mash", product->fermentables.at(i).f_recommend_mash ? 1:0); + obj.insert("f_add_after_boil", product->fermentables.at(i).f_add_after_boil ? 1:0); + obj.insert("f_adjust_to_total_100", product->fermentables.at(i).f_adjust_to_total_100 ? 1:0); + obj.insert("f_percentage", round(product->fermentables.at(i).f_percentage * 10) / 10); + obj.insert("f_di_ph", round(product->fermentables.at(i).f_di_ph * 100000) / 100000); + obj.insert("f_acid_to_ph_57", round(product->fermentables.at(i).f_acid_to_ph_57 * 100000) / 100000); + qDebug() << "fermentables_Json" << i << obj; + array.append(obj); /* Append this object */ + } + QJsonDocument doc; + doc.setArray(array); +// qDebug() << doc.toJson(QJsonDocument::Compact); + query.bindValue(":json_fermentables", doc.toJson(QJsonDocument::Compact)); + } + + if (product->hops.size() == 0) { + query.bindValue(":json_hops", "[]"); + } else { + QJsonArray array; + for (int i = 0; i < product->hops.size(); i++) { + QJsonObject obj; + obj.insert("h_name", product->hops.at(i).h_name); + obj.insert("h_origin", product->hops.at(i).h_origin); + obj.insert("h_amount", round(product->hops.at(i).h_amount * 10000) / 10000); + obj.insert("h_cost", round(product->hops.at(i).h_cost * 100) / 100); + obj.insert("h_type", product->hops.at(i).h_type); + obj.insert("h_form", product->hops.at(i).h_form); + obj.insert("h_useat", product->hops.at(i).h_useat); + obj.insert("h_time", round(product->hops.at(i).h_time)); + obj.insert("h_alpha", round(product->hops.at(i).h_alpha * 100) / 100); + obj.insert("h_beta", round(product->hops.at(i).h_beta * 100) / 100); + obj.insert("h_hsi", round(product->hops.at(i).h_hsi * 100) / 100); + obj.insert("h_humulene", round(product->hops.at(i).h_humulene * 100) / 100); + obj.insert("h_caryophyllene", round(product->hops.at(i).h_caryophyllene * 100) / 100); + obj.insert("h_cohumulone", round(product->hops.at(i).h_cohumulone * 100) / 100); + obj.insert("h_myrcene", round(product->hops.at(i).h_myrcene * 100) / 100); + obj.insert("h_total_oil", round(product->hops.at(i).h_total_oil * 100) / 100); + qDebug() << "hops_Json" << i << obj; + array.append(obj); /* Append this object */ + } + QJsonDocument doc; + doc.setArray(array); + query.bindValue(":json_hops", doc.toJson(QJsonDocument::Compact)); + } + + if (product->miscs.size() == 0) { + query.bindValue(":json_miscs", "[]"); + } else { + QJsonArray array; + for (int i = 0; i < product->miscs.size(); i++) { + QJsonObject obj; + obj.insert("m_name", product->miscs.at(i).m_name); + obj.insert("m_amount", round(product->miscs.at(i).m_amount * 10000) / 10000); + obj.insert("m_type", product->miscs.at(i).m_type); + obj.insert("m_use_use", product->miscs.at(i).m_use_use); + obj.insert("m_time", round(product->miscs.at(i).m_time)); + obj.insert("m_amount_is_weight", product->miscs.at(i).m_amount_is_weight ? 1:0); + obj.insert("m_cost", round(product->miscs.at(i).m_cost * 10000) / 10000); + qDebug() << "miscs_Json" << i << obj; + array.append(obj); /* Append this object */ + } + QJsonDocument doc; + doc.setArray(array); +// qDebug() << doc.toJson(QJsonDocument::Compact); + query.bindValue(":json_miscs", doc.toJson(QJsonDocument::Compact)); + } + + if (product->yeasts.size() == 0) { + query.bindValue(":json_yeasts", "[]"); + } else { + QJsonArray array; + for (int i = 0; i < product->yeasts.size(); i++) { + QJsonObject obj; + obj.insert("y_name", product->yeasts.at(i).y_name); + obj.insert("y_laboratory", product->yeasts.at(i).y_laboratory); + obj.insert("y_product_id", product->yeasts.at(i).y_product_id); + obj.insert("y_amount", round(product->yeasts.at(i).y_amount * 10000) / 10000); + obj.insert("y_type", product->yeasts.at(i).y_type); + obj.insert("y_form", product->yeasts.at(i).y_form); + obj.insert("y_min_temperature", round(product->yeasts.at(i).y_min_temperature * 10) / 10); + obj.insert("y_max_temperature", round(product->yeasts.at(i).y_max_temperature * 10) / 10); + obj.insert("y_flocculation", product->yeasts.at(i).y_flocculation); + obj.insert("y_attenuation", round(product->yeasts.at(i).y_attenuation * 10) / 10); + obj.insert("y_cells", product->yeasts.at(i).y_cells); + obj.insert("y_tolerance", round(product->yeasts.at(i).y_tolerance * 10) / 10); + obj.insert("y_inventory", round(product->yeasts.at(i).y_inventory * 10000) / 10000); + obj.insert("y_use", product->yeasts.at(i).y_use); + obj.insert("y_sta1", product->yeasts.at(i).y_sta1 ? 1:0); + obj.insert("y_bacteria", product->yeasts.at(i).y_bacteria ? 1:0); + obj.insert("y_harvest_top", product->yeasts.at(i).y_harvest_top ? 1:0); + obj.insert("y_harvest_time", product->yeasts.at(i).y_harvest_time); + obj.insert("y_pitch_temperature", round(product->yeasts.at(i).y_pitch_temperature * 10) / 10); + obj.insert("y_pofpos", product->yeasts.at(i).y_pofpos ? 1:0); + obj.insert("y_zymocide", product->yeasts.at(i).y_zymocide); + obj.insert("y_gr_hl_lo", product->yeasts.at(i).y_gr_hl_lo); + obj.insert("y_sg_lo", round(product->yeasts.at(i).y_sg_lo * 1000) / 1000); + obj.insert("y_gr_hl_hi", product->yeasts.at(i).y_gr_hl_hi); + obj.insert("y_sg_hi", round(product->yeasts.at(i).y_sg_hi * 1000) / 1000); + obj.insert("y_cost", round(product->yeasts.at(i).y_cost * 1000) / 1000); + qDebug() << "yeasts_Json" << i << obj; + array.append(obj); /* Append this object */ + } + QJsonDocument doc; + doc.setArray(array); +// qDebug() << doc.toJson(QJsonDocument::Compact); + query.bindValue(":json_yeasts", doc.toJson(QJsonDocument::Compact)); + } + + if (product->mashs.size() == 0) { + query.bindValue(":json_mashs", "[]"); + qDebug() << "Saved empty mashs"; + } else { + QJsonArray array; + for (int i = 0; i < product->mashs.size(); i++) { + QJsonObject obj; + obj.insert("step_name", product->mashs.at(i).step_name); + obj.insert("step_type", product->mashs.at(i).step_type); + obj.insert("step_volume", round(product->mashs.at(i).step_volume * 100) / 100); + obj.insert("step_infuse_amount", round(product->mashs.at(i).step_infuse_amount * 100) / 100); + obj.insert("step_infuse_temp", round(product->mashs.at(i).step_infuse_temp * 100) / 100); + obj.insert("step_temp", round(product->mashs.at(i).step_temp * 100) / 100); + obj.insert("step_time", round(product->mashs.at(i).step_time * 100) / 100); + obj.insert("ramp_time", round(product->mashs.at(i).ramp_time * 100) / 100); + obj.insert("end_temp", round(product->mashs.at(i).end_temp * 100) / 100); + obj.insert("step_wg_ratio", round(product->mashs.at(i).step_wg_ratio * 100) / 100); + qDebug() << "mashs_Json" << i << obj; + array.append(obj); /* Append this object */ + } + QJsonDocument doc; + doc.setArray(array); + qDebug() << doc.toJson(QJsonDocument::Compact); + query.bindValue(":json_mashs", doc.toJson(QJsonDocument::Compact)); + } + + if (this->recno == -1) { + query.bindValue(":uuid", QUuid::createUuid().toString().mid(1, 36)); + } else { + query.bindValue(":recno", this->recno); + } + query.exec(); + qDebug() << query.lastQuery(); + if (query.lastError().isValid()) { + qDebug() << "EditProduct" << query.lastError(); + QMessageBox::warning(this, tr("Database error"), + tr("MySQL error: %1\n%2\n%3") + .arg(query.lastError().nativeErrorCode()) + .arg(query.lastError().driverText()) + .arg(query.lastError().databaseText())); + } else { + /* + * If this was a new product, find out what record number we + * have got and set it. So when the user saves this record + * again, it will be updated instead of inserting a new copy. + */ + if (this->recno < 0) { + QVariant id = query.lastInsertId(); + this->recno = product->record = id.toInt(); + qDebug() << "EditProduct Inserted record" << this->recno; + } else { + qDebug() << "EditProduct Updated record" << this->recno; + } + } + } + + ui->saveButton->setEnabled(false); + this->textIsChanged = false; + WindowTitle(); +} + + +#include "EditProductTab1.cpp" +#include "EditProductTab2.cpp" +#include "EditProductTab3.cpp" +#include "EditProductTab4.cpp" +#include "EditProductTab5.cpp" +#include "EditProductTab6.cpp" +#include "EditProductTab7.cpp" +#include "EditProductTab8.cpp" +#include "EditProductTab9.cpp" +#include "EditProductTab10.cpp" +#include "EditProductTab11.cpp" +#include "EditProductExport.cpp" + + +void EditProduct::on_deleteButton_clicked() +{ + QSqlQuery query; + + int rc = QMessageBox::warning(this, tr("Delete product"), tr("Delete %1").arg(product->name), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + if (rc == QMessageBox::No) + return; + + query.prepare("DELETE FROM products WHERE record = :recno"); + query.bindValue(":recno", this->recno); + query.exec(); + if (query.lastError().isValid()) { + qDebug() << "EditProduct" << query.lastError(); + QMessageBox::warning(this, tr("Database error"), + tr("MySQL error: %1\n%2\n%3") + .arg(query.lastError().nativeErrorCode()) + .arg(query.lastError().driverText()) + .arg(query.lastError().databaseText())); + } else { + qDebug() << "EditProduct Deleted" << this->recno; + } + + this->close(); + this->setResult(1); +} + + +void EditProduct::is_changed() +{ + ui->saveButton->setEnabled(true); + ui->deleteButton->setEnabled(((this->recno >= 0) ? true:false) && ! product->locked); + this->textIsChanged = true; + WindowTitle(); +} + + +void EditProduct::on_quitButton_clicked() +{ + if (this->textIsChanged) { + int rc = QMessageBox::warning(this, tr("Product changed"), tr("The product has been modified. Save changes?"), + QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Save); + switch (rc) { + case QMessageBox::Save: + on_saveButton_clicked(); + break; /* Saved and then Quit */ + case QMessageBox::Discard: + break; /* Quit without Save */ + case QMessageBox::Cancel: + return; /* Return to the editor page */ + } + } + + this->close(); + this->setResult(1); +} diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 src/EditProduct.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/EditProduct.h Thu Apr 28 22:49:13 2022 +0200 @@ -0,0 +1,187 @@ +#ifndef _EDITPRODUCT_H +#define _EDITPRODUCT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "global.h" + +namespace Ui { +class EditProduct; +} + +class EditProduct : public QDialog +{ + Q_OBJECT + +signals: + void entry_changed(); + +public: + explicit EditProduct(int id, QWidget *parent = 0); + ~EditProduct(); + +private slots: + void on_saveButton_clicked(); + void on_quitButton_clicked(); + void on_deleteButton_clicked(); + void on_exportButton_clicked(); + void on_printButton_clicked(); + void is_changed(); + void name_changed(QString); + void notes_changed(); + void style_changed(int val); + void colormethod_changed(int val); + void ibumethod_changed(int val); + void est_og_changed(double val); + void efficiency_changed(double val); + void boil_time_changed(int val); + void batch_size_changed(double val); + void brew_type_changed(int val); + void locked_changed(bool val); + void refreshFermentables(); + void refreshHops(); + void refreshMiscs(); + void refreshYeasts(); + void refreshMashs(); + void refreshWaters(); + void refreshAll(); + void ferment_amount_changed(double val); + void ferment_pct_changed(double val); + void ferment_to100_changed(bool val); + void ferment_select_changed(int val); + void ferment_instock_changed(bool val); + void ferment_added_changed(int val); + void hop_amount_changed(double val); + void hop_time_changed(int val); + void hop_select_changed(int val); + void hop_instock_changed(bool val); + void hop_useat_changed(int val); + void misc_amount_changed(double val); + void misc_time_changed(int val); + void misc_select_changed(int val); + void misc_instock_changed(bool val); + void misc_useat_changed(int val); + void yeast_amount_changed(double val); + void yeast_select_changed(int val); + void yeast_instock_changed(bool val); + void yeast_useat_changed(int val); + void addFermentRow_clicked(); + void deleteFermentRow_clicked(); + void editFermentRow_clicked(); + void addHopRow_clicked(); + void deleteHopRow_clicked(); + void editHopRow_clicked(); + void addMiscRow_clicked(); + void deleteMiscRow_clicked(); + void editMiscRow_clicked(); + void addYeastRow_clicked(); + void deleteYeastRow_clicked(); + void editYeastRow_clicked(); + void addMashRow_clicked(); + void deleteMashRow_clicked(); + void editMashRow_clicked(); + void upMashRow_clicked(); + void downMashRow_clicked(); + + void w1_name_changed(int val); + void w2_name_changed(int val); + void w2_volume_changed(double val); + void wb_cacl2_changed(double val); + void wb_caso4_changed(double val); + void wb_mgso4_changed(double val); + void wb_nacl_changed(double val); + void wb_mgcl2_changed(double val); + void wb_nahco3_changed(double val); + void wb_caco3_changed(double val); + void mw_calc_acid_clicked(); + void wt_target_changed(int val); + void mw_ph_changed(double val); + void mw_acid_changed(double val); + void mw_type_changed(int val); + void sp_source_changed(int val); + void sp_type_changed(int val); + void sp_ph_changed(double val); + void step_name_changed(QString val); + void step_type_changed(int val); + void step_temp_changed(double val); + void end_temp_changed(double val); + void step_time_changed(double val); + void ramp_time_changed(double val); + void infuse_changed(double val); + void mash_select_changed(int val); + void mash_name_changed(QString); + + /* Modified progress bars */ + void ferment_perc_mash_valueChanged(int value); + void ferment_perc_sugars_valueChanged(int value); + void ferment_perc_cara_valueChanged(int value); + void ferment_lintner_valueChanged(int value); + void hop_Flavour_valueChanged(int value); + void hop_Aroma_valueChanged(int value); + +private: + Ui::EditProduct *ui; + QString bar_red = "QProgressBar::chunk {background: #FF0000;}"; + QString bar_orange = "QProgressBar::chunk {background: #EB7331;}"; + QString bar_green = "QProgressBar::chunk {background: #008C00;}"; + QString bar_20 = "QProgressBar::chunk {background: #004D00;}"; + QString bar_40 = "QProgressBar::chunk {background: #008C00;}"; + QString bar_60 = "QProgressBar::chunk {background: #00BF00;}"; + QString bar_80 = "QProgressBar::chunk {background: #00FF00;}"; + QString bar_100 = "QProgressBar::chunk {background: #80FF80;}"; + int recno; + bool textIsChanged = false; + /* + * Variables for popup ingredients editing. + */ + QComboBox *fselectEdit, *faddedEdit, *hselectEdit,*haddedEdit, *useatEdit, *mselectEdit, *yselectEdit; + QLineEdit *fnameEdit, *fsupplierEdit, *hnameEdit, *horiginEdit, *mnameEdit, *ynameEdit, *ylaboratoryEdit, *yproduct_idEdit, *stepnameEdit; + QDoubleSpinBox *famountEdit, *pctEdit, *fmaxEdit, *hamountEdit, *ibuEdit, *mamountEdit, *yamountEdit; + QDoubleSpinBox *steptempEdit, *endtempEdit, *steptimeEdit, *ramptimeEdit, *stepivolEdit, *stepitmpEdit; + QSpinBox *htimeEdit, *mtimeEdit; + QCheckBox *to100Edit, *finstockEdit, *hinstockEdit, *minstockEdit, *yinstockEdit; + QLabel *htimeLabel, *mtimeLabel, *mamountLabel, *yamountLabel, *ivolLabel, *itmpLabel; + + void to100Fermentables(int row); + static bool ferment_sort_test(const Fermentables &D1, const Fermentables &D2); + static bool hop_sort_test(const Hops &D1, const Hops &D2); + static bool misc_sort_test(const Miscs &D1, const Miscs &D2); + static bool yeast_sort_test(const Yeasts &D1, const Yeasts &D2); + void WindowTitle(); + void setLocked(bool val); + void brewing_salt_sub(QString salt, double val); + void set_brewing_salt(QString salt, double val); + void calcFermentables(); + void calcFermentablesFromOG(double og); + void calcIBUs(); + void adjustHops(double factor); + void adjustMiscs(double factor); + double ZAlkalinity(double pHZ); + double ZRA(double pHZ); + double BufferCapacity(Fermentables F); + double AcidRequired(double ZpH, Fermentables F); + double ProtonDeficit(double pHZ); + double MashpH(); + void calcWater(); + void calcSparge(); + double GetBUGU(); + double GetOptSO4Clratio(); + void calcYeast(); + void adjustYeasts(double factor); + double infusionVol(double step_infused, double step_mashkg, double infuse_temp, double step_temp, double last_temp); + double decoctionVol(double step_volume, double step_temp, double prev_temp); + void calcMash(); + void adjustWaters(double factor); +}; + +#endif diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 src/EditProductExport.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/EditProductExport.cpp Thu Apr 28 22:49:13 2022 +0200 @@ -0,0 +1,270 @@ +/** + * EditProduct.cpp is part of bmsapp. + * + * Export product. + * + * 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 . + */ + + +void EditProduct::on_exportButton_clicked() +{ + qDebug() << "export"; + + const QStringList styletype({ "Lager", "Ale", "Mead", "Wheat", "Mixed", "Cider" }); + const QStringList producttypes({ "Extract", "Partial Mash", "All Grain" }); + const QStringList color_method({ "Morey", "Mosher", "Daniels", "Halberstadt", "Naudts" }); + const QStringList ibu_method({ "Tinseth", "Rager", "Daniels", "Garetz", "Mosher", "Noonan" }); + const QStringList hop_types({ "Bittering", "Aroma", "Both" }); + const QStringList hop_forms({ "Pellet", "Plug", "Leaf", "Leaf", "Pellet" }); + /* "Leaf Wet", "Cryo" */ + /* We use more hop forms then beerxml knows about, so we send known names */ + /* instead of what we internally use. */ + const QStringList hop_use({ "Mash", "First wort", "Boil", "Aroma", "Whirlpool", "Dry hop" }); + const QStringList fermentable_type({ "Grain", "Sugar", "Extract", "Dry extract", "Adjunct" }); + const QStringList fermentable_graintype({ "Base", "Roast", "Crystal", "Kilned", "Sour malt", "Special", "No malt" }); + const QStringList yeast_type({ "Lager", "Ale", "Wheat", "Wine", "Champagne", "Other", "Other", "Other", "Other", "Other" }); + const QStringList yeast_form({ "Liquid", "Dry", "Slant", "Culture", "Frozen", "Bottle", "Dry" }); + const QStringList yeast_use({ "Primary", "Secondary", "Tertiary", "Bottle" }); + const QStringList misc_type({ "Spice", "Herb", "Flavor", "Fining", "Water agent", "Yeast nutrient", "Other" }); + const QStringList misc_use({ "Starter", "Mash", "Boil", "Primary", "Secondary", "Bottling" }); + const QStringList step_type({ "Infusion", "Temperature", "Decoction" }); + + QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), QDir::homePath() + "/" + product->name + ".xml", tr("Files (*.xml)")); + if (fileName == 0) { + QMessageBox::warning(this, tr("Save File"), tr("No XML file selected.")); + return; + } + + QFile file(fileName); + file.open(QIODevice::WriteOnly); + + QXmlStreamWriter *xmlWriter = new QXmlStreamWriter(&file); + xmlWriter->writeStartDocument(); + xmlWriter->setAutoFormatting(true); + xmlWriter->setAutoFormattingIndent(1); + + xmlWriter->writeStartElement("RECIPES"); + xmlWriter->writeStartElement("RECIPE"); + /* + * Product basics + */ + xmlWriter->writeTextElement("VERSION", "1"); + xmlWriter->writeTextElement("NAME", product->name); + if (product->notes != "") + xmlWriter->writeTextElement("NOTES", product->notes); + xmlWriter->writeTextElement("TYPE", producttypes[product->type]); + xmlWriter->writeTextElement("BREWER", "Anonymous"); + xmlWriter->writeTextElement("BATCH_SIZE", QString::number(product->batch_size, 'f', 4)); + xmlWriter->writeTextElement("BOIL_SIZE", QString::number(product->boil_size, 'f', 4)); + xmlWriter->writeTextElement("BOIL_TIME", QString::number(product->boil_time, 'f', 3)); + xmlWriter->writeTextElement("EFFICIENCY", QString::number(product->efficiency, 'f', 4)); + xmlWriter->writeTextElement("EST_OG", QString::number(product->est_og, 'f', 3)); + xmlWriter->writeTextElement("EST_FG", QString::number(product->est_fg, 'f', 3)); + if (product->est_abv > 0) + xmlWriter->writeTextElement("EST_ABV", QString::number(product->est_abv, 'f', 1)); + if (product->est_color > 0) { + xmlWriter->writeTextElement("EST_COLOR", QString::number(Utils::ebc_to_srm(product->est_color), 'f', 6)); + xmlWriter->writeTextElement("COLOR_METHOD", color_method[product->color_method]); + } + if (product->est_ibu > 0) { + xmlWriter->writeTextElement("EST_IBU", QString::number(product->est_ibu, 'f', 1)); + xmlWriter->writeTextElement("IBU_METHOD", ibu_method[product->ibu_method]); + } + + xmlWriter->writeStartElement("STYLE"); + xmlWriter->writeTextElement("VERSION", "1"); + xmlWriter->writeTextElement("NAME", product->st_name); + xmlWriter->writeTextElement("CATEGORY", product->st_category); + xmlWriter->writeTextElement("CATEGORY_NUMBER", QString::number(product->st_category_number, 'f', 0)); + xmlWriter->writeTextElement("STYLE_LETTER", product->st_letter); + xmlWriter->writeTextElement("STYLE_GUIDE", product->st_guide); + xmlWriter->writeTextElement("TYPE", styletype[product->st_type]); + xmlWriter->writeTextElement("OG_MIN", QString::number(product->st_og_min, 'f', 3)); + xmlWriter->writeTextElement("OG_MAX", QString::number(product->st_og_max, 'f', 3)); + xmlWriter->writeTextElement("FG_MIN", QString::number(product->st_fg_min, 'f', 3)); + xmlWriter->writeTextElement("FG_MAX", QString::number(product->st_fg_max, 'f', 3)); + xmlWriter->writeTextElement("IBU_MIN", QString::number(product->st_ibu_min, 'f', 0)); + xmlWriter->writeTextElement("IBU_MAX", QString::number(product->st_ibu_max, 'f', 0)); + xmlWriter->writeTextElement("COLOR_MIN", QString::number(Utils::ebc_to_srm(product->st_color_min), 'f', 2)); + xmlWriter->writeTextElement("COLOR_MAX", QString::number(Utils::ebc_to_srm(product->st_color_max), 'f', 2)); + xmlWriter->writeTextElement("CARB_MIN", QString::number(product->st_carb_min, 'f', 1)); + xmlWriter->writeTextElement("CARB_MAX", QString::number(product->st_carb_max, 'f', 1)); + xmlWriter->writeTextElement("ABV_MIN", QString::number(product->st_abv_min, 'f', 1)); + xmlWriter->writeTextElement("ABV_MAX", QString::number(product->st_abv_max, 'f', 1)); + xmlWriter->writeEndElement(); // STYLE + + xmlWriter->writeStartElement("EQUIPMENT"); + xmlWriter->writeTextElement("VERSION", "1"); + xmlWriter->writeTextElement("NAME", "Dummy Brewery"); + xmlWriter->writeTextElement("BATCH_SIZE", QString::number(product->batch_size, 'f', 2)); + xmlWriter->writeTextElement("BOIL_SIZE", QString::number(product->boil_size, 'f', 2)); + xmlWriter->writeTextElement("BOIL_TIME", QString::number(product->boil_time, 'f', 0)); + xmlWriter->writeEndElement(); // EQUIPMENT + + xmlWriter->writeStartElement("HOPS"); + for (int i = 0; i < product->hops.size(); i++) { + xmlWriter->writeStartElement("HOP"); + xmlWriter->writeTextElement("VERSION", "1"); + xmlWriter->writeTextElement("NAME", product->hops.at(i).h_name); + xmlWriter->writeTextElement("ALPHA", QString::number(product->hops.at(i).h_alpha, 'f', 1)); + xmlWriter->writeTextElement("AMOUNT", QString::number(product->hops.at(i).h_amount, 'f', 4)); + xmlWriter->writeTextElement("USE", hop_use[product->hops.at(i).h_useat]); + xmlWriter->writeTextElement("TIME", QString::number(product->hops.at(i).h_time, 'f', 0)); + xmlWriter->writeTextElement("TYPE", hop_types[product->hops.at(i).h_type]); + xmlWriter->writeTextElement("FORM", hop_forms[product->hops.at(i).h_form]); + xmlWriter->writeTextElement("BETA", QString::number(product->hops.at(i).h_beta, 'f', 1)); + xmlWriter->writeTextElement("HSI", QString::number(product->hops.at(i).h_hsi, 'f', 1)); + xmlWriter->writeTextElement("ORIGIN", product->hops.at(i).h_origin); + xmlWriter->writeEndElement(); + } + xmlWriter->writeEndElement(); // HOPS + + xmlWriter->writeStartElement("FERMENTABLES"); + for (int i = 0; i < product->fermentables.size(); i++) { + xmlWriter->writeStartElement("FERMENTABLE"); + xmlWriter->writeTextElement("VERSION", "1"); + xmlWriter->writeTextElement("NAME", product->fermentables.at(i).f_name); + xmlWriter->writeTextElement("TYPE", fermentable_type[product->fermentables.at(i).f_type]); + xmlWriter->writeTextElement("AMOUNT", QString::number(product->fermentables.at(i).f_amount, 'f', 4)); + xmlWriter->writeTextElement("YIELD", QString::number(product->fermentables.at(i).f_yield, 'f', 1)); + xmlWriter->writeTextElement("COLOR", QString::number(Utils::ebc_to_srm(product->fermentables.at(i).f_color), 'f', 1)); + xmlWriter->writeTextElement("ADD_AFTER_BOIL", product->fermentables.at(i).f_add_after_boil ? "TRUE":"FALSE"); + xmlWriter->writeTextElement("ORIGIN", product->fermentables.at(i).f_origin); + xmlWriter->writeTextElement("SUPPLIER", product->fermentables.at(i).f_supplier); + if (product->fermentables.at(i).f_coarse_fine_diff) + xmlWriter->writeTextElement("COARSE_FINE_DIFF", QString::number(product->fermentables.at(i).f_coarse_fine_diff, 'f', 4)); + if (product->fermentables.at(i).f_moisture) + xmlWriter->writeTextElement("MOISTURE", QString::number(product->fermentables.at(i).f_moisture, 'f', 4)); + if (product->fermentables.at(i).f_diastatic_power) + xmlWriter->writeTextElement("DIASTATIC_POWER", QString::number(product->fermentables.at(i).f_diastatic_power, 'f', 4)); + if (product->fermentables.at(i).f_protein) + xmlWriter->writeTextElement("PROTEIN", QString::number(product->fermentables.at(i).f_protein, 'f', 4)); + if (product->fermentables.at(i).f_max_in_batch) + xmlWriter->writeTextElement("MAX_IN_BATCH", QString::number(product->fermentables.at(i).f_max_in_batch, 'f', 1)); + xmlWriter->writeTextElement("RECOMMEND_MASH", product->fermentables.at(i).f_recommend_mash ? "TRUE":"FALSE"); + xmlWriter->writeTextElement("GRAINTYPE", fermentable_graintype[product->fermentables.at(i).f_graintype]); + xmlWriter->writeEndElement(); + } + xmlWriter->writeEndElement(); // FERMENTABLES + + xmlWriter->writeStartElement("MISCS"); + for (int i = 0; i < product->miscs.size(); i++) { + xmlWriter->writeStartElement("MISC"); + xmlWriter->writeTextElement("VERSION", "1"); + xmlWriter->writeTextElement("NAME", product->miscs.at(i).m_name); + xmlWriter->writeTextElement("TYPE", misc_type[product->miscs.at(i).m_type]); + xmlWriter->writeTextElement("AMOUNT", QString::number(product->miscs.at(i).m_amount, 'f', 5)); + xmlWriter->writeTextElement("AMOUNT_IS_WEIGHT", product->miscs.at(i).m_amount_is_weight ? "TRUE":"FALSE"); + xmlWriter->writeTextElement("USE", misc_use[product->miscs.at(i).m_use_use]); + xmlWriter->writeTextElement("TIME", QString::number(product->miscs.at(i).m_time, 'f', 0)); + xmlWriter->writeEndElement(); + } + xmlWriter->writeEndElement(); // MISCS + + xmlWriter->writeStartElement("YEASTS"); + for (int i = 0; i < product->yeasts.size(); i++) { + xmlWriter->writeStartElement("YEAST"); + xmlWriter->writeTextElement("VERSION", "1"); + xmlWriter->writeTextElement("NAME", product->yeasts.at(i).y_name); + xmlWriter->writeTextElement("TYPE", yeast_type[product->yeasts.at(i).y_type]); + xmlWriter->writeTextElement("FORM", yeast_form[product->yeasts.at(i).y_form]); + xmlWriter->writeTextElement("AMOUNT", QString::number(product->yeasts.at(i).y_amount, 'f', 5)); + xmlWriter->writeTextElement("AMOUNT_IS_WEIGHT", (product->yeasts.at(i).y_form == 1) ? "TRUE":"FALSE"); + xmlWriter->writeTextElement("LABORATORY", product->yeasts.at(i).y_laboratory); + xmlWriter->writeTextElement("PRODUCT_ID", product->yeasts.at(i).y_product_id); + xmlWriter->writeTextElement("MIN_TEMPERATURE", QString::number(product->yeasts.at(i).y_min_temperature, 'f', 1)); + xmlWriter->writeTextElement("MAX_TEMPERATURE", QString::number(product->yeasts.at(i).y_max_temperature, 'f', 1)); + xmlWriter->writeTextElement("ATTENUATION", QString::number(product->yeasts.at(i).y_attenuation, 'f', 1)); + xmlWriter->writeTextElement("ADD_TO_SECONDARY", (product->yeasts.at(i).y_use == 0) ? "FALSE":"TRUE"); + xmlWriter->writeEndElement(); + } + xmlWriter->writeEndElement(); // YEASTS + + xmlWriter->writeStartElement("WATERS"); + if (product->w1_amount > 0) { + xmlWriter->writeStartElement("WATER"); + xmlWriter->writeTextElement("VERSION", "1"); + xmlWriter->writeTextElement("NAME", product->w1_name); + xmlWriter->writeTextElement("AMOUNT", QString::number(product->w1_amount, 'f', 2)); + xmlWriter->writeTextElement("CALCIUM", QString::number(product->w1_calcium, 'f', 2)); + xmlWriter->writeTextElement("MAGNESIUM", QString::number(product->w1_magnesium, 'f', 2)); + xmlWriter->writeTextElement("BICARBONATE", QString::number(product->w1_total_alkalinity * 1.22, 'f', 2)); + xmlWriter->writeTextElement("SULFATE", QString::number(product->w1_sulfate, 'f', 2)); + xmlWriter->writeTextElement("CHLORIDE", QString::number(product->w1_chloride, 'f', 2)); + xmlWriter->writeTextElement("SODIUM", QString::number(product->w1_sodium, 'f', 2)); + xmlWriter->writeTextElement("PH", QString::number(product->w1_ph, 'f', 2)); + xmlWriter->writeTextElement("TOTAL_ALKALINITY", QString::number(product->w1_total_alkalinity, 'f', 2)); + xmlWriter->writeEndElement(); + if (product->w2_amount > 0) { + xmlWriter->writeStartElement("WATER"); + xmlWriter->writeTextElement("VERSION", "1"); + xmlWriter->writeTextElement("NAME", product->w2_name); + xmlWriter->writeTextElement("AMOUNT", QString::number(product->w2_amount, 'f', 2)); + xmlWriter->writeTextElement("CALCIUM", QString::number(product->w2_calcium, 'f', 2)); + xmlWriter->writeTextElement("MAGNESIUM", QString::number(product->w2_magnesium, 'f', 2)); + xmlWriter->writeTextElement("BICARBONATE", QString::number(product->w2_total_alkalinity * 1.22, 'f', 2)); + xmlWriter->writeTextElement("SULFATE", QString::number(product->w2_sulfate, 'f', 2)); + xmlWriter->writeTextElement("CHLORIDE", QString::number(product->w2_chloride, 'f', 2)); + xmlWriter->writeTextElement("SODIUM", QString::number(product->w2_sodium, 'f', 2)); + xmlWriter->writeTextElement("PH", QString::number(product->w2_ph, 'f', 2)); + xmlWriter->writeTextElement("TOTAL_ALKALINITY", QString::number(product->w2_total_alkalinity, 'f', 2)); + xmlWriter->writeEndElement(); + } + } + xmlWriter->writeEndElement(); // WATERS + + xmlWriter->writeStartElement("MASH"); + xmlWriter->writeTextElement("VERSION", "1"); + xmlWriter->writeTextElement("NAME", product->mash_name); + xmlWriter->writeTextElement("GRAIN_TEMP", "10.0"); + xmlWriter->writeTextElement("PH", QString::number(product->sparge_ph, 'f', 2)); + xmlWriter->writeTextElement("SPARGE_TEMP", QString::number(product->sparge_temp, 'f', 2)); + xmlWriter->writeStartElement("MASH_STEPS"); + for (int i = 0; i < product->mashs.size(); i++) { + xmlWriter->writeStartElement("MASH_STEP"); + xmlWriter->writeTextElement("VERSION", "1"); + xmlWriter->writeTextElement("NAME", product->mashs.at(i).step_name); + xmlWriter->writeTextElement("TYPE", step_type[product->mashs.at(i).step_type]); + if (product->mashs.at(i).step_type == 0) { + xmlWriter->writeTextElement("INFUSE_AMOUNT", QString::number(product->mashs.at(i).step_infuse_amount, 'f', 3)); + xmlWriter->writeTextElement("INFUSE_TEMP", QString::number(product->mashs.at(i).step_infuse_temp, 'f', 3)); + } + if (product->mashs.at(i).step_type == 2) { + xmlWriter->writeTextElement("DECOCTION_AMT", QString::number(product->mashs.at(i).step_infuse_amount, 'f', 3)); + } + xmlWriter->writeTextElement("STEP_TEMP", QString::number(product->mashs.at(i).step_temp, 'f', 1)); + xmlWriter->writeTextElement("STEP_TIME", QString::number(product->mashs.at(i).step_time, 'f', 1)); + xmlWriter->writeTextElement("RAMP_TIME", QString::number(product->mashs.at(i).ramp_time, 'f', 1)); + xmlWriter->writeTextElement("END_TEMP", QString::number(product->mashs.at(i).end_temp, 'f', 1)); + xmlWriter->writeTextElement("PH", QString::number(product->mash_ph, 'f', 1)); + xmlWriter->writeEndElement(); + } + xmlWriter->writeEndElement(); // MASH_STEPS + xmlWriter->writeEndElement(); // MASH + xmlWriter->writeEndElement(); // RECIPE + xmlWriter->writeEndElement(); // RECIPES + xmlWriter->writeEndDocument(); + QMessageBox::information(this, tr("Save File"), tr("XML export ready")); + + file.close(); +} + + +void EditProduct::on_printButton_clicked() +{ + PrinterDialog(PR_RECIPE, -1, this); +} + + diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 src/EditProductTab1.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/EditProductTab1.cpp Thu Apr 28 22:49:13 2022 +0200 @@ -0,0 +1,267 @@ +/** + * EditProduct.cpp is part of bmsapp. + * + * Tab 1, generic settings. + * + * 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 . + */ + + + +void EditProduct::name_changed(QString name) +{ + product->name = name; + is_changed(); +} + + +void EditProduct::notes_changed() +{ + /* The text cannot be passed in a simple way :) */ + product->notes = ui->notesEdit->toPlainText(); + is_changed(); +} + + +/* + * New beerstyle is selected. + */ +void EditProduct::style_changed(int val) +{ + QSqlQuery query; + + query.prepare("SELECT * FROM profile_styles ORDER BY style_guide,style_letter,name"); + query.exec(); + query.first(); + // Skip to the record index. + for (int i = 0; i < (val - 1); i++) { + query.next(); + } + // Set relevant fields and update ranges. + product->st_name = query.value(1).toString(); + product->st_category = query.value(2).toString(); + product->st_category_number = query.value(3).toInt(); + product->st_letter = query.value(4).toString(); + product->st_guide = query.value(5).toString(); + product->st_type = query.value(6).toInt(); + product->st_og_min = query.value(7).toDouble(); + product->st_og_max = query.value(8).toDouble(); + product->st_fg_min = query.value(9).toDouble(); + product->st_fg_max = query.value(10).toDouble(); + product->st_ibu_min = query.value(11).toDouble(); + product->st_ibu_max = query.value(12).toDouble(); + product->st_color_min = query.value(13).toDouble(); + product->st_color_max = query.value(14).toDouble(); + product->st_carb_min = query.value(15).toDouble(); + product->st_carb_max = query.value(16).toDouble(); + product->st_abv_min = query.value(17).toDouble(); + product->st_abv_max = query.value(18).toDouble(); + + ui->st_nameEdit->setText(product->st_name); + ui->st_groupEdit->setText(product->st_letter); + ui->st_guideEdit->setText(product->st_guide); + ui->st_catEdit->setText(product->st_category); + ui->st_catnrEdit->setText(QString("%1").arg(product->st_category_number)); + ui->st_typeEdit->setText(style_types[product->st_type]); + + ui->est_ogShow->setRange(query.value(7).toDouble(), query.value(8).toDouble()); + ui->est_fgShow->setRange(query.value(9).toDouble(), query.value(10).toDouble()); + ui->est_ibuShow->setRange(query.value(11).toDouble(), query.value(12).toDouble()); + ui->est_colorShow->setRange(query.value(13).toDouble(), query.value(14).toDouble()); + ui->est_carbShow->setRange(query.value(15).toDouble(), query.value(16).toDouble()); + ui->est_abvShow->setRange(query.value(17).toDouble(), query.value(18).toDouble()); + + is_changed(); +} + + +void EditProduct::colormethod_changed(int val) +{ + product->color_method = val; + calcFermentables(); + is_changed(); +} + + +void EditProduct::ibumethod_changed(int val) +{ + product->ibu_method = val; + calcIBUs(); + is_changed(); +} + + +void EditProduct::est_og_changed(double val) +{ + product->est_og = val; + calcFermentablesFromOG(val);// Adjust fermentables amounts + calcFermentables(); // Update the product details + calcIBUs(); + calcMash(); + is_changed(); + emit refreshAll(); +} + + +void EditProduct::efficiency_changed(double val) +{ + double estog = product->est_og; + product->efficiency = val; + calcFermentablesFromOG(estog); + calcFermentables(); + calcIBUs(); + is_changed(); + emit refreshAll(); +} + + +void EditProduct::boil_time_changed(int val) +{ + qDebug() << "boil_time_changed" << val; + double new_evap = (0.1 * product->batch_size) * val / 60.0; + product->boil_size = product->batch_size + new_evap; + product->boil_time = val; + ui->boil_sizeEdit->setValue(product->boil_size); + calcFermentables(); + calcIBUs(); + is_changed(); +} + + +void EditProduct::batch_size_changed(double val) +{ + qDebug() << "batch_size_changed" << val << "old" << product->batch_size; + + double evap = (0.1 * val) * product->boil_time / 60.0; + product->boil_size = val + evap; + double factor = val / product->batch_size; + ui->boil_sizeEdit->setValue(product->boil_size); + product->sparge_volume *= factor; + ui->sp_volEdit->setValue(product->sparge_volume); + product->batch_size = val; + calcFermentablesFromOG(product->est_og); // Keep the OG + adjustWaters(factor); + calcFermentables(); + adjustHops(factor); + adjustMiscs(factor); + adjustYeasts(factor); + calcIBUs(); + calcWater(); + //calcSparge(); + calcMash(); + is_changed(); + emit refreshAll(); +} + + +void EditProduct::brew_type_changed(int val) +{ + product->type; + is_changed(); +} + + +void EditProduct::locked_changed(bool val) +{ + qDebug() << "locked_changed" << val; + + product->locked = val; + setLocked(val); + is_changed(); +} + + +void EditProduct::setLocked(bool val) +{ + /* Tab 1, generic */ + ui->typeEdit->setDisabled(val); + ui->color_methodEdit->setDisabled(val); + ui->ibu_methodEdit->setDisabled(val); + ui->beerstyleEdit->setDisabled(val); + ui->nameEdit->setReadOnly(val); + ui->notesEdit->setReadOnly(val); + ui->batch_sizeEdit->setReadOnly(val); + ui->batch_sizeEdit->setButtonSymbols(val ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + ui->boil_sizeEdit->setReadOnly(val); + ui->boil_sizeEdit->setButtonSymbols(val ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + ui->boil_timeEdit->setReadOnly(val); + ui->boil_timeEdit->setButtonSymbols(val ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + ui->efficiencyEdit->setReadOnly(val); + ui->efficiencyEdit->setButtonSymbols(val ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + ui->est_ogEdit->setReadOnly(val); + ui->est_ogEdit->setButtonSymbols(val ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + + /* Tab 2, fermentables */ + ui->est_og2Edit->setReadOnly(val); + ui->est_og2Edit->setButtonSymbols(val ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + ui->addFermentable->setEnabled(! val); + + /* Tab 3, hops */ + ui->addHop->setEnabled(! val); + + /* Tab 4, miscs */ + ui->addMisc->setEnabled(! val); + + /* Tab 5, yeasts */ + ui->addYeast->setEnabled(! val); + + /* Tab 6, mash */ + ui->addMash->setEnabled(! val); + ui->mash_nameEdit->setReadOnly(val); + ui->mash_pickEdit->setDisabled(val); + + /* Tab 7, water */ + ui->sp_sourceEdit->setDisabled(val); + ui->wt_sourceEdit->setDisabled(val); + ui->w1_nameEdit->setDisabled(val); + ui->w2_nameEdit->setDisabled(val); + ui->mw_acidPick->setDisabled(val); + ui->sp_acidtypeEdit->setDisabled(val); + ui->w2_volEdit->setReadOnly(val); + ui->w2_volEdit->setButtonSymbols(val ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + ui->sp_phEdit->setReadOnly(val); + ui->sp_phEdit->setButtonSymbols(val ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + ui->sp_tempEdit->setReadOnly(val); + ui->sp_tempEdit->setButtonSymbols(val ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + ui->sp_volEdit->setReadOnly(val); + ui->sp_volEdit->setButtonSymbols(val ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + ui->bs_cacl2Edit->setReadOnly(val); + ui->bs_cacl2Edit->setButtonSymbols(val ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + ui->bs_caso4Edit->setReadOnly(val); + ui->bs_caso4Edit->setButtonSymbols(val ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + ui->bs_mgso4Edit->setReadOnly(val); + ui->bs_mgso4Edit->setButtonSymbols(val ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + ui->bs_naclEdit->setReadOnly(val); + ui->bs_naclEdit->setButtonSymbols(val ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + ui->bs_mgcl2Edit->setReadOnly(val); + ui->bs_mgcl2Edit->setButtonSymbols(val ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + ui->bs_nahco3Edit->setReadOnly(val); + ui->bs_nahco3Edit->setButtonSymbols(val ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + ui->bs_caco3Edit->setReadOnly(val); + ui->bs_caco3Edit->setButtonSymbols(val ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + if (val) { + ui->mw_phEdit->setReadOnly(true); + ui->mw_phEdit->setButtonSymbols(QAbstractSpinBox::NoButtons); + ui->mw_acidvolEdit->setReadOnly(true); + ui->mw_acidvolEdit->setButtonSymbols(QAbstractSpinBox::NoButtons); + } else { + ui->mw_phEdit->setReadOnly(! product->calc_acid); + ui->mw_phEdit->setButtonSymbols(product->calc_acid ? QAbstractSpinBox::UpDownArrows : QAbstractSpinBox::NoButtons); + ui->mw_acidvolEdit->setReadOnly(product->calc_acid); + ui->mw_acidvolEdit->setButtonSymbols(product->calc_acid ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + } + ui->mw_autoEdit->setDisabled(val); +} + + diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 src/EditProductTab10.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/EditProductTab10.cpp Thu Apr 28 22:49:13 2022 +0200 @@ -0,0 +1,20 @@ +/** + * EditProduct.cpp is part of bmsapp. + * + * Tab 2, equipment settings. + * + * 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 . + */ + + diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 src/EditProductTab11.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/EditProductTab11.cpp Thu Apr 28 22:49:13 2022 +0200 @@ -0,0 +1,20 @@ +/** + * EditProduct.cpp is part of bmsapp. + * + * Tab 11, package + * + * 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 . + */ + + diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 src/EditProductTab12.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/EditProductTab12.cpp Thu Apr 28 22:49:13 2022 +0200 @@ -0,0 +1,20 @@ +/** + * EditProduct.cpp is part of bmsapp. + * + * Tab 12, taste notes. + * + * 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 . + */ + + diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 src/EditProductTab2.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/EditProductTab2.cpp Thu Apr 28 22:49:13 2022 +0200 @@ -0,0 +1,20 @@ +/** + * EditProduct.cpp is part of bmsapp. + * + * Tab 2, equipment settings. + * + * 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 . + */ + + diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 src/EditProductTab3.cpp --- /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 . + */ + + + +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(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(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::of(&QComboBox::currentIndexChanged), this, &EditProduct::ferment_select_changed); + connect(famountEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::ferment_amount_changed); + connect(pctEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::ferment_pct_changed); + connect(faddedEdit, QOverload::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(); +} + + diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 src/EditProductTab4.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/EditProductTab4.cpp Thu Apr 28 22:49:13 2022 +0200 @@ -0,0 +1,636 @@ +/** + * EditProduct.cpp is part of bmsapp. + * + * Tab 4, hops + * + * 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 . + */ + + + +bool EditProduct::hop_sort_test(const Hops &D1, const Hops &D2) +{ + if (D1.h_useat > D2.h_useat) + return false; + if (D1.h_useat < D2.h_useat) + return true; + /* Same useat moments, test time. */ + if (D1.h_time < D2.h_time) + return false; + if (D1.h_time > D2.h_time) + return true; + /* Finally consider the amounts */ + return (D1.h_amount > D2.h_amount); +} + + +void EditProduct::refreshHops() +{ + QString w; + QWidget* pWidget; + QHBoxLayout* pLayout; + QTableWidgetItem *item; + + qDebug() << "refreshHops" << product->hops.size(); + std::sort(product->hops.begin(), product->hops.end(), hop_sort_test); + + const QStringList labels({tr("Origin"), tr("Hop"), tr("Type"), tr("Form"), tr("Alpha"), tr("Use at"), tr("Time"), + tr("IBU"), tr("Amount"), tr("Delete"), tr("Edit") }); + + ui->hopsTable->setColumnCount(11); + ui->hopsTable->setColumnWidth(0, 150); /* Origin */ + ui->hopsTable->setColumnWidth(1, 225); /* Hop */ + ui->hopsTable->setColumnWidth(2, 84); /* Type */ + ui->hopsTable->setColumnWidth(3, 84); /* Form */ + ui->hopsTable->setColumnWidth(4, 75); /* Alpha% */ + ui->hopsTable->setColumnWidth(5, 75); /* Added */ + ui->hopsTable->setColumnWidth(6, 75); /* Time */ + ui->hopsTable->setColumnWidth(7, 60); /* IBU */ + ui->hopsTable->setColumnWidth(8, 90); /* Amount */ + ui->hopsTable->setColumnWidth(9, 80); /* Delete */ + ui->hopsTable->setColumnWidth(10, 80); /* Edit */ + ui->hopsTable->setHorizontalHeaderLabels(labels); + ui->hopsTable->verticalHeader()->hide(); + ui->hopsTable->setRowCount(product->hops.size()); + + for (int i = 0; i < product->hops.size(); i++) { + + ui->hopsTable->setItem(i, 0, new QTableWidgetItem(product->hops.at(i).h_origin)); + ui->hopsTable->setItem(i, 1, new QTableWidgetItem(product->hops.at(i).h_name)); + + item = new QTableWidgetItem(hop_types[product->hops.at(i).h_type]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->hopsTable->setItem(i, 2, item); + + item = new QTableWidgetItem(hop_forms[product->hops.at(i).h_form]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->hopsTable->setItem(i, 3, item); + + item = new QTableWidgetItem(QString("%1%").arg(product->hops.at(i).h_alpha, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->hopsTable->setItem(i, 4, item); + + item = new QTableWidgetItem(hop_useat[product->hops.at(i).h_useat]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->hopsTable->setItem(i, 5, item); + + if (product->hops.at(i).h_useat == 2 || product->hops.at(i).h_useat == 4) { // Boil or whirlpool + item = new QTableWidgetItem(QString("%1 min.").arg(product->hops.at(i).h_time, 1, 'f', 0, '0')); + } else if (product->hops.at(i).h_useat == 5) { // Dry-hop + item = new QTableWidgetItem(QString("%1 days.").arg(product->hops.at(i).h_time / 1440, 1, 'f', 0, '0')); + } else { + item = new QTableWidgetItem(QString("")); + } + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->hopsTable->setItem(i, 6, item); + + double ibu = Utils::toIBU(product->hops.at(i).h_useat, product->hops.at(i).h_form, product->preboil_sg, product->batch_size, product->hops.at(i).h_amount, + product->hops.at(i).h_time, product->hops.at(i).h_alpha, product->ibu_method, 0, product->hops.at(i).h_time, 0); + item = new QTableWidgetItem(QString("%1").arg(ibu, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->hopsTable->setItem(i, 7, item); + + if (product->hops.at(i).h_amount < 1.0) { + item = new QTableWidgetItem(QString("%1 gr").arg(product->hops.at(i).h_amount * 1000.0, 2, 'f', 1, '0')); + } else { + item = new QTableWidgetItem(QString("%1 kg").arg(product->hops.at(i).h_amount, 4, 'f', 3, '0')); + } + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->hopsTable->setItem(i, 8, item); + + /* 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(deleteHopRow_clicked())); + pLayout = new QHBoxLayout(pWidget); + pLayout->addWidget(btn_dele); + pLayout->setContentsMargins(5, 0, 5, 0); + pWidget->setLayout(pLayout); + ui->hopsTable->setCellWidget(i, 9, 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(editHopRow_clicked())); + pLayout = new QHBoxLayout(pWidget); + pLayout->addWidget(btn_edit); + pLayout->setContentsMargins(5, 0, 5, 0); + pWidget->setLayout(pLayout); + ui->hopsTable->setCellWidget(i, 10, pWidget); + } +} + + +void EditProduct::hop_Flavour_valueChanged(int value) +{ + if (value < 20) { + ui->hop_tasteShow->setStyleSheet(bar_20); + ui->hop_tasteShow->setFormat(tr("Very low")); + } else if (value < 40) { + ui->hop_tasteShow->setStyleSheet(bar_40); + ui->hop_tasteShow->setFormat(tr("Low")); + } else if (value < 60) { + ui->hop_tasteShow->setStyleSheet(bar_60); + ui->hop_tasteShow->setFormat(tr("Moderate")); + } else if (value < 80) { + ui->hop_tasteShow->setStyleSheet(bar_80); + ui->hop_tasteShow->setFormat(tr("High")); + } else { + ui->hop_tasteShow->setStyleSheet(bar_100); + ui->hop_tasteShow->setFormat(tr("Very high")); + } +} + + +void EditProduct::hop_Aroma_valueChanged(int value) +{ + if (value < 20) { + ui->hop_aromaShow->setStyleSheet(bar_20); + ui->hop_aromaShow->setFormat(tr("Very low")); + } else if (value < 40) { + ui->hop_aromaShow->setStyleSheet(bar_40); + ui->hop_aromaShow->setFormat(tr("Low")); + } else if (value < 60) { + ui->hop_aromaShow->setStyleSheet(bar_60); + ui->hop_aromaShow->setFormat(tr("Moderate")); + } else if (value < 80) { + ui->hop_aromaShow->setStyleSheet(bar_80); + ui->hop_aromaShow->setFormat(tr("High")); + } else { + ui->hop_aromaShow->setStyleSheet(bar_100); + ui->hop_aromaShow->setFormat(tr("Very high")); + } +} + + +void EditProduct::calcIBUs() +{ + double hop_flavour = 0, hop_aroma = 0, ibus = 0; + + for (int i = 0; i < product->hops.size(); i++) { + + ibus += Utils::toIBU(product->hops.at(i).h_useat, product->hops.at(i).h_form, product->preboil_sg, product->batch_size, product->hops.at(i).h_amount, + product->hops.at(i).h_time, product->hops.at(i).h_alpha, product->ibu_method, 0, product->hops.at(i).h_time, 0); + hop_flavour += Utils::hopFlavourContribution(product->hops.at(i).h_time, product->batch_size, product->hops.at(i).h_useat, product->hops.at(i).h_amount); + hop_aroma += Utils::hopAromaContribution(product->hops.at(i).h_time, product->batch_size, product->hops.at(i).h_useat, product->hops.at(i).h_amount); + } + + hop_flavour = round(hop_flavour * 1000.0 / 5.0) / 10; + hop_aroma = round(hop_aroma * 1000.0 / 6.0) / 10; + if (hop_flavour > 100) + hop_flavour = 100; + if (hop_aroma > 100) + hop_aroma = 100; + qDebug() << "ibu" << product->est_ibu << ibus << "flavour" << hop_flavour << "aroma" << hop_aroma << "method" << product->ibu_method; + + product->est_ibu = ibus; + ui->est_ibuEdit->setValue(product->est_ibu); + ui->est_ibu2Edit->setValue(product->est_ibu); + ui->est_ibuShow->setValue(product->est_ibu); + ui->hop_tasteShow->setValue(hop_flavour); + ui->hop_aromaShow->setValue(hop_aroma); +} + + +void EditProduct::addHopRow_clicked() +{ + Hops newh; + + qDebug() << "Add hop row"; + + for (int i = 0; i < product->hops.size(); i++) { + if (product->hops.at(i).h_amount == 0 && product->hops.at(i).h_alpha == 0) + return; // Add only one at a time. + } + + newh.h_name = "Select one"; + newh.h_origin = ""; + newh.h_amount = 0; + newh.h_cost = 0; + newh.h_type = 0; + newh.h_form = 0; + newh.h_useat = 2; + newh.h_time = 0; + newh.h_alpha = 0; + newh.h_beta = 0; + newh.h_hsi = 0; + newh.h_humulene = 0; + newh.h_caryophyllene = 0; + newh.h_cohumulone = 0; + newh.h_myrcene = 0; + newh.h_total_oil = 0; + + product->hops.append(newh); + emit refreshAll(); +} + + +void EditProduct::deleteHopRow_clicked() +{ + if (product->locked || product->hops.size() < 1) + return; + + QPushButton *pb = qobject_cast(QObject::sender()); + int row = pb->objectName().toInt(); + qDebug() << "Delete hop row" << row << product->hops.size(); + + int rc = QMessageBox::warning(this, tr("Delete hop"), tr("Delete %1").arg(product->hops.at(row).h_name), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + if (rc == QMessageBox::No) + return; + + product->hops.removeAt(row); + is_changed(); + emit refreshAll(); +} + + +void EditProduct::hop_amount_changed(double val) +{ + QTableWidgetItem *item; + + qDebug() << "hop_amount_changed()" << product->hops_row << val; + + product->hops[product->hops_row].h_amount = val / 1000.0; + item = new QTableWidgetItem(QString("%1 gr").arg(val, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->hopsTable->setItem(product->hops_row, 8, item); + + double ibu = Utils::toIBU(product->hops.at(product->hops_row).h_useat, product->hops.at(product->hops_row).h_form, product->preboil_sg, + product->batch_size, product->hops.at(product->hops_row).h_amount, product->hops.at(product->hops_row).h_time, + product->hops.at(product->hops_row).h_alpha, product->ibu_method, 0, product->hops.at(product->hops_row).h_time, 0); + + ibuEdit->setValue(ibu); + item = new QTableWidgetItem(QString("%1").arg(ibu, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->hopsTable->setItem(product->hops_row, 7, item); + + calcIBUs(); + is_changed(); +} + + +void EditProduct::hop_time_changed(int val) +{ + QTableWidgetItem *item; + + qDebug() << "hop_time_changed()" << product->hops_row << val; + + if (product->hops.at(product->hops_row).h_useat == 2 || product->hops.at(product->hops_row).h_useat == 4) { // Boil or whirlpool + item = new QTableWidgetItem(QString("%1 min.").arg(val, 1, 'f', 0, '0')); + product->hops[product->hops_row].h_time = val; + } else if (product->hops.at(product->hops_row).h_useat == 5) { // Dry-hop + item = new QTableWidgetItem(QString("%1 days.").arg(val, 1, 'f', 0, '0')); + product->hops[product->hops_row].h_time = val * 1440; + } else { + item = new QTableWidgetItem(QString("")); + product->hops[product->hops_row].h_time = val; + } + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->hopsTable->setItem(product->hops_row, 6, item); + + double ibu = Utils::toIBU(product->hops.at(product->hops_row).h_useat, product->hops.at(product->hops_row).h_form, product->preboil_sg, + product->batch_size, product->hops.at(product->hops_row).h_amount, product->hops.at(product->hops_row).h_time, + product->hops.at(product->hops_row).h_alpha, product->ibu_method, 0, product->hops.at(product->hops_row).h_time, 0); + + ibuEdit->setValue(ibu); + item = new QTableWidgetItem(QString("%1").arg(ibu, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->hopsTable->setItem(product->hops_row, 7, item); + + calcIBUs(); + is_changed(); +} + + +void EditProduct::hop_select_changed(int val) +{ + QSqlQuery query; + bool instock = hinstockEdit->isChecked(); + QString w; + QTableWidgetItem *item; + + if (val < 1) + return; + + qDebug() << "hop_select_changed()" << product->fermentables_row << val << instock; + + /* + * Search the hop pointed by the index and instock flag. + */ + QString sql = "SELECT name,origin,alpha,beta,humulene,caryophyllene,cohumulone,myrcene,hsi,total_oil,type,form,cost FROM inventory_hops "; + if (instock) + sql.append("WHERE inventory > 0 "); + sql.append("ORDER BY origin,name"); + query.prepare(sql); + query.exec(); + query.first(); + for (int i = 0; i < (val - 1); i++) { + query.next(); + } + qDebug() << "found" << query.value(1).toString() << query.value(0).toString(); + + /* + * Replace the hop record contents + */ + product->hops[product->hops_row].h_name = query.value(0).toString(); + product->hops[product->hops_row].h_origin = query.value(1).toString(); + product->hops[product->hops_row].h_alpha = query.value(2).toDouble(); + product->hops[product->hops_row].h_beta = query.value(3).toDouble(); + product->hops[product->hops_row].h_humulene = query.value(4).toDouble(); + product->hops[product->hops_row].h_caryophyllene = query.value(5).toDouble(); + product->hops[product->hops_row].h_cohumulone = query.value(6).toDouble(); + product->hops[product->hops_row].h_myrcene = query.value(7).toDouble(); + product->hops[product->hops_row].h_hsi = query.value(8).toDouble(); + product->hops[product->hops_row].h_total_oil = query.value(9).toDouble(); + product->hops[product->hops_row].h_type = query.value(10).toInt(); + product->hops[product->hops_row].h_form = query.value(11).toInt(); + product->hops[product->hops_row].h_cost = query.value(12).toDouble(); + + /* + * Update the visible fields + */ + hnameEdit->setText(product->hops.at(product->hops_row).h_name); + horiginEdit->setText(product->hops.at(product->hops_row).h_origin); + + double ibu = Utils::toIBU(product->hops.at(product->hops_row).h_useat, product->hops.at(product->hops_row).h_form, product->preboil_sg, + product->batch_size, product->hops.at(product->hops_row).h_amount, product->hops.at(product->hops_row).h_time, + product->hops.at(product->hops_row).h_alpha, product->ibu_method, 0, product->hops.at(product->hops_row).h_time, 0); + ibuEdit->setValue(ibu); + + ui->hopsTable->setItem(product->hops_row, 0, new QTableWidgetItem(product->hops.at(product->hops_row).h_origin)); + ui->hopsTable->setItem(product->hops_row, 1, new QTableWidgetItem(product->hops.at(product->hops_row).h_name)); + + item = new QTableWidgetItem(hop_types[product->hops.at(product->hops_row).h_type]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->hopsTable->setItem(product->hops_row, 2, item); + + item = new QTableWidgetItem(hop_forms[product->hops.at(product->hops_row).h_form]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->hopsTable->setItem(product->hops_row, 3, item); + + item = new QTableWidgetItem(QString("%1%").arg(product->hops.at(product->hops_row).h_alpha, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->hopsTable->setItem(product->hops_row, 4, item); + + item = new QTableWidgetItem(QString("%1").arg(ibu, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->hopsTable->setItem(product->hops_row, 7, item); + + calcIBUs(); + is_changed(); +} + + +void EditProduct::hop_instock_changed(bool val) +{ + QSqlQuery query; + + qDebug() << "hop_instock_changed()" << product->hops_row << val; + + this->hselectEdit->setCurrentIndex(-1); + this->hselectEdit->clear(); + QString sql = "SELECT origin,name,alpha,inventory FROM inventory_hops "; + if (val) + sql.append("WHERE inventory > 0 "); + sql.append("ORDER BY origin,name"); + query.prepare(sql); + query.exec(); + query.first(); + this->hselectEdit->addItem(""); // Start with empty value + for (int i = 0; i < query.size(); i++) { + this->hselectEdit->addItem(query.value(0).toString()+" - "+query.value(1).toString()+" ("+query.value(2).toString()+"%) "+ + QString("%1 gr").arg(query.value(3).toDouble() * 1000.0, 2, 'f', 1, '0')); + query.next(); + } +} + + +void EditProduct::hop_useat_changed(int val) +{ + qDebug() << "hop_useat_changed()" << product->hops_row << val; + + product->hops[product->hops_row].h_useat = val; + QTableWidgetItem *item = new QTableWidgetItem(hop_useat[val]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->hopsTable->setItem(product->hops_row, 5, item); + + if (val == 2 || val == 4) { // Boil or whirlpool + htimeLabel->setText(tr("Time in minutes:")); + htimeEdit->setValue(product->hops.at(product->hops_row).h_time); + htimeEdit->setReadOnly(false); + } else if (val == 5) { // Dry-hop + htimeLabel->setText(tr("Time in days:")); + htimeEdit->setValue(product->hops.at(product->hops_row).h_time / 1440); + htimeEdit->setReadOnly(false); + } else { + htimeLabel->setText(""); + htimeEdit->setValue(0); + htimeEdit->setReadOnly(true); + } + + is_changed(); + emit refreshAll(); +} + + +void EditProduct::editHopRow_clicked() +{ + QSqlQuery query; + + if (product->locked) + return; + + QPushButton *pb = qobject_cast(QObject::sender()); + product->hops_row = pb->objectName().toInt(); + qDebug() << "Edit hop row" << product->hops_row; + Hops backup = product->hops.at(product->hops_row); + + QDialog* dialog = new QDialog(this); + dialog->resize(738, 260); + QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog); + buttonBox->setObjectName(QString::fromUtf8("buttonBox")); + buttonBox->setGeometry(QRect(30, 210, 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 hop:")); + nameLabel->setGeometry(QRect(10, 10, 141, 20)); + nameLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + QLabel *originLabel = new QLabel(dialog); + originLabel->setObjectName(QString::fromUtf8("originLabel")); + originLabel->setText(tr("Origin:")); + originLabel->setGeometry(QRect(10, 40, 141, 20)); + originLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + QLabel *amountLabel = new QLabel(dialog); + amountLabel->setObjectName(QString::fromUtf8("amountLabel")); + amountLabel->setText(tr("Amount in gr:")); + amountLabel->setGeometry(QRect(10, 100, 141, 20)); + amountLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + htimeLabel = new QLabel(dialog); + htimeLabel->setObjectName(QString::fromUtf8("htimeLabel")); + if (product->hops.at(product->hops_row).h_useat == 5) // Dry-hop + htimeLabel->setText(tr("Time in days:")); + else if (product->hops.at(product->hops_row).h_useat == 2 || product->hops.at(product->hops_row).h_useat == 4) // Boil or whirlpool + htimeLabel->setText(tr("Time in minutes:")); + else + htimeLabel->setText(""); + + htimeLabel->setGeometry(QRect(10, 130, 141, 20)); + htimeLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + QLabel *useatLabel = new QLabel(dialog); + useatLabel->setObjectName(QString::fromUtf8("useatLabel")); + useatLabel->setText(tr("Use at:")); + useatLabel->setGeometry(QRect(10, 160, 141, 20)); + useatLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + QLabel *selectLabel = new QLabel(dialog); + selectLabel->setObjectName(QString::fromUtf8("selectLabel")); + selectLabel->setText(tr("Select hop:")); + 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 *ibuLabel = new QLabel(dialog); + ibuLabel->setObjectName(QString::fromUtf8("maxLabel")); + ibuLabel->setText(tr("Bitterness IBU:")); + ibuLabel->setGeometry(QRect(420, 130, 121, 20)); + ibuLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + + hselectEdit = new QComboBox(dialog); + hselectEdit->setObjectName(QString::fromUtf8("selectEdit")); + hselectEdit->setGeometry(QRect(160, 70, 371, 23)); + + hnameEdit = new QLineEdit(dialog); + hnameEdit->setObjectName(QString::fromUtf8("hnameEdit")); + hnameEdit->setText(product->hops.at(product->hops_row).h_name); + hnameEdit->setGeometry(QRect(160, 10, 511, 23)); + hnameEdit->setReadOnly(true); + horiginEdit = new QLineEdit(dialog); + horiginEdit->setObjectName(QString::fromUtf8("horiginEdit")); + horiginEdit->setText(product->hops.at(product->hops_row).h_origin); + horiginEdit->setGeometry(QRect(160, 40, 511, 23)); + horiginEdit->setReadOnly(true); + hamountEdit = new QDoubleSpinBox(dialog); + hamountEdit->setObjectName(QString::fromUtf8("hamountEdit")); + hamountEdit->setGeometry(QRect(160, 100, 121, 24)); + hamountEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + hamountEdit->setAccelerated(true); + hamountEdit->setDecimals(1); + hamountEdit->setMaximum(1000000.0); + hamountEdit->setSingleStep(0.5); + hamountEdit->setValue(product->hops.at(product->hops_row).h_amount * 1000.0); + htimeEdit = new QSpinBox(dialog); + htimeEdit->setObjectName(QString::fromUtf8("htimeEdit")); + htimeEdit->setGeometry(QRect(160, 130, 121, 24)); + htimeEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + htimeEdit->setAccelerated(true); + htimeEdit->setMaximum(10000.0); + if (product->hops.at(product->hops_row).h_useat == 2 || product->hops.at(product->hops_row).h_useat == 4) { // Boil or whirlpool + htimeEdit->setValue(product->hops.at(product->hops_row).h_time); + htimeEdit->setReadOnly(false); + } else if (product->hops.at(product->hops_row).h_useat == 5){ // Dry-hop + htimeEdit->setValue(product->hops.at(product->hops_row).h_time / 1440); + htimeEdit->setReadOnly(false); + } else { + htimeEdit->setReadOnly(true); + } + useatEdit = new QComboBox(dialog); + useatEdit->setObjectName(QString::fromUtf8("useatEdit")); + useatEdit->setGeometry(QRect(160, 160, 161, 23)); + useatEdit->addItem(tr("Mash")); + useatEdit->addItem(tr("First wort")); + useatEdit->addItem(tr("Boil")); + useatEdit->addItem(tr("Aroma")); + useatEdit->addItem(tr("Whirlpool")); + useatEdit->addItem(tr("Dry hop")); + useatEdit->setCurrentIndex(product->hops.at(product->hops_row).h_useat); + + hinstockEdit = new QCheckBox(dialog); + hinstockEdit->setObjectName(QString::fromUtf8("hinstockEdit")); + hinstockEdit->setGeometry(QRect(655, 70, 85, 21)); + hinstockEdit->setChecked(true); + + ibuEdit = new QDoubleSpinBox(dialog); + ibuEdit->setObjectName(QString::fromUtf8("ibuEdit")); + ibuEdit->setGeometry(QRect(550, 130, 121, 24)); + ibuEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + ibuEdit->setReadOnly(true); + ibuEdit->setButtonSymbols(QAbstractSpinBox::NoButtons); + ibuEdit->setDecimals(1); + double ibu = Utils::toIBU(product->hops.at(product->hops_row).h_useat, product->hops.at(product->hops_row).h_form, product->preboil_sg, + product->batch_size, product->hops.at(product->hops_row).h_amount, product->hops.at(product->hops_row).h_time, + product->hops.at(product->hops_row).h_alpha, product->ibu_method, 0, product->hops.at(product->hops_row).h_time, 0); + ibuEdit->setValue(ibu); + + hop_instock_changed(true); + + connect(hselectEdit, QOverload::of(&QComboBox::currentIndexChanged), this, &EditProduct::hop_select_changed); + connect(hamountEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::hop_amount_changed); + connect(htimeEdit, QOverload::of(&QSpinBox::valueChanged), this, &EditProduct::hop_time_changed); + connect(useatEdit, QOverload::of(&QComboBox::currentIndexChanged), this, &EditProduct::hop_useat_changed); + connect(hinstockEdit, &QCheckBox::stateChanged, this, &EditProduct::hop_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->hops[product->hops_row] = backup; + } else { + /* Clear time if hop is not used for boil, whirlpool or dry-hop. */ + if (! (product->hops.at(product->hops_row).h_useat == 2 || + product->hops.at(product->hops_row).h_useat == 4 || + product->hops.at(product->hops_row).h_useat == 5)) { + if (product->hops.at(product->hops_row).h_time) { + product->hops[product->hops_row].h_time = 0; + is_changed(); + } + } + } + + disconnect(hselectEdit, nullptr, nullptr, nullptr); + disconnect(hamountEdit, nullptr, nullptr, nullptr); + disconnect(htimeEdit, nullptr, nullptr, nullptr); + disconnect(useatEdit, nullptr, nullptr, nullptr); + disconnect(hinstockEdit, nullptr, nullptr, nullptr); + disconnect(buttonBox, nullptr, nullptr, nullptr); + + emit refreshAll(); +} + + +void EditProduct::adjustHops(double factor) +{ + double amount; + + if (product->hops.size() == 0) + return; + + for (int i = 0; i < product->hops.size(); i++) { + amount = product->hops.at(i).h_amount * factor; + product->hops[i].h_amount = amount; + } +} + + diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 src/EditProductTab5.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/EditProductTab5.cpp Thu Apr 28 22:49:13 2022 +0200 @@ -0,0 +1,686 @@ +/** + * EditProduct.cpp is part of bmsapp. + * + * tab 5, miscs. + * + * 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 . + */ + + +bool EditProduct::misc_sort_test(const Miscs &D1, const Miscs &D2) +{ + if (D1.m_use_use > D2.m_use_use) + return false; + if (D1.m_use_use < D2.m_use_use) + return true; + if (D1.m_type > D2.m_type) + return false; + if (D1.m_type < D2.m_type) + return true; + return (D1.m_amount > D2.m_amount); +} + + +void EditProduct::refreshMiscs() +{ + QString w; + QWidget* pWidget; + QHBoxLayout* pLayout; + QTableWidgetItem *item; + + qDebug() << "refreshMiscs" << product->miscs.size(); + std::sort(product->miscs.begin(), product->miscs.end(), misc_sort_test); + + const QSignalBlocker blocker1(ui->bs_cacl2Edit); + const QSignalBlocker blocker2(ui->bs_caso4Edit); + const QSignalBlocker blocker3(ui->bs_mgso4Edit); + const QSignalBlocker blocker4(ui->bs_naclEdit); + const QSignalBlocker blocker5(ui->bs_mgcl2Edit); + const QSignalBlocker blocker6(ui->bs_nahco3Edit); + const QSignalBlocker blocker7(ui->bs_caco3Edit); + const QSignalBlocker blocker8(ui->mw_acidPick); + const QSignalBlocker blocker9(ui->mw_acidvolEdit); + + const QStringList labels({tr("Ingredient"), tr("Type"), tr("Use at"), tr("Time"), tr("Amount"), tr("Delete"), tr("Edit") }); + + ui->miscsTable->setColumnCount(7); + ui->miscsTable->setColumnWidth(0, 300); /* Ingredient */ + ui->miscsTable->setColumnWidth(1, 100); /* Type */ + ui->miscsTable->setColumnWidth(2, 100); /* Added */ + ui->miscsTable->setColumnWidth(3, 75); /* Time */ + ui->miscsTable->setColumnWidth(4, 90); /* Amount */ + ui->miscsTable->setColumnWidth(5, 80); /* Delete */ + ui->miscsTable->setColumnWidth(6, 80); /* Edit */ + ui->miscsTable->setHorizontalHeaderLabels(labels); + ui->miscsTable->verticalHeader()->hide(); + ui->miscsTable->setRowCount(product->miscs.size()); + + for (int i = 0; i < product->miscs.size(); i++) { + + ui->miscsTable->setItem(i, 0, new QTableWidgetItem(product->miscs.at(i).m_name)); + + item = new QTableWidgetItem(misc_types[product->miscs.at(i).m_type]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->miscsTable->setItem(i, 1, item); + + item = new QTableWidgetItem(misc_uses[product->miscs.at(i).m_use_use]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->miscsTable->setItem(i, 2, item); + + if (product->miscs.at(i).m_use_use == 2) { // Boil + item = new QTableWidgetItem(QString("%1 min.").arg(product->miscs.at(i).m_time, 1, 'f', 0, '0')); + } else if (product->miscs.at(i).m_use_use == 3 || product->miscs.at(i).m_use_use == 4) { // Primary or secondary + item = new QTableWidgetItem(QString("%1 days.").arg(product->miscs.at(i).m_time / 1440, 1, 'f', 0, '0')); + } else { + item = new QTableWidgetItem(QString("")); + } + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->miscsTable->setItem(i, 3, item); + + if (product->miscs.at(i).m_amount_is_weight) + item = new QTableWidgetItem(QString("%1 gr").arg(product->miscs.at(i).m_amount * 1000.0, 3, 'f', 2, '0')); + else + item = new QTableWidgetItem(QString("%1 ml").arg(product->miscs.at(i).m_amount * 1000.0, 3, 'f', 2, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->miscsTable->setItem(i, 4, item); + + /* + * Add the Delete and Edit row buttons. + * Not for water agents, these are set on the water tab. + */ + if (product->miscs.at(i).m_type == 4) { + ui->miscsTable->removeCellWidget(i, 5); + ui->miscsTable->removeCellWidget(i, 6); + } else { + 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(deleteMiscRow_clicked())); + pLayout = new QHBoxLayout(pWidget); + pLayout->addWidget(btn_dele); + pLayout->setContentsMargins(5, 0, 5, 0); + pWidget->setLayout(pLayout); + ui->miscsTable->setCellWidget(i, 5, 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(editMiscRow_clicked())); + pLayout = new QHBoxLayout(pWidget); + pLayout->addWidget(btn_edit); + pLayout->setContentsMargins(5, 0, 5, 0); + pWidget->setLayout(pLayout); + ui->miscsTable->setCellWidget(i, 6, pWidget); + } + + /* + * Update the water agents. + */ + if (product->miscs.at(i).m_type == 4) { + if (product->miscs.at(i).m_name == "CaCl2") { + ui->bs_cacl2Edit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "CaSO4") { + ui->bs_caso4Edit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "MgSO4") { + ui->bs_mgso4Edit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "NaCl") { + ui->bs_naclEdit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "MgCl2") { + ui->bs_mgcl2Edit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "NaHCO3") { + ui->bs_nahco3Edit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "CaCO3") { + ui->bs_caco3Edit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "Melkzuur" || product->miscs.at(i).m_name == "Lactic") { + product->wa_acid_name = 0; + product->wa_acid_perc = my_acids.at(0).AcidPrc; + ui->mw_acidPick->setCurrentIndex(0); + ui->mw_acidpercEdit->setValue(my_acids.at(0).AcidPrc); + ui->mw_acidvolEdit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "Zoutzuur" || product->miscs.at(i).m_name == "Hydrochloric") { + product->wa_acid_name = 1; + product->wa_acid_perc = my_acids.at(1).AcidPrc; + ui->mw_acidPick->setCurrentIndex(1); + ui->mw_acidpercEdit->setValue(my_acids.at(1).AcidPrc); + ui->mw_acidvolEdit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "Fosforzuur" || product->miscs.at(i).m_name == "Phosphoric") { + product->wa_acid_name = 2; + product->wa_acid_perc = my_acids.at(2).AcidPrc; + ui->mw_acidPick->setCurrentIndex(2); + ui->mw_acidpercEdit->setValue(my_acids.at(2).AcidPrc); + ui->mw_acidvolEdit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "Zwavelzuur" || product->miscs.at(i).m_name == "Sulfuric") { + product->wa_acid_name = 3; + product->wa_acid_perc = my_acids.at(3).AcidPrc; + ui->mw_acidPick->setCurrentIndex(3); + ui->mw_acidpercEdit->setValue(my_acids.at(3).AcidPrc); + ui->mw_acidvolEdit->setValue(product->miscs.at(i).m_amount * 1000.0); + } + } + } +} + + +/* + * Manipulate the memory array and update the miscs table. + */ +void EditProduct::brewing_salt_sub(QString salt, double val) +{ + QTableWidgetItem *item; + + val = round(val * 100.0) / 100.0; + if (val == 0) { + /* + * Remove this salt if it is in the table. + */ + for (int i = 0; i < product->miscs.size(); i++) { + if (salt.contains(product->miscs.at(i).m_name)) { + qDebug() << " brewing_salt_sub delete" << salt; + product->miscs.removeAt(i); + refreshMiscs(); + return; + } + } + qDebug() << " brewing_salt_sub delete error"; + return; + } + + /* + * First see if this salt is in the table. + * If it is, update the amount. + */ + for (int i = 0; i < product->miscs.size(); i++) { + if (salt.contains(product->miscs.at(i).m_name)) { + product->miscs[i].m_amount = val / 1000.0; + if (product->miscs.at(i).m_amount_is_weight) + item = new QTableWidgetItem(QString("%1 gr").arg(val, 3, 'f', 2, '0')); + else + item = new QTableWidgetItem(QString("%1 ml").arg(val, 3, 'f', 2, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->miscsTable->setItem(i, 4, item); + return; + } + } + + /* + * We need a new entry. Search in the database is tricky because + * we are here with possible more names for the same salt. The + * name can be like 'Lactic Melkzuur'. So we select only the + * brewing salts and manually check their names. + */ + QSqlQuery query("SELECT * FROM inventory_miscs WHERE type = 4"); + while (query.next()) { + if (salt.contains(query.value(1).toString())) { + qDebug() << " found it, append"; + Miscs m; + m.m_name = query.value(1).toString(); + m.m_amount = val / 1000.0; + m.m_type = query.value(2).toInt(); + m.m_use_use = query.value(3).toInt(); + m.m_time = query.value(4).toDouble(); + m.m_amount_is_weight = query.value(5).toInt() ? true:false; + m.m_cost = query.value(10).toDouble(); + product->miscs.append(m); + refreshMiscs(); + return; + } + } + + qDebug() << "brewing_salt_sub, nothing done." << salt << val; +} + + +/* + * Edit brewing salt and recalculate. + */ +void EditProduct::set_brewing_salt(QString salt, double val) +{ + val = round(val * 100.0) / 100.0; + qDebug() << "set_brewing_salt" << salt << val; + brewing_salt_sub(salt, val); + calcWater(); + is_changed(); +} + + +void EditProduct::addMiscRow_clicked() +{ + Miscs newm; + + for (int i = 0; i < product->miscs.size(); i++) { + if (product->miscs.at(i).m_amount == 0) + return; // Add only one at a time. + } + + newm.m_name = "Select one"; + newm.m_amount = 0; + newm.m_type = 0; + newm.m_use_use = 0; + newm.m_time = 0; + newm.m_amount_is_weight = true; + newm.m_cost = 0; + product->miscs.append(newm); + is_changed(); + emit refreshAll(); +} + + +void EditProduct::deleteMiscRow_clicked() +{ + if (product->locked || product->miscs.size() < 1) + return; + + QPushButton *pb = qobject_cast(QObject::sender()); + int row = pb->objectName().toInt(); + qDebug() << "Delete misc row" << row << product->miscs.size(); + + int rc = QMessageBox::warning(this, tr("Delete misc"), tr("Delete %1").arg(product->miscs.at(row).m_name), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + if (rc == QMessageBox::No) + return; + + product->miscs.removeAt(row); + is_changed(); + emit refreshAll(); +} + + +void EditProduct::misc_amount_changed(double val) +{ + QTableWidgetItem *item; + + qDebug() << "misc_amount_changed()" << product->miscs_row << val; + + product->miscs[product->miscs_row].m_amount = val / 1000.0; + if (product->miscs.at(product->miscs_row).m_amount_is_weight) { + item = new QTableWidgetItem(QString("%1 gr").arg(product->miscs.at(product->miscs_row).m_amount * 1000.0, 3, 'f', 2, '0')); + } else { + item = new QTableWidgetItem(QString("%1 ml").arg(product->miscs.at(product->miscs_row).m_amount * 1000.0, 3, 'f', 2, '0')); + } + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->miscsTable->setItem(product->miscs_row, 4, item); + + is_changed(); +} + + +void EditProduct::misc_time_changed(int val) +{ + QTableWidgetItem *item; + + qDebug() << "misc_time_changed()" << product->miscs_row << val; + + if (product->miscs.at(product->miscs_row).m_use_use == 2) { // Boil + product->miscs[product->miscs_row].m_time = val; + item = new QTableWidgetItem(QString("%1 min.").arg(val, 1, 'f', 0, '0')); + } else if (product->miscs.at(product->miscs_row).m_use_use == 3 || product->miscs.at(product->miscs_row).m_use_use == 4) { // Primary or secondary + product->miscs[product->miscs_row].m_time = val * 1440; + item = new QTableWidgetItem(QString("%1 days.").arg(val, 1, 'f', 0, '0')); + } else { + item = new QTableWidgetItem(QString("")); + } + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->miscsTable->setItem(product->miscs_row, 3, item); + + is_changed(); +} + + +void EditProduct::misc_select_changed(int val) +{ + QSqlQuery query; + bool instock = minstockEdit->isChecked(); + QString w; + QTableWidgetItem *item; + + if (val < 1) + return; + + qDebug() << "misc_select_changed()" << product->miscs_row << val << instock; + + /* + * Search the misc ingredient pointed by the index and instock flag. + */ + QString sql = "SELECT name,type,use_use,time,amount_is_weight,cost FROM inventory_miscs WHERE "; + if (instock) + sql.append("inventory > 0 AND "); + sql.append("type != 4 ORDER BY name"); + qDebug() << sql; + query.prepare(sql); + query.exec(); + query.first(); + for (int i = 0; i < (val - 1); i++) { + query.next(); + } + qDebug() << "found" << query.value(0).toString(); + + /* + * Replace the misc record contents + */ + product->miscs[product->miscs_row].m_name = query.value(0).toString(); + product->miscs[product->miscs_row].m_type = query.value(1).toInt(); + product->miscs[product->miscs_row].m_use_use = query.value(2).toInt(); + product->miscs[product->miscs_row].m_time = query.value(3).toDouble(); + product->miscs[product->miscs_row].m_amount_is_weight = query.value(4).toInt() ? true:false; + product->miscs[product->miscs_row].m_cost = query.value(5).toDouble(); + + /* + * Update the visible fields + */ + mnameEdit->setText(product->miscs.at(product->miscs_row).m_name); + ui->miscsTable->setItem(product->miscs_row, 0, new QTableWidgetItem(product->miscs.at(product->miscs_row).m_name)); + + item = new QTableWidgetItem(misc_types[product->miscs.at(product->miscs_row).m_type]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->miscsTable->setItem(product->miscs_row, 1, item); + + item = new QTableWidgetItem(misc_uses[product->miscs.at(product->miscs_row).m_use_use]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->miscsTable->setItem(product->miscs_row, 2, item); + useatEdit->setCurrentIndex(product->miscs.at(product->miscs_row).m_use_use); + + if (product->miscs.at(product->miscs_row).m_use_use == 3 || product->miscs.at(product->miscs_row).m_use_use == 4) { // Fermentation stages + mtimeEdit->setValue(product->miscs.at(product->miscs_row).m_time / 1440); + mtimeEdit->setReadOnly(false); + mtimeLabel->setText(tr("Time in days:")); + item = new QTableWidgetItem(QString("%1 days.").arg(product->miscs.at(product->miscs_row).m_time / 1440, 1, 'f', 0, '0')); + } else if (product->miscs.at(product->miscs_row).m_use_use == 2) { // Boil + mtimeEdit->setValue(product->miscs.at(product->miscs_row).m_time); + mtimeEdit->setReadOnly(false); + mtimeLabel->setText(tr("Time in minutes:")); + item = new QTableWidgetItem(QString("%1 min.").arg(product->miscs.at(product->miscs_row).m_time, 1, 'f', 0, '0')); + } else { + mtimeEdit->setReadOnly(true); + mtimeLabel->setText(""); + item = new QTableWidgetItem(QString("")); + } + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->miscsTable->setItem(product->miscs_row, 3, item); + + if (product->miscs.at(product->miscs_row).m_amount_is_weight) { + mamountLabel->setText(tr("Amount in gr:")); + item = new QTableWidgetItem(QString("%1 gr").arg(product->miscs.at(product->miscs_row).m_amount * 1000.0, 3, 'f', 2, '0')); + } else { + mamountLabel->setText(tr("Amount in ml:")); + item = new QTableWidgetItem(QString("%1 ml").arg(product->miscs.at(product->miscs_row).m_amount * 1000.0, 3, 'f', 2, '0')); + } + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->miscsTable->setItem(product->miscs_row, 4, item); + + is_changed(); + emit refreshAll(); +} + + +void EditProduct::misc_instock_changed(bool val) +{ + QSqlQuery query; + + qDebug() << "misc_instock_changed()" << product->miscs_row << val; + + this->mselectEdit->setCurrentIndex(-1); + this->mselectEdit->clear(); + QString sql = "SELECT name,type,amount_is_weight,inventory FROM inventory_miscs WHERE "; + if (val) + sql.append("inventory > 0 AND "); + sql.append("type != 4 ORDER BY name"); + query.prepare(sql); + query.exec(); + query.first(); + this->mselectEdit->addItem(""); // Start with empty value + for (int i = 0; i < query.size(); i++) { + this->mselectEdit->addItem(query.value(0).toString()+ " (" + misc_types[query.value(1).toInt()] + ") " + + QString("%1 %2").arg(query.value(3).toDouble() * 1000.0, 3, 'f', 2, '0').arg(query.value(2).toInt()?"gr":"ml")); + query.next(); + } +} + + +void EditProduct::misc_useat_changed(int val) +{ + QTableWidgetItem *item; + + qDebug() << "misc_useat_changed" << val; + product->miscs[product->miscs_row].m_use_use = val; + item = new QTableWidgetItem(misc_uses[val]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->miscsTable->setItem(product->miscs_row, 2, item); + + if (val == 3 || val == 4) { // Fermentation stages + product->miscs[product->miscs_row].m_time = mtimeEdit->value() * 1440; + mtimeEdit->setReadOnly(false); + mtimeLabel->setText(tr("Time in days:")); + item = new QTableWidgetItem(QString("%1 days.").arg(product->miscs.at(product->miscs_row).m_time / 1440, 1, 'f', 0, '0')); + } else if (val == 2) { // Boil + product->miscs[product->miscs_row].m_time = mtimeEdit->value(); + mtimeEdit->setReadOnly(false); + mtimeLabel->setText(tr("Time in minutes:")); + item = new QTableWidgetItem(QString("%1 min.").arg(product->miscs.at(product->miscs_row).m_time, 1, 'f', 0, '0')); + } else { + product->miscs[product->miscs_row].m_time = 0; + mtimeEdit->setValue(0); + mtimeEdit->setReadOnly(true); + mtimeLabel->setText(""); + item = new QTableWidgetItem(QString("")); + } + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->miscsTable->setItem(product->miscs_row, 3, item); + + is_changed(); +} + + +void EditProduct::editMiscRow_clicked() +{ + QSqlQuery query; + + if (product->locked) + return; + + QPushButton *pb = qobject_cast(QObject::sender()); + product->miscs_row = pb->objectName().toInt(); + qDebug() << "Edit misc row" << product->miscs_row; + Miscs backup = product->miscs.at(product->miscs_row); + + QDialog* dialog = new QDialog(this); + dialog->resize(738, 230); + QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog); + buttonBox->setObjectName(QString::fromUtf8("buttonBox")); + buttonBox->setGeometry(QRect(30, 180, 671, 32)); + buttonBox->setLayoutDirection(Qt::LeftToRight); + buttonBox->setOrientation(Qt::Horizontal); + buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); + buttonBox->setCenterButtons(true); + + QLabel *nameLabel = new QLabel(dialog); + nameLabel->setObjectName(QString::fromUtf8("nameLabel")); + nameLabel->setText(tr("Current ingredient:")); + nameLabel->setGeometry(QRect(10, 10, 141, 20)); + nameLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + + mamountLabel = new QLabel(dialog); + mamountLabel->setObjectName(QString::fromUtf8("mamountLabel")); + if (product->miscs.at(product->miscs_row).m_amount_is_weight) + mamountLabel->setText(tr("Amount in gr:")); + else + mamountLabel->setText(tr("Amount in ml:")); + mamountLabel->setGeometry(QRect(10, 70, 141, 20)); + mamountLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + + mtimeLabel = new QLabel(dialog); + mtimeLabel->setObjectName(QString::fromUtf8("mtimeLabel")); + if (product->miscs.at(product->miscs_row).m_use_use == 3 || product->miscs.at(product->miscs_row).m_use_use == 4) // Fermentation stages + mtimeLabel->setText(tr("Time in days:")); + else if (product->miscs.at(product->miscs_row).m_use_use == 2) // Boil + mtimeLabel->setText(tr("Time in minutes:")); + else + mtimeLabel->setText(""); + mtimeLabel->setGeometry(QRect(10, 100, 141, 20)); + mtimeLabel->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, 40, 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, 40, 121, 20)); + instockLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + + mselectEdit = new QComboBox(dialog); + mselectEdit->setObjectName(QString::fromUtf8("selectEdit")); + mselectEdit->setGeometry(QRect(160, 40, 371, 23)); + + mnameEdit = new QLineEdit(dialog); + mnameEdit->setObjectName(QString::fromUtf8("mnameEdit")); + mnameEdit->setText(product->miscs.at(product->miscs_row).m_name); + mnameEdit->setGeometry(QRect(160, 10, 511, 23)); + mnameEdit->setReadOnly(true); + + mamountEdit = new QDoubleSpinBox(dialog); + mamountEdit->setObjectName(QString::fromUtf8("mamountEdit")); + mamountEdit->setGeometry(QRect(160, 70, 121, 24)); + mamountEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + mamountEdit->setAccelerated(true); + mamountEdit->setDecimals(2); + mamountEdit->setMaximum(1000000.0); + mamountEdit->setSingleStep(0.1); + mamountEdit->setValue(product->miscs.at(product->miscs_row).m_amount * 1000.0); + + mtimeEdit = new QSpinBox(dialog); + mtimeEdit->setObjectName(QString::fromUtf8("mtimeEdit")); + mtimeEdit->setGeometry(QRect(160, 100, 121, 24)); + mtimeEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + mtimeEdit->setAccelerated(true); + mtimeEdit->setMaximum(10000.0); + if (product->miscs.at(product->miscs_row).m_use_use == 3 || product->miscs.at(product->miscs_row).m_use_use == 4) { // Fermentation stages + mtimeEdit->setValue(product->miscs.at(product->miscs_row).m_time / 1440); + mtimeEdit->setReadOnly(false); + } else if (product->miscs.at(product->miscs_row).m_use_use == 2) { // Boil + mtimeEdit->setValue(product->miscs.at(product->miscs_row).m_time); + mtimeEdit->setReadOnly(false); + } else { + mtimeEdit->setReadOnly(true); + } + + useatEdit = new QComboBox(dialog); + useatEdit->setObjectName(QString::fromUtf8("useatEdit")); + useatEdit->setGeometry(QRect(160, 130, 161, 23)); + useatEdit->addItem(tr("Starter")); + useatEdit->addItem(tr("Mash")); + useatEdit->addItem(tr("Boil")); + useatEdit->addItem(tr("Primary")); + useatEdit->addItem(tr("Secondary")); + useatEdit->addItem(tr("Bottling")); + useatEdit->setCurrentIndex(product->miscs.at(product->miscs_row).m_use_use); + + minstockEdit = new QCheckBox(dialog); + minstockEdit->setObjectName(QString::fromUtf8("minstockEdit")); + minstockEdit->setGeometry(QRect(655, 40, 85, 21)); + minstockEdit->setChecked(true); + + misc_instock_changed(true); + + connect(mselectEdit, QOverload::of(&QComboBox::currentIndexChanged), this, &EditProduct::misc_select_changed); + connect(mamountEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::misc_amount_changed); + connect(mtimeEdit, QOverload::of(&QSpinBox::valueChanged), this, &EditProduct::misc_time_changed); + connect(useatEdit, QOverload::of(&QComboBox::currentIndexChanged), this, &EditProduct::misc_useat_changed); + connect(minstockEdit, &QCheckBox::stateChanged, this, &EditProduct::misc_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->miscs[product->miscs_row] = backup; + } else { + /* Clear time if misc is not used for boil or fermentation. */ + if (! (product->miscs.at(product->miscs_row).m_use_use == 2 || + product->miscs.at(product->miscs_row).m_use_use == 3 || + product->miscs.at(product->miscs_row).m_use_use == 4)) { + if (product->miscs.at(product->miscs_row).m_time) { + product->miscs[product->miscs_row].m_time = 0; + is_changed(); + } + } + } + + disconnect(mselectEdit, nullptr, nullptr, nullptr); + disconnect(mamountEdit, nullptr, nullptr, nullptr); + disconnect(mtimeEdit, nullptr, nullptr, nullptr); + disconnect(useatEdit, nullptr, nullptr, nullptr); + disconnect(minstockEdit, nullptr, nullptr, nullptr); + disconnect(buttonBox, nullptr, nullptr, nullptr); + + emit refreshAll(); +} + + +void EditProduct::adjustMiscs(double factor) +{ + double amount; + + if (product->miscs.size() == 0) + return; + + const QSignalBlocker blocker1(ui->bs_cacl2Edit); + const QSignalBlocker blocker2(ui->bs_caso4Edit); + const QSignalBlocker blocker3(ui->bs_mgso4Edit); + const QSignalBlocker blocker4(ui->bs_naclEdit); + const QSignalBlocker blocker5(ui->bs_mgcl2Edit); + const QSignalBlocker blocker6(ui->bs_nahco3Edit); + const QSignalBlocker blocker7(ui->bs_caco3Edit); + const QSignalBlocker blocker9(ui->mw_acidvolEdit); + + for (int i = 0; i < product->miscs.size(); i++) { + amount = product->miscs.at(i).m_amount * factor; + product->miscs[i].m_amount = amount; + + /* + * Update the water agents. + */ + if (product->miscs.at(i).m_type == 4) { + if (product->miscs.at(i).m_name == "CaCl2") { + ui->bs_cacl2Edit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "CaSO4") { + ui->bs_caso4Edit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "MgSO4") { + ui->bs_mgso4Edit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "NaCl") { + ui->bs_naclEdit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "MgCl2") { + ui->bs_mgcl2Edit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "NaHCO3") { + ui->bs_nahco3Edit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "CaCO3") { + ui->bs_caco3Edit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "Melkzuur" || product->miscs.at(i).m_name == "Lactic") { + ui->mw_acidvolEdit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "Zoutzuur" || product->miscs.at(i).m_name == "Hydrochloric") { + ui->mw_acidvolEdit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "Fosforzuur" || product->miscs.at(i).m_name == "Phosphoric") { + ui->mw_acidvolEdit->setValue(product->miscs.at(i).m_amount * 1000.0); + } else if (product->miscs.at(i).m_name == "Zwavelzuur" || product->miscs.at(i).m_name == "Sulfuric") { + ui->mw_acidvolEdit->setValue(product->miscs.at(i).m_amount * 1000.0); + } + } + } +} + + diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 src/EditProductTab6.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/EditProductTab6.cpp Thu Apr 28 22:49:13 2022 +0200 @@ -0,0 +1,593 @@ +/** + * EditProduct.cpp is part of bmsapp. + * + * tab 6, yeasts. + * + * 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 yeaste 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 . + */ + + +bool EditProduct::yeast_sort_test(const Yeasts &D1, const Yeasts &D2) +{ + if (D1.y_use > D2.y_use) + return false; + if (D1.y_use < D2.y_use) + return true; + return (D1.y_amount > D2.y_amount); +} + + +void EditProduct::refreshYeasts() +{ + QString w; + QWidget* pWidget; + QHBoxLayout* pLayout; + QTableWidgetItem *item; + + qDebug() << "refreshYeasts" << product->yeasts.size(); + std::sort(product->yeasts.begin(), product->yeasts.end(), yeast_sort_test); + + const QStringList labels({tr("Yeast"), tr("Laboratory"), tr("Code"), tr("Type"), tr("Use for"), tr("Min. °C"), tr("Max. °C"), + tr("Tol. %"), tr("Attn. %"), tr("Amount"), tr("Delete"), tr("Edit") }); + + ui->yeastsTable->setColumnCount(12); + ui->yeastsTable->setColumnWidth(0, 200); /* Yeast */ + ui->yeastsTable->setColumnWidth(1, 125); /* Laboratory */ + ui->yeastsTable->setColumnWidth(2, 80); /* Code */ + ui->yeastsTable->setColumnWidth(3, 80); /* Type */ + ui->yeastsTable->setColumnWidth(4, 100); /* Usage */ + ui->yeastsTable->setColumnWidth(5, 60); /* Min. */ + ui->yeastsTable->setColumnWidth(6, 60); /* Max. */ + ui->yeastsTable->setColumnWidth(7, 60); /* Tolerance */ + ui->yeastsTable->setColumnWidth(8, 60); /* Attenuation */ + ui->yeastsTable->setColumnWidth(9, 90); /* Amount */ + ui->yeastsTable->setColumnWidth(10, 80); /* Delete */ + ui->yeastsTable->setColumnWidth(11, 80); /* Edit */ + ui->yeastsTable->setHorizontalHeaderLabels(labels); + ui->yeastsTable->verticalHeader()->hide(); + ui->yeastsTable->setRowCount(product->yeasts.size()); + + for (int i = 0; i < product->yeasts.size(); i++) { + + ui->yeastsTable->setItem(i, 0, new QTableWidgetItem(product->yeasts.at(i).y_name)); + ui->yeastsTable->setItem(i, 1, new QTableWidgetItem(product->yeasts.at(i).y_laboratory)); + ui->yeastsTable->setItem(i, 2, new QTableWidgetItem(product->yeasts.at(i).y_product_id)); + + item = new QTableWidgetItem(yeast_forms[product->yeasts.at(i).y_form]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->yeastsTable->setItem(i, 3, item); + + item = new QTableWidgetItem(yeast_use[product->yeasts.at(i).y_use]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->yeastsTable->setItem(i, 4, item); + + item = new QTableWidgetItem(QString("%1").arg(product->yeasts.at(i).y_min_temperature, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->yeastsTable->setItem(i, 5, item); + + item = new QTableWidgetItem(QString("%1").arg(product->yeasts.at(i).y_max_temperature, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->yeastsTable->setItem(i, 6, item); + + item = new QTableWidgetItem(QString("%1").arg(product->yeasts.at(i).y_tolerance, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->yeastsTable->setItem(i, 7, item); + + item = new QTableWidgetItem(QString("%1").arg(product->yeasts.at(i).y_attenuation, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->yeastsTable->setItem(i, 8, item); + + if (product->yeasts.at(i).y_form == 0) + item = new QTableWidgetItem(QString("%1 pack").arg(product->yeasts.at(i).y_amount, 1, 'f', 0, '0')); + else if (product->yeasts.at(i).y_form == 1) + item = new QTableWidgetItem(QString("%1 gr").arg(product->yeasts.at(i).y_amount * 1000.0, 3, 'f', 2, '0')); + else + item = new QTableWidgetItem(QString("%1 ml").arg(product->yeasts.at(i).y_amount * 1000.0, 3, 'f', 2, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->yeastsTable->setItem(i, 9, item); + + 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(deleteYeastRow_clicked())); + pLayout = new QHBoxLayout(pWidget); + pLayout->addWidget(btn_dele); + pLayout->setContentsMargins(5, 0, 5, 0); + pWidget->setLayout(pLayout); + ui->yeastsTable->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(editYeastRow_clicked())); + pLayout = new QHBoxLayout(pWidget); + pLayout->addWidget(btn_edit); + pLayout->setContentsMargins(5, 0, 5, 0); + pWidget->setLayout(pLayout); + ui->yeastsTable->setCellWidget(i, 11, pWidget); + } +} + + +/* + * The results are not stored line in EditProduct. This is just a hint. + */ +void EditProduct::calcYeast() +{ + double sg = product->est_og; + double plato = Utils::sg_to_plato(sg); + double volume = product->batch_size * 0.9; // Volume min trub chiller loss. + bool maybe_starter = false; + double pitchrate = 0.75; + double initcells = 0; + + qDebug() << "calcYeast()"; + ui->yeastProcedure->setCurrentIndex(0); + + if (product->yeasts.size() == 0) + return; // No yeast in product. + + for (int i = 0; i < product->yeasts.size(); i++) { + if (product->yeasts.at(i).y_use == 0) { // Primary + if (product->yeasts.at(i).y_form == 1) { + /* + * Dry yeast, build the formule with the yeast parameters. + * Based on https://www.lallemandbrewing.com/en/canada/brewers-corner/brewing-tools/pitching-rate-calculator/ + */ + ui->yeastProcedure->setCurrentIndex(2); + ui->lo_gr_hlEdit->setValue(product->yeasts.at(i).y_gr_hl_lo); + ui->hi_gr_hlEdit->setValue(product->yeasts.at(i).y_gr_hl_hi); + ui->lo_sgEdit->setValue(product->yeasts.at(i).y_sg_lo); + ui->hi_sgEdit->setValue(product->yeasts.at(i).y_sg_hi); + double og = product->yeasts.at(i).y_sg_lo; + double f1 = product->yeasts.at(i).y_gr_hl_lo / 100.0; + double f2 = round(f1 / 5 * 1000000.0) / 1000000.0; + double multiplier = (sg <= og) ? f1 : (f1 + f2 * (sg - og) / 0.008); + qDebug() << " sg:" << sg << "og:" << og << "f1:" << f1 << "f2:" << f2 << "multiplier:" << multiplier; + double yeast_grams = round(volume * multiplier * 100.0) / 100.0; // * (100 / dataRecord.starter_viability), 2); + double yeast_gr_hl = round((yeast_grams / (volume * 0.01)) * 100.0) / 100.0; + ui->need_grEdit->setValue(yeast_grams); + ui->pitch_grEdit->setValue(yeast_gr_hl); + qDebug() << " need" << yeast_grams << "grams, gr/hl:" << yeast_gr_hl; + return; + } else { + /* + * Liquid, slant, culture etc. + * pitchrate see https://www.brewersfriend.com/yeast-pitch-rate-and-starter-calculator/ + * and http://braukaiser.com/blog/blog/2012/11/03/estimating-yeast-growth/ + */ + ui->yeastProcedure->setCurrentIndex(1); + if (product->yeasts.at(i).y_type == 0) { // Lager yeast + pitchrate = 1.5; + if (sg > 1.060) + pitchrate = 2.0; + } else if (product->yeasts.at(i).y_type == 6) { // Real Kveik + pitchrate = 0.075; + } else { + pitchrate = 0.75; + if (sg > 1.060) + pitchrate = 1.0; + } + if (product->yeasts.at(i).y_form == 0) + initcells = (product->yeasts.at(i).y_cells / 1000000000) * product->yeasts.at(i).y_amount * 0.97; // 97% viability assumed. + else + initcells = (product->yeasts.at(i).y_cells / 1000000) * product->yeasts.at(i).y_amount * 0.97; + + double needed = round(pitchrate * volume * plato * 10.0) / 10.0; + double starter = 0; + if (needed > initcells) { + maybe_starter = true; + starter = round(needed / 2.0) / 100.0; // A very rough starter size estimate. + } + + ui->pitchrateEdit->setValue(pitchrate); + ui->initcellsEdit->setValue(initcells); + ui->targetcellsEdit->setValue(needed); + ui->starterEdit->setValue(starter); + + qDebug() << " pitchrate:" << pitchrate << "needed:" << needed << "initcells:" << initcells << "starter" << maybe_starter << "size" << starter; + } + break; + } + } +} + + +void EditProduct::addYeastRow_clicked() +{ + Yeasts newy; + + qDebug() << "Add yeast row"; + + for (int i = 0; i < product->yeasts.size(); i++) { + if (product->yeasts.at(i).y_amount == 0) + return; // Add only one at a time. + } + + newy.y_name = "Select one"; + newy.y_laboratory = ""; + newy.y_product_id = ""; + newy.y_amount = 0; + newy.y_type = 0; + newy.y_form = 0; + newy.y_min_temperature = 0; + newy.y_max_temperature = 0; + newy.y_flocculation = 0; + newy.y_attenuation = 0; + newy.y_cells = 0; + newy.y_tolerance = 0; + newy.y_inventory = 0; + newy.y_use = 0; + newy.y_sta1 = false; + newy.y_bacteria = false; + newy.y_harvest_top = false; + newy.y_harvest_time = 0; + newy.y_pitch_temperature = 0; + newy.y_pofpos = false; + newy.y_zymocide = 0; + newy.y_gr_hl_lo = 0; + newy.y_sg_lo = 0; + newy.y_gr_hl_hi = 0; + newy.y_sg_hi = 0; + newy.y_cost = 0; + + product->yeasts.append(newy); + emit refreshAll(); +} + + +void EditProduct::deleteYeastRow_clicked() +{ + if (product->locked || product->yeasts.size() < 1) + return; + + QPushButton *pb = qobject_cast(QObject::sender()); + int row = pb->objectName().toInt(); + qDebug() << "Delete yeast row" << row << product->yeasts.size(); + + int rc = QMessageBox::warning(this, tr("Delete yeast"), tr("Delete %1").arg(product->yeasts.at(row).y_name), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + if (rc == QMessageBox::No) + return; + + product->yeasts.removeAt(row); + is_changed(); + emit refreshAll(); +} + + +void EditProduct::yeast_amount_changed(double val) +{ + QTableWidgetItem *item; + + qDebug() << "yeast_amount_changed()" << product->yeasts_row << val; + + if (product->yeasts.at(product->yeasts_row).y_form == 0) { + product->yeasts[product->yeasts_row].y_amount = val; + item = new QTableWidgetItem(QString("%1 pack").arg(val, 1, 'f', 0, '0')); + } else if (product->yeasts.at(product->yeasts_row).y_form == 1) { + product->yeasts[product->yeasts_row].y_amount = val / 1000.0; + item = new QTableWidgetItem(QString("%1 gr").arg(val, 3, 'f', 2, '0')); + } else { + product->yeasts[product->yeasts_row].y_amount = val / 1000.0; + item = new QTableWidgetItem(QString("%1 ml").arg(val, 3, 'f', 2, '0')); + } + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->yeastsTable->setItem(product->yeasts_row, 9, item); + + calcYeast(); + is_changed(); +} + + +void EditProduct::yeast_select_changed(int val) +{ + QSqlQuery query; + bool instock = yinstockEdit->isChecked(); + QString w; + QTableWidgetItem *item; + + if (val < 1) + return; + + qDebug() << "yeast_select_changed()" << product->yeasts_row << val << instock; + + /* + * Search the yeast pointed by the index and instock flag. + */ + QString sql = "SELECT name,laboratory,product_id,type,form,min_temperature,max_temperature,flocculation,attenuation," + "cells,tolerance,sta1,bacteria,harvest_top,harvest_time,pitch_temperature,pofpos,zymocide," + "gr_hl_lo,sg_lo,gr_hl_hi,sg_hi,cost FROM inventory_yeasts "; + if (instock) + sql.append("WHERE inventory > 0 "); + sql.append("ORDER BY laboratory,product_id,name"); + query.prepare(sql); + query.exec(); + query.first(); + for (int i = 0; i < (val - 1); i++) { + query.next(); + } + qDebug() << "found" << query.value(0).toString() << query.value(2).toString(); + + /* + * Replace the yeast record contents + */ + product->yeasts[product->yeasts_row].y_name = query.value(0).toString(); + product->yeasts[product->yeasts_row].y_laboratory = query.value(1).toString(); + product->yeasts[product->yeasts_row].y_product_id = query.value(2).toString(); + product->yeasts[product->yeasts_row].y_type = query.value(3).toInt(); + product->yeasts[product->yeasts_row].y_form = query.value(4).toInt(); + product->yeasts[product->yeasts_row].y_min_temperature = query.value(5).toDouble(); + product->yeasts[product->yeasts_row].y_max_temperature = query.value(6).toDouble(); + product->yeasts[product->yeasts_row].y_flocculation = query.value(7).toInt(); + product->yeasts[product->yeasts_row].y_attenuation = query.value(8).toDouble(); + product->yeasts[product->yeasts_row].y_cells = query.value(9).toDouble(); + product->yeasts[product->yeasts_row].y_tolerance = query.value(10).toDouble(); + product->yeasts[product->yeasts_row].y_sta1 = query.value(11).toInt() ? true:false; + product->yeasts[product->yeasts_row].y_bacteria = query.value(12).toInt() ? true:false; + product->yeasts[product->yeasts_row].y_harvest_top = query.value(13).toInt() ? true:false; + product->yeasts[product->yeasts_row].y_harvest_time = query.value(14).toInt(); + product->yeasts[product->yeasts_row].y_pitch_temperature = query.value(15).toDouble(); + product->yeasts[product->yeasts_row].y_pofpos = query.value(16).toInt() ? true:false; + product->yeasts[product->yeasts_row].y_zymocide = query.value(17).toInt(); + product->yeasts[product->yeasts_row].y_gr_hl_lo = query.value(18).toInt(); + product->yeasts[product->yeasts_row].y_sg_lo = query.value(19).toDouble(); + product->yeasts[product->yeasts_row].y_gr_hl_hi = query.value(20).toInt(); + product->yeasts[product->yeasts_row].y_sg_hi = query.value(21).toDouble(); + product->yeasts[product->yeasts_row].y_cost = query.value(22).toDouble(); + + /* + * Update the visible fields + */ + ynameEdit->setText(product->yeasts.at(product->yeasts_row).y_name); + ylaboratoryEdit->setText(product->yeasts.at(product->yeasts_row).y_laboratory); + yproduct_idEdit->setText(product->yeasts.at(product->yeasts_row).y_product_id); + if (product->yeasts.at(product->yeasts_row).y_form == 0) { + yamountEdit->setDecimals(0); + yamountEdit->setSingleStep(1.0); + yamountLabel->setText(tr("Total packs:")); + } else if (product->yeasts.at(product->yeasts_row).y_form == 1) { + yamountEdit->setDecimals(1); + yamountEdit->setSingleStep(0.5); + yamountLabel->setText(tr("Amount in gr:")); + } else { + yamountEdit->setDecimals(1); + yamountEdit->setSingleStep(0.5); + yamountLabel->setText(tr("Amount in ml:")); + } + + ui->yeastsTable->setItem(product->yeasts_row, 0, new QTableWidgetItem(product->yeasts.at(product->yeasts_row).y_name)); + ui->yeastsTable->setItem(product->yeasts_row, 1, new QTableWidgetItem(product->yeasts.at(product->yeasts_row).y_laboratory)); + ui->yeastsTable->setItem(product->yeasts_row, 2, new QTableWidgetItem(product->yeasts.at(product->yeasts_row).y_product_id)); + + item = new QTableWidgetItem(yeast_forms[product->yeasts.at(product->yeasts_row).y_form]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->yeastsTable->setItem(product->yeasts_row, 3, item); + + item = new QTableWidgetItem(QString("%1").arg(product->yeasts.at(product->yeasts_row).y_min_temperature, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->yeastsTable->setItem(product->yeasts_row, 5, item); + + item = new QTableWidgetItem(QString("%1").arg(product->yeasts.at(product->yeasts_row).y_max_temperature, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->yeastsTable->setItem(product->yeasts_row, 6, item); + + item = new QTableWidgetItem(QString("%1").arg(product->yeasts.at(product->yeasts_row).y_tolerance, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->yeastsTable->setItem(product->yeasts_row, 7, item); + + item = new QTableWidgetItem(QString("%1").arg(product->yeasts.at(product->yeasts_row).y_attenuation, 2, 'f', 1, '0')); + item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); + ui->yeastsTable->setItem(product->yeasts_row, 8, item); + + calcYeast(); + is_changed(); +} + + +void EditProduct::yeast_instock_changed(bool val) +{ + QSqlQuery query; + + qDebug() << "yeast_instock_changed()" << product->yeasts_row << val; + + this->yselectEdit->setCurrentIndex(-1); + this->yselectEdit->clear(); + QString sql = "SELECT name,laboratory,product_id,inventory FROM inventory_yeasts "; + if (val) + sql.append("WHERE inventory > 0 "); + sql.append("ORDER BY laboratory,product_id,name"); + query.prepare(sql); + query.exec(); + query.first(); + this->yselectEdit->addItem(""); // Start with empty value + for (int i = 0; i < query.size(); i++) { + this->yselectEdit->addItem(query.value(1).toString()+" - "+query.value(2).toString()+" "+query.value(0).toString() + + QString(" (%1 gr)").arg(query.value(3).toDouble() * 1000.0, 2, 'f', 1, '0')); + query.next(); + } +} + + +void EditProduct::yeast_useat_changed(int val) +{ + qDebug() << "yeast_useat_changed()" << product->yeasts_row << val; + + product->yeasts[product->yeasts_row].y_use = val; + QTableWidgetItem *item = new QTableWidgetItem(yeast_use[val]); + item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); + ui->yeastsTable->setItem(product->yeasts_row, 5, item); + is_changed(); + emit refreshAll(); +} + + +void EditProduct::editYeastRow_clicked() +{ + QSqlQuery query; + + if (product->locked) + return; + + QPushButton *pb = qobject_cast(QObject::sender()); + product->yeasts_row = pb->objectName().toInt(); + qDebug() << "Edit yeast row" << product->yeasts_row; + Yeasts backup = product->yeasts.at(product->yeasts_row); + + QDialog* dialog = new QDialog(this); + dialog->resize(738, 260); + QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog); + buttonBox->setObjectName(QString::fromUtf8("buttonBox")); + buttonBox->setGeometry(QRect(30, 210, 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("Yeast name:")); + nameLabel->setGeometry(QRect(10, 10, 141, 20)); + nameLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + QLabel *laboratoryLabel = new QLabel(dialog); + laboratoryLabel->setObjectName(QString::fromUtf8("laboratoryLabel")); + laboratoryLabel->setText(tr("Laboratory:")); + laboratoryLabel->setGeometry(QRect(10, 40, 141, 20)); + laboratoryLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + QLabel *product_idLabel = new QLabel(dialog); + product_idLabel->setObjectName(QString::fromUtf8("product_idLabel")); + product_idLabel->setText(tr("Laboratory:")); + product_idLabel->setGeometry(QRect(10, 70, 141, 20)); + product_idLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + QLabel *selectLabel = new QLabel(dialog); + selectLabel->setObjectName(QString::fromUtf8("selectLabel")); + selectLabel->setText(tr("Select yeast:")); + selectLabel->setGeometry(QRect(10,100, 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,100, 121, 20)); + instockLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + yamountLabel = new QLabel(dialog); + yamountLabel->setObjectName(QString::fromUtf8("amountLabel")); + if (product->yeasts.at(product->yeasts_row).y_form == 0) + yamountLabel->setText(tr("Total packs:")); + else if (product->yeasts.at(product->yeasts_row).y_form == 1) + yamountLabel->setText(tr("Amount in gr:")); + else + yamountLabel->setText(tr("Amount in ml:")); + yamountLabel->setGeometry(QRect(10, 130, 141, 20)); + yamountLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + QLabel *useatLabel = new QLabel(dialog); + useatLabel->setObjectName(QString::fromUtf8("useatLabel")); + useatLabel->setText(tr("Use at:")); + useatLabel->setGeometry(QRect(10, 160, 141, 20)); + useatLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + + ynameEdit = new QLineEdit(dialog); + ynameEdit->setObjectName(QString::fromUtf8("ynameEdit")); + ynameEdit->setText(product->yeasts.at(product->yeasts_row).y_name); + ynameEdit->setGeometry(QRect(160, 10, 511, 23)); + ynameEdit->setReadOnly(true); + ylaboratoryEdit = new QLineEdit(dialog); + ylaboratoryEdit->setObjectName(QString::fromUtf8("ylaboratoryEdit")); + ylaboratoryEdit->setText(product->yeasts.at(product->yeasts_row).y_laboratory); + ylaboratoryEdit->setGeometry(QRect(160, 40, 511, 23)); + ylaboratoryEdit->setReadOnly(true); + yproduct_idEdit = new QLineEdit(dialog); + yproduct_idEdit->setObjectName(QString::fromUtf8("yproduct_idEdit")); + yproduct_idEdit->setText(product->yeasts.at(product->yeasts_row).y_product_id); + yproduct_idEdit->setGeometry(QRect(160, 70, 511, 23)); + yproduct_idEdit->setReadOnly(true); + yselectEdit = new QComboBox(dialog); + yselectEdit->setObjectName(QString::fromUtf8("selectEdit")); + yselectEdit->setGeometry(QRect(160,100, 371, 23)); + yinstockEdit = new QCheckBox(dialog); + yinstockEdit->setObjectName(QString::fromUtf8("yinstockEdit")); + yinstockEdit->setGeometry(QRect(655,100, 85, 21)); + yinstockEdit->setChecked(true); + yamountEdit = new QDoubleSpinBox(dialog); + yamountEdit->setObjectName(QString::fromUtf8("yamountEdit")); + yamountEdit->setGeometry(QRect(160, 130, 121, 24)); + yamountEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + yamountEdit->setAccelerated(true); + if (product->yeasts.at(product->yeasts_row).y_form == 0) { + yamountEdit->setDecimals(0); + yamountEdit->setSingleStep(1.0); + yamountEdit->setValue(product->yeasts.at(product->yeasts_row).y_amount); + } else if (product->yeasts.at(product->yeasts_row).y_form == 1) { + yamountEdit->setDecimals(1); + yamountEdit->setSingleStep(0.5); + yamountEdit->setValue(product->yeasts.at(product->yeasts_row).y_amount * 1000.0); + } else { + yamountEdit->setDecimals(1); + yamountEdit->setSingleStep(0.5); + yamountEdit->setValue(product->yeasts.at(product->yeasts_row).y_amount * 1000.0); + } + yamountEdit->setMaximum(1000000000.0); + useatEdit = new QComboBox(dialog); + useatEdit->setObjectName(QString::fromUtf8("useatEdit")); + useatEdit->setGeometry(QRect(160, 160, 161, 23)); + useatEdit->addItem(tr("Primary")); + useatEdit->addItem(tr("Secondary")); + useatEdit->addItem(tr("Tertiary")); + useatEdit->addItem(tr("Bottle")); + useatEdit->setCurrentIndex(product->yeasts.at(product->yeasts_row).y_use); + + yeast_instock_changed(true); + + connect(yselectEdit, QOverload::of(&QComboBox::currentIndexChanged), this, &EditProduct::yeast_select_changed); + connect(yamountEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::yeast_amount_changed); + connect(useatEdit, QOverload::of(&QComboBox::currentIndexChanged), this, &EditProduct::yeast_useat_changed); + connect(yinstockEdit, &QCheckBox::stateChanged, this, &EditProduct::yeast_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->yeasts[product->yeasts_row] = backup; + } else { + + } + + disconnect(yselectEdit, nullptr, nullptr, nullptr); + disconnect(yamountEdit, nullptr, nullptr, nullptr); + disconnect(useatEdit, nullptr, nullptr, nullptr); + disconnect(yinstockEdit, nullptr, nullptr, nullptr); + disconnect(buttonBox, nullptr, nullptr, nullptr); + + emit refreshAll(); +} + + +void EditProduct::adjustYeasts(double factor) +{ + double amount; + + if (product->yeasts.size() == 0) + return; + + for (int i = 0; i < product->yeasts.size(); i++) { + if (product->yeasts.at(i).y_form == 1) { // Only adjust dry yeast + amount = product->yeasts.at(i).y_amount * factor; + product->yeasts[i].y_amount = amount; + } + } +} + diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 src/EditProductTab7.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/EditProductTab7.cpp Thu Apr 28 22:49:13 2022 +0200 @@ -0,0 +1,682 @@ +/** + * 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 . + */ + + +void EditProduct::refreshMashs() +{ + QString w; + QWidget* pWidget; + QHBoxLayout* pLayout; + QTableWidgetItem *item; + QIcon down_icon, up_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); + + const QStringList labels({tr("Step name"), tr("Type"), tr("Start"), tr("End"), tr("Rest"), tr("Ramp"), + tr("Inf/dec"), tr("Inf/dec"), tr("Volume"), tr("W/G ratio"), "", "", tr("Delete"), tr("Edit") }); + + ui->mashsTable->setColumnCount(14); + ui->mashsTable->setColumnWidth(0, 189); /* Step name */ + ui->mashsTable->setColumnWidth(1, 100); /* Type */ + ui->mashsTable->setColumnWidth(2, 70); /* Start temp */ + ui->mashsTable->setColumnWidth(3, 70); /* End temp */ + ui->mashsTable->setColumnWidth(4, 70); /* Rest time */ + ui->mashsTable->setColumnWidth(5, 70); /* Ramp time */ + ui->mashsTable->setColumnWidth(6, 70); /* Infusion vol */ + ui->mashsTable->setColumnWidth(7, 70); /* Infusion tmp */ + ui->mashsTable->setColumnWidth(8, 70); /* Volume */ + ui->mashsTable->setColumnWidth(9, 80); /* W/G ratio */ + ui->mashsTable->setColumnWidth(10, 30); /* Up button */ + ui->mashsTable->setColumnWidth(11, 30); /* Down button */ + ui->mashsTable->setColumnWidth(12, 80); /* Delete */ + ui->mashsTable->setColumnWidth(13, 80); /* Edit */ + ui->mashsTable->setHorizontalHeaderLabels(labels); + ui->mashsTable->verticalHeader()->hide(); + ui->mashsTable->setRowCount(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(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); + + if (i > 0) { + pWidget = new QWidget(); + QPushButton* btn_up = new QPushButton(); + btn_up->setObjectName(QString("%1").arg(i)); /* Send row with the button */ + btn_up->setIcon(up_icon); + connect(btn_up, SIGNAL(clicked()), this, SLOT(upMashRow_clicked())); + pLayout = new QHBoxLayout(pWidget); + pLayout->addWidget(btn_up); + pLayout->setContentsMargins(5, 0, 5, 0); + pWidget->setLayout(pLayout); + ui->mashsTable->setCellWidget(i, 10, pWidget); + } else { + ui->mashsTable->removeCellWidget(i, 10); + } + + if (i < (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, 11, pWidget); + } else { + ui->mashsTable->removeCellWidget(i, 11); + } + + pWidget = new QWidget(); + QPushButton* btn_dele = new QPushButton(); + btn_dele->setObjectName(QString("%1").arg(i)); /* Send row with the button */ + btn_dele->setText(tr("Delete")); + connect(btn_dele, SIGNAL(clicked()), this, SLOT(deleteMashRow_clicked())); + pLayout = new QHBoxLayout(pWidget); + pLayout->addWidget(btn_dele); + pLayout->setContentsMargins(5, 0, 5, 0); + pWidget->setLayout(pLayout); + ui->mashsTable->setCellWidget(i, 12, pWidget); + + pWidget = new QWidget(); + QPushButton* btn_edit = new QPushButton(); + btn_edit->setObjectName(QString("%1").arg(i)); /* Send row with the button */ + btn_edit->setText(tr("Edit")); + connect(btn_edit, SIGNAL(clicked()), this, SLOT(editMashRow_clicked())); + pLayout = new QHBoxLayout(pWidget); + pLayout->addWidget(btn_edit); + pLayout->setContentsMargins(5, 0, 5, 0); + pWidget->setLayout(pLayout); + ui->mashsTable->setCellWidget(i, 13, pWidget); + } +} + + +double EditProduct::infusionVol(double step_infused, double step_mashkg, double infuse_temp, double step_temp, double last_temp) +{ + double a = last_temp * (equip_tun_weight * equip_tun_specific_heat + step_infused * SpecificHeatWater + step_mashkg * SpecificHeatMalt); + double b = step_temp * (equip_tun_weight * equip_tun_specific_heat + step_infused * SpecificHeatWater + step_mashkg * SpecificHeatMalt); + double vol = round(((b - a) / ((infuse_temp - step_temp) * SpecificHeatWater)) * 100.0) / 100.0; + + if (vol < 0) + vol = 0; + qDebug() << " infusionVol(" << step_infused << "," << step_mashkg << "," << infuse_temp <<"," << step_temp << "," << last_temp << "):" << vol; + return vol; +} + + +double EditProduct::decoctionVol(double step_volume, double step_temp, double prev_temp) +{ + double a = (equip_tun_weight * equip_tun_specific_heat + step_volume * SpecificHeatWater) * (step_temp - prev_temp); + double b = SpecificHeatWater * (99 - step_temp); + double vol = 0; + + if (b > 0) + vol = round((a / b) * 1000000.0) / 1000000.0; + qDebug() << " decoctionVol(" << step_volume << "," << step_temp << "," << prev_temp << "):" << vol; + return vol; +} + + +void 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 + equip_tun_weight * tuntemp * equip_tun_specific_heat; + b = product->mashs[i].step_temp * + (equip_tun_weight * equip_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; + } + } + + /* 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')); +} + + +void EditProduct::addMashRow_clicked() +{ + Mashs 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; + + QPushButton *pb = qobject_cast(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(QObject::sender()); + int row = pb->objectName().toInt(); + qDebug() << "Move up mash row" << row << product->mashs.size(); + + Mashs 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(QObject::sender()); + int row = pb->objectName().toInt(); + qDebug() << "Move down mash row" << row << product->mashs.size(); + + Mashs 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); + + is_changed(); + emit refreshAll(); +} + + +void EditProduct::editMashRow_clicked() +{ + QSqlQuery query; + + if (product->locked) + return; + + QPushButton *pb = qobject_cast(QObject::sender()); + product->mashs_row = pb->objectName().toInt(); + qDebug() << "Edit mash row" << product->mashs_row; + Mashs backup = product->mashs.at(product->mashs_row); + + QDialog* dialog = new QDialog(this); + dialog->resize(738, 230); + QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog); + buttonBox->setObjectName(QString::fromUtf8("buttonBox")); + buttonBox->setGeometry(QRect(30, 180, 671, 32)); + buttonBox->setLayoutDirection(Qt::LeftToRight); + buttonBox->setOrientation(Qt::Horizontal); + buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); + buttonBox->setCenterButtons(true); + + QLabel *nameLabel = new QLabel(dialog); + nameLabel->setObjectName(QString::fromUtf8("nameLabel")); + nameLabel->setText(tr("Step name:")); + nameLabel->setGeometry(QRect(10, 10, 141, 20)); + nameLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + stepnameEdit = new QLineEdit(dialog); + stepnameEdit->setObjectName(QString::fromUtf8("stepnameEdit")); + stepnameEdit->setText(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); + + /* + * Only used for Infusion steps. + */ + ivolLabel = new QLabel(dialog); + ivolLabel->setObjectName(QString::fromUtf8("ivolLabel")); + ivolLabel->setText(tr("Infusion volume:")); + ivolLabel->setGeometry(QRect(10, 130, 141, 20)); + ivolLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + ivolLabel->setVisible(product->mashs.at(product->mashs_row).step_type == 0); + stepivolEdit = new QDoubleSpinBox(dialog); + stepivolEdit->setObjectName(QString::fromUtf8("stepivolEdit")); + stepivolEdit->setGeometry(QRect(160, 130, 121, 24)); + stepivolEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + stepivolEdit->setVisible(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, 130, 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, 130, 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::of(&QComboBox::currentIndexChanged), this, &EditProduct::step_type_changed); + connect(steptempEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::step_temp_changed); + connect(endtempEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::end_temp_changed); + connect(steptimeEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::step_time_changed); + connect(ramptimeEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::ramp_time_changed); + connect(stepivolEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::infuse_changed); + connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject())); + connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept())); + + dialog->setModal(true); + dialog->exec(); + if (dialog->result() == QDialog::Rejected) { + qDebug() << "reject and rollback"; + 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(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) { + qDebug() << "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(); + Mashs 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(); +} + + diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 src/EditProductTab8.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/EditProductTab8.cpp Thu Apr 28 22:49:13 2022 +0200 @@ -0,0 +1,797 @@ +/** + * EditProduct.cpp is part of bmsapp. + * + * tab 8, water. + * + * 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 . + */ + + +void EditProduct::refreshWaters() +{ + + // calc_acid + ui->mw_phEdit->setValue(product->mash_ph); + // mash_name + //ui->mw_acidpercEdit->setValue(product-> + +} + + +/* + * Z alkalinity is the amount of acid (in mEq/l) needed to bring water to the target pH (Z pH) + */ +double EditProduct::ZAlkalinity(double pHZ) +{ + double C43 = Utils::Charge(4.3); + double Cw = Utils::Charge(product->wg_ph); + double Cz = Utils::Charge(pHZ); + double DeltaCNaught = -C43 + Cw; + double CT = product->wg_total_alkalinity / 50 / DeltaCNaught; + double DeltaCZ = -Cz + Cw; + return CT * DeltaCZ; +} + + +/* + * Z Residual alkalinity is the amount of acid (in mEq/l) needed + * to bring the water in the mash to the target pH (Z pH). + */ +double EditProduct::ZRA(double pHZ) +{ + double Calc = product->wg_calcium / (MMCa / 2); + double Magn = product->wg_magnesium / (MMMg / 2); + double Z = ZAlkalinity(pHZ); + return Z - (Calc / 3.5 + Magn / 7); +} + + +double EditProduct::BufferCapacity(Fermentables F) +{ + double C1 = 0; + + if ((F.f_di_ph != 5.7) && ((F.f_acid_to_ph_57 < - 0.1) || (F.f_acid_to_ph_57 > 0.1))) { + C1 = F.f_acid_to_ph_57 / (F.f_di_ph - 5.7); + } else { + /* + * If the acid_to_ph_5.7 is unknown from the maltster, guess the required acid. + */ + switch (F.f_graintype) { + case 0: // Base, Special, Kilned + case 3: + case 5: C1 = 0.014 * F.f_color - 34.192; + break; + case 2: C1 = -0.0597 * F.f_color - 32.457; // Crystal + break; + case 1: C1 = 0.0107 * F.f_color - 54.768; // Roast + break; + case 4: C1 = -149; // Sour malt + break; + } + } + return C1; +} + + +double EditProduct::AcidRequired(double ZpH, Fermentables F) +{ + double C1 = BufferCapacity(F); + double x = F.f_di_ph; + return C1 * (ZpH - x); +} + + +double EditProduct::ProtonDeficit(double pHZ) +{ + double C1, x; + int i, error_count = 0; + double Result = ZRA(pHZ) * product->wg_amount; + Fermentables F; + + /* + * proton deficit for the grist + */ + if (product->fermentables.size()) { + for (i = 0; i < product->fermentables.size(); i++) { + F = product->fermentables.at(i); + if (F.f_added == 0 && F.f_graintype != 6) { // Added == Mash && graintype != No Malt + x = AcidRequired(pHZ, F) * F.f_amount; + Result += x; + } + } + } else { + error_count++; + if (error_count < 5) + qDebug() << " ProtonDeficit" << pHZ << "invalid grist, return" << Result; + } + return Result; +} + + +double EditProduct::MashpH() +{ + int n = 0; + double pH = 5.4; + double deltapH = 0.001; + double deltapd = 0.1; + double pd = ProtonDeficit(pH); + + while (((pd < -deltapd) || (pd > deltapd)) && (n < 2000)) { + n++; + if (pd < -deltapd) + pH -= deltapH; + else if (pd > deltapd) + pH += deltapH; + pd = ProtonDeficit(pH); + } + pH = round(pH * 1000000) / 1000000.0; + qDebug() << " MashpH() n:" << n << "pH:" << pH; + return pH; +} + + +void EditProduct::calcWater() +{ + double liters = 0; + double calcium = 0; + double magnesium = 0; + double sodium = 0; + double total_alkalinity = 0; + double bicarbonate = 0; + double chloride = 0; + double sulfate = 0; + double ph = 0; + double TpH = 0; + double frac, RA; + double protonDeficit = 0; + double Acid = 0, Acidmg = 0; + int AT; + + qDebug() << "calcWater()"; + + /* + * If there is a dilute water source, mix the waters. + */ + if (product->w2_name != "") { + liters = product->w1_amount + product->w2_amount; + calcium = Utils::mix(product->w1_amount, product->w2_amount, product->w1_calcium, product->w2_calcium); + magnesium = Utils::mix(product->w1_amount, product->w2_amount, product->w1_magnesium, product->w2_magnesium); + sodium = Utils::mix(product->w1_amount, product->w2_amount, product->w1_sodium, product->w2_sodium); + chloride = Utils::mix(product->w1_amount, product->w2_amount, product->w1_chloride, product->w2_chloride); + sulfate = Utils::mix(product->w1_amount, product->w2_amount, product->w1_sulfate, product->w2_sulfate); + total_alkalinity = Utils::mix(product->w1_amount, product->w2_amount, product->w1_total_alkalinity, product->w2_total_alkalinity); + ph = -log10(((pow(10, -product->w1_ph) * product->w1_amount) + (pow(10, -product->w2_ph) * product->w2_amount)) / liters); + } else { + liters = product->w1_amount; + calcium = product->w1_calcium; + magnesium = product->w1_magnesium; + sodium = product->w1_sodium; + chloride = product->w1_chloride; + sulfate = product->w1_sulfate; + total_alkalinity = product->w1_total_alkalinity; + ph = product->w1_ph; + } + + product->wg_amount = liters; + product->wg_calcium = round(calcium * 10.0) / 10.0; + product->wg_magnesium = round(magnesium * 10.0) / 10.0; + product->wg_sodium = round(sodium * 10.0) / 10.0; + product->wg_chloride = round(chloride * 10.0) / 10.0; + product->wg_sulfate = round(sulfate * 10.0) / 10.0; + product->wg_total_alkalinity = round(total_alkalinity * 10.0) / 10.0; + product->wg_ph = ph; + + ui->wg_volEdit->setValue(liters); + ui->wg_caEdit->setValue(calcium); + ui->wg_mgEdit->setValue(magnesium); + ui->wg_hco3Edit->setValue(total_alkalinity * 1.22); + ui->wg_caco3Edit->setValue(total_alkalinity); + ui->wg_naEdit->setValue(sodium); + ui->wg_clEdit->setValue(chloride); + ui->wg_so4Edit->setValue(sulfate); + ui->wg_phEdit->setValue(ph); + bicarbonate = total_alkalinity * 1.22; + + /* Save mixed water ions for later */ + double wg_calcium = calcium; + double wg_sodium = sodium; + double wg_total_alkalinity = total_alkalinity; + double wg_chloride = chloride; + double wg_sulfate = sulfate; + double wg_bicarbonate = bicarbonate; + + double mash_ph = MashpH(); + qDebug() << " Distilled water mash pH:" << mash_ph; + + /* Calculate Salt additions */ + if (liters > 0) { + calcium += ( ui->bs_cacl2Edit->value() * MMCa / MMCaCl2 * 1000 + ui->bs_caso4Edit->value() * MMCa / MMCaSO4 * 1000 + + ui->bs_caco3Edit->value() * MMCa / MMCaCO3 * 1000) / liters; + magnesium += (ui->bs_mgso4Edit->value() * MMMg / MMMgSO4 * 1000 + ui->bs_mgcl2Edit->value() * MMMg / MMMgCl2 * 1000) / liters; + sodium += (ui->bs_naclEdit->value() * MMNa / MMNaCl * 1000 + ui->bs_nahco3Edit->value() * MMNa / MMNaHCO3 * 1000) / liters; + sulfate += (ui->bs_caso4Edit->value() * MMSO4 / MMCaSO4 * 1000 + ui->bs_mgso4Edit->value() * MMSO4 / MMMgSO4 * 1000) / liters; + chloride += (2 * ui->bs_cacl2Edit->value() * MMCl / MMCaCl2 * 1000 + ui->bs_naclEdit->value() * MMCl / MMNaCl * 1000 + + ui->bs_mgcl2Edit->value() * MMCl / MMMgCl2 * 1000) / liters; + bicarbonate += (ui->bs_nahco3Edit->value() * MMHCO3 / MMNaHCO3 * 1000 + ui->bs_caco3Edit->value() / 3 * MMHCO3 / MMCaCO3 * 1000) / liters; + } + + const QSignalBlocker blocker1(ui->mw_acidPick); + const QSignalBlocker blocker2(ui->mw_acidpercEdit); + const QSignalBlocker blocker3(ui->mw_acidvolEdit); + const QSignalBlocker blocker4(ui->wb_phEdit); + const QSignalBlocker blocker5(ui->mw_phEdit); + + if (product->wa_acid_name < 0 || product->wa_acid_name >= my_acids.size()) { + product->wa_acid_name = 0; + product->wa_acid_perc = my_acids.at(0).AcidPrc; + ui->mw_acidPick->setCurrentIndex(0); + ui->mw_acidpercEdit->setValue(my_acids.at(0).AcidPrc); + } + AT = product->wa_acid_name; + + /* + * Note that the next calculations do not correct the pH change by the added salts. + * This pH change is at most 0.1 pH and is a minor difference in Acid amount. + */ + if (product->calc_acid) { + /* + * Auto calculate the needed acid. + */ + TpH = product->mash_ph; + protonDeficit = ProtonDeficit(TpH); + qDebug() << " calc_acid tgt:" << TpH << "protonDeficit:" << protonDeficit; + if (protonDeficit > 0) { + frac = Utils::CalcFrac(TpH, my_acids[AT].pK1, my_acids[AT].pK2, my_acids[AT].pK3); + Acid = protonDeficit / frac; + Acid *= my_acids[AT].MolWt; // mg. + Acidmg = Acid; + Acid = Acid / my_acids[AT].AcidSG; + Acid = round((Acid / (product->wa_acid_perc / 100.0)) * 100.0) / 100.0; + qDebug() << " Mash auto Acid final ml:" << Acid; + + QString w = my_acids[AT].name_en + ' ' + my_acids[AT].name_nl; + brewing_salt_sub(w, Acid); + ui->mw_acidvolEdit->setValue(Acid); + + bicarbonate = bicarbonate - protonDeficit * frac / liters; + total_alkalinity = bicarbonate * 50 / 61; + } + ph = TpH; + ui->wb_phEdit->setValue(ph); + product->mash_ph = ph; + } else { // Manual + /* + * Manual adjust acid, calculate resulting pH. + */ + double pHa = ph; // Mixed water pH. + // Then calculate the new pH with added acids and malts + qDebug() << " Mash pH:" << pHa; + Acid = my_acids[AT].AcidSG * (product->wa_acid_perc / 100.0); // ml + Acid *= ui->mw_acidvolEdit->value(); + Acid /= my_acids[AT].MolWt; // mg; + Acidmg = Acid; + + //find the pH where the protondeficit = protondeficit by the acid + frac = Utils::CalcFrac(pHa, my_acids[AT].pK1, my_acids[AT].pK2, my_acids[AT].pK3); + protonDeficit = Acid * frac; + //qDebug() << " protonDeficit Acid:" << protonDeficit << "frac:" << frac << "pH:" << pHa; + + double deltapH = 0.001; + double deltapd = 0.1; + double pd = round(ProtonDeficit(pHa) * 1000000.0) / 1000000.0; + int n = 0; + while (((pd < (protonDeficit - deltapd)) || (pd > (protonDeficit + deltapd))) && (n < 4000)) { + n++; + if (pd < (protonDeficit - deltapd)) + pHa -= deltapH; + else if (pd > (protonDeficit + deltapd)) + pHa += deltapH; + frac = Utils::CalcFrac(pHa, my_acids[AT].pK1, my_acids[AT].pK2, my_acids[AT].pK3); + protonDeficit = Acid * frac; + pd = ProtonDeficit(pHa); + } + //qDebug() << " n:" << n << "pd:" << pd << "protonDeficit:" << protonDeficit << "frac:" << frac << "pHa:" << pHa; + + bicarbonate = wg_bicarbonate - protonDeficit * frac / liters; + total_alkalinity = bicarbonate * 50 / 61; + ph = pHa; + ui->wb_phEdit->setValue(ph); + ui->mw_phEdit->setValue(ph); + product->mash_ph = ph; + } + + if ((AT == 3) && (liters > 0)) { // Sulfuctic / Zwavelzuur + RA = ui->bs_caso4Edit->value() * MMSO4 / MMCaSO4 + ui->bs_mgso4Edit->value() * MMSO4 / MMMgSO4 + Acidmg / 1000 * MMSO4 / (MMSO4 + 2); + RA = 1000 * RA / liters; + sulfate = wg_sulfate + RA; // Not add to sulfate?? + } else if ((AT == 1) && (liters > 0)) { // Hydrochloric, Zoutzuur + RA = ui->bs_cacl2Edit->value() * MMCl / MMCaCl2 + ui->bs_naclEdit->value() * MMCl / MMNaCl + Acidmg / 1000 * MMCl / (MMCl + 1); + RA = 1000 * RA / liters; + chloride = wg_chloride + RA; + } + + double BUGU = GetBUGU(); + ui->buguEdit->setValue(BUGU); + if (BUGU < 0.32) + ui->buguResult->setText(tr("Very malty and sweet")); + else if (BUGU < 0.43) + ui->buguResult->setText(tr("Malty, sweet")); + else if (BUGU < 0.52) + ui->buguResult->setText(tr("Balanced")); + else if (BUGU < 0.63) + ui->buguResult->setText(tr("Hoppy, bitter")); + else + ui->buguResult->setText(tr("Very hoppy, very bitter")); + + double OptSO4Clratio = GetOptSO4Clratio(); + ui->so4clEdit->setValue(OptSO4Clratio); + ui->cur_so4clResult->setRange(0.7 * OptSO4Clratio, 1.3 * OptSO4Clratio); + if (OptSO4Clratio < 0.4) + ui->so4clResult->setText(tr("Too malty")); + else if (OptSO4Clratio < 0.6) + ui->so4clResult->setText(tr("Very malty")); + else if (OptSO4Clratio < 0.8) + ui->so4clResult->setText(tr("Malty")); + else if (OptSO4Clratio < 1.5) + ui->so4clResult->setText(tr("Balanced")); + else if (OptSO4Clratio < 2.0) + ui->so4clResult->setText(tr("Little bitter")); + else if (OptSO4Clratio < 4.0) + ui->so4clResult->setText(tr("Bitter")); + else if (OptSO4Clratio < 9.0) + ui->so4clResult->setText(tr("Very bitter")); + else + ui->so4clResult->setText(tr("Too bitter")); + if (chloride > 0) + RA = sulfate / chloride; + else + RA = 10; + ui->cur_so4clEdit->setValue(RA); + ui->cur_so4clResult->setValue(RA); + + product->wb_calcium = calcium; + product->wb_magnesium = magnesium; + product->wb_total_alkalinity = total_alkalinity; + product->wb_sodium = sodium; + product->wb_chloride = chloride; + product->wb_sulfate = sulfate; + product->wb_ph = ph; + + ui->wb_caEdit->setValue(calcium); + ui->wb_mgEdit->setValue(magnesium); + ui->wb_hco3Edit->setValue(bicarbonate); + ui->wb_caco3Edit->setValue(total_alkalinity); + ui->wb_naEdit->setValue(sodium); + ui->wb_clEdit->setValue(chloride); + ui->wb_so4Edit->setValue(sulfate); + + ui->wb_caEdit->setStyleSheet((calcium < 40 || calcium > 150) ? "background-color: red":"background-color: green"); + ui->wb_mgEdit->setStyleSheet((magnesium < 5 || magnesium > 40) ? "background-color: red":"background-color: green"); + ui->wb_naEdit->setStyleSheet((sodium > 150) ? "background-color: red":"background-color: green"); + /* + * Both chloride and sulfate should be above 50 according to + * John Palmer. So the Cl/SO4 ratio calculation will work. + */ + ui->wb_clEdit->setStyleSheet((chloride <= 50 || chloride > 150) ? "background-color: red":"background-color: green"); + ui->wb_so4Edit->setStyleSheet((sulfate <= 50 || sulfate > 400) ? "background-color: red":"background-color: green"); + /* + * (cloride + sulfate) > 500 is too high + */ + if ((chloride + sulfate) > 500) { + ui->wb_clEdit->setStyleSheet("background-color: red"); + ui->wb_so4Edit->setStyleSheet("background-color: red"); + } + ui->wb_phEdit->setStyleSheet((ph < 5.2 || ph > 5.6) ? "background-color: red":"background-color: green"); + ui->wb_hco3Edit->setStyleSheet((bicarbonate > 250) ? "background-color: red":"background-color: green"); + ui->wb_caco3Edit->setStyleSheet((bicarbonate > 250) ? "background-color: red":"background-color: green"); + + calcSparge(); +} + + +/* + * Based on the work of ajDeLange. + */ +void EditProduct::calcSparge() +{ + double TargetpH = product->sparge_ph; + double Source_pH = product->w1_ph; + double Source_alkalinity = product->w1_total_alkalinity; + + qDebug() << "calcSparge()"; + + const QSignalBlocker blocker1(ui->sp_sourceEdit); + + // Select watersource or fallback to the first source. + if (product->sparge_source == 1) { // Source 2 + if (product->w2_ph > 0.0 && product->w2_amount > 0) { + Source_pH = product->w2_ph; + Source_alkalinity = product->w2_total_alkalinity; + } else { + product->sparge_source = 0; // Source 1 + ui->sp_sourceEdit->setCurrentIndex(0); + } + } else if (product->sparge_source == 2) { // Mixed + if (product->w2_ph > 0.0 && product->w2_amount > 0) { + Source_pH = product->wg_ph; + Source_alkalinity = product->wg_total_alkalinity; + } else { + product->sparge_source = 0; // Source 1 + ui->sp_sourceEdit->setCurrentIndex(0); + } + } + + // Step 1: Compute the mole fractions of carbonic (f1o), bicarbonate (f2o) and carbonate(f3o) at the water pH + double r1 = pow(10, Source_pH - 6.35); + double r2 = pow(10, Source_pH - 10.33); + double d = 1 + r1 + r1 * r2; + double f1 = 1 / d; + double f3 = r1 * r2 / d; + + // Step 2. Compute the mole fractions at pH = 4.3 (the pH which defines alkalinity) + double r143 = pow(10, 4.3 - 6.35); + double r243 = pow(10, 4.3 - 10.33); + double d43 = 1 + r143 + r143 * r243; + double f143 = 1 / d43; + double f343 = r143 * r243 / d43; + + // Step 4. Solve + //double Ct = (Source_alkalinity - 1000 * (pow(10, -4.3) - pow(10, -Source_pH))) / ((f143 - f1) + (f3 - f343)); + double Ct = Source_alkalinity / 50 / ((f143 - f1) + (f3 - f343)); + + // Step 5. Compute mole fractions at desired pH + double r1g = pow(10, TargetpH - 6.35); + double r2g = pow(10, TargetpH - 10.33); + double dg = 1 + r1g + r1g * r2g; + double f1g = 1 / dg; + double f3g = r1g * r2g / dg; + + // Step 6. Use these to compute the milliequivalents acid required per liter (mEq/L) + double Acid = Ct * ((f1g - f1) + (f3 - f3g)) + pow(10, -TargetpH) - pow(10, -Source_pH); //mEq/l + Acid += 0.01; // Add acid that would be required for distilled water. + + //Step 8. Get the acid data. + int AT = product->sparge_acid_type; + if (AT < 0 || AT >= my_acids.size()) { + AT = 0; + product->sparge_acid_type = 0; + ui->sp_acidtypeEdit->setCurrentIndex(0); + product->sparge_acid_perc = my_acids[0].AcidPrc; + ui->sp_acidpercEdit->setValue(product->sparge_acid_perc); + } + double fract = Utils::CalcFrac(TargetpH, my_acids[AT].pK1, my_acids[AT].pK2, my_acids[AT].pK3); + + // Step 9. Now divide the mEq required by the "fraction". This is the required number of moles of acid. + Acid /= fract; + + // Step 10. Multiply by molecular weight of the acid + Acid *= my_acids[AT].MolWt; //mg + + // Step 11. Divide by Specific Gravity and Percentage to get the final ml. + Acid = Acid / my_acids[AT].AcidSG / (product->sparge_acid_perc / 100); //ml + Acid *= product->sparge_volume; //ml acid total + Acid = round(Acid * 100.0) / 100.0; + product->sparge_acid_amount = Acid / 1000; + ui->sp_acidvolEdit->setValue(Acid); +} + + +void EditProduct::sp_source_changed(int val) +{ + product->sparge_source = val; + calcSparge(); + is_changed(); +} + + +void EditProduct::sp_type_changed(int val) +{ + product->sparge_acid_type = val; + product->sparge_acid_perc = my_acids[val].AcidPrc; + ui->sp_acidpercEdit->setValue(product->sparge_acid_perc); + calcSparge(); + is_changed(); +} + + +void EditProduct::sp_ph_changed(double val) +{ + product->sparge_ph = val; + calcSparge(); + is_changed(); +} + + +double EditProduct::GetBUGU() +{ + double gu = (product->est_og - 1) * 1000; + if (gu > 0) + return product->est_ibu / gu; + return 0.5; +} + + +double EditProduct::GetOptSO4Clratio() +{ + if (ui->wt_so4Edit->value() > 0 && ui->wt_clEdit->value()) { + /* If target water is selected .. */ + return (ui->wt_so4Edit->value() / ui->wt_clEdit->value()); + } + double BUGU = GetBUGU(); + return (-1.2 * BUGU + 1.4); +} + + +void EditProduct::mw_calc_acid_clicked() +{ + product->calc_acid = ! product->calc_acid; + ui->mw_autoEdit->setChecked(product->calc_acid); + ui->mw_phEdit->setReadOnly(! product->calc_acid); + ui->mw_phEdit->setButtonSymbols(product->calc_acid ? QAbstractSpinBox::UpDownArrows : QAbstractSpinBox::NoButtons); + ui->mw_acidvolEdit->setReadOnly(product->calc_acid); + ui->mw_acidvolEdit->setButtonSymbols(product->calc_acid ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows); + is_changed(); + calcWater(); +} + + +void EditProduct::mw_ph_changed(double val) +{ + if (! product->calc_acid) + return; + + if (product->mash_ph != val) { + qDebug() << "mw_ph_changed" << val << product->mash_ph; + product->mash_ph = val; + is_changed(); + calcWater(); + } +} + + +void EditProduct::mw_acid_changed(double val) +{ + if (product->calc_acid) + return; + + qDebug() << "on_mw_acid_changed" << val; + QString w = my_acids[product->wa_acid_name].name_en + ' ' + my_acids[product->wa_acid_name].name_nl; + set_brewing_salt(w, val); +} + + +void EditProduct::mw_type_changed(int val) +{ + if (val == product->wa_acid_name) + return; + + qDebug() << "on_mw_type_changed" << val << "old" << product->wa_acid_name; + /* + * First remove current acid. + */ + QString w = my_acids[product->wa_acid_name].name_en + ' ' + my_acids[product->wa_acid_name].name_nl; + brewing_salt_sub(w, 0); + + product->wa_acid_name = val; + w = my_acids[product->wa_acid_name].name_en + ' ' + my_acids[product->wa_acid_name].name_nl; + + product->wa_acid_perc = my_acids.at(val).AcidPrc; + ui->mw_acidpercEdit->setValue(my_acids.at(val).AcidPrc); + brewing_salt_sub(w, ui->mw_acidvolEdit->value()); // For now, set old amount. + + is_changed(); + calcWater(); +} + + +void EditProduct::w2_volume_changed(double val) +{ + qDebug() << "w2_vol_changed" << val; + + if (product->w2_total_alkalinity && product->w2_sulfate) { + /* + * Seems a valid water, but don't go over the total. + */ + if (val < (product->w1_amount + product->w2_amount)) { + product->w1_amount -= val - product->w2_amount; + product->w2_amount = val; + ui->w1_volEdit->setValue(product->w1_amount); + } + } else { + /* + * Invalid water, block changes. + */ + product->w2_amount = 0; + } + ui->w2_volEdit->setValue(product->w2_amount); + + calcWater(); + is_changed(); +} + + +void EditProduct::w1_name_changed(int val) +{ + QSqlQuery query; + + qDebug() << "w1_name_changed" << val; + const QSignalBlocker blocker1(ui->w1_nameEdit); + if (val == 0) { + /* + * If no water is selected, take the default water. + */ + val = my_default_water; + ui->w1_nameEdit->setCurrentIndex(val); + } + + query.prepare("SELECT * FROM inventory_waters ORDER BY record"); + query.exec(); + query.first(); + for (int i = 0; i < (val - 1); i++) { + query.next(); + } + qDebug() << "set water" << query.value(1).toString(); + + product->w1_name = query.value(1).toString(); + product->w1_calcium = query.value(3).toDouble(); + product->w1_magnesium = query.value(8).toDouble(); + product->w1_total_alkalinity = query.value(11).toDouble(); + product->w1_sodium = query.value(7).toDouble(); + product->w1_chloride = query.value(6).toDouble(); + product->w1_sulfate = query.value(5).toDouble(); + product->w1_ph = query.value(9).toDouble(); + + ui->w1_caEdit->setValue(product->w1_calcium); + ui->w1_mgEdit->setValue(product->w1_magnesium); + ui->w1_hco3Edit->setValue(product->w1_total_alkalinity * 1.22); + ui->w1_caco3Edit->setValue(product->w1_total_alkalinity); + ui->w1_naEdit->setValue(product->w1_sodium); + ui->w1_clEdit->setValue(product->w1_chloride); + ui->w1_so4Edit->setValue(product->w1_sulfate); + ui->w1_phEdit->setValue(product->w1_ph); + + is_changed(); + calcWater(); +} + + +void EditProduct::w2_name_changed(int val) +{ + QSqlQuery query; + + qDebug() << "w2_name_changed" << val; + + if (val == 0) { // Clear water 2. + product->w2_name = ""; + product->w2_calcium = 0; + product->w2_magnesium = 0; + product->w2_total_alkalinity = 0; + product->w2_sodium = 0; + product->w2_chloride = 0; + product->w2_sulfate = 0; + product->w2_ph = 0; + product->w1_amount += product->w2_amount; + product->w2_amount = 0; + } else { + query.prepare("SELECT * FROM inventory_waters ORDER BY record"); + query.exec(); + query.first(); + for (int i = 0; i < (val - 1); i++) { + query.next(); + } + qDebug() << "set water" << query.value(1).toString(); + + product->w2_name = query.value(1).toString(); + product->w2_calcium = query.value(3).toDouble(); + product->w2_magnesium = query.value(8).toDouble(); + product->w2_total_alkalinity = query.value(11).toDouble(); + product->w2_sodium = query.value(7).toDouble(); + product->w2_chloride = query.value(6).toDouble(); + product->w2_sulfate = query.value(5).toDouble(); + product->w2_ph = query.value(9).toDouble(); + } + ui->w1_volEdit->setValue(product->w1_amount); + ui->w2_volEdit->setValue(product->w2_amount); + ui->w2_caEdit->setValue(product->w2_calcium); + ui->w2_mgEdit->setValue(product->w2_magnesium); + ui->w2_hco3Edit->setValue(product->w2_total_alkalinity * 1.22); + ui->w2_caco3Edit->setValue(product->w2_total_alkalinity); + ui->w2_naEdit->setValue(product->w2_sodium); + ui->w2_clEdit->setValue(product->w2_chloride); + ui->w2_so4Edit->setValue(product->w2_sulfate); + ui->w2_phEdit->setValue(product->w2_ph); + + is_changed(); + calcWater(); +} + + +void EditProduct::wt_target_changed(int val) +{ + QSqlQuery query; + + if (val == 0) { + /* Clear values */ + ui->wt_caEdit->setValue(0); + ui->wt_mgEdit->setValue(0); + ui->wt_hco3Edit->setValue(0); + ui->wt_caco3Edit->setValue(0); + ui->wt_naEdit->setValue(0); + ui->wt_clEdit->setValue(0); + ui->wt_so4Edit->setValue(0); + } else { + query.prepare("SELECT * FROM profile_water ORDER BY name"); + query.exec(); + query.first(); + for (int i = 0; i < (val - 1); i++) { + query.next(); + } + ui->wt_caEdit->setValue(query.value(2).toDouble()); + ui->wt_mgEdit->setValue(query.value(7).toDouble()); + ui->wt_hco3Edit->setValue(query.value(3).toDouble()); + ui->wt_caco3Edit->setValue(query.value(10).toDouble()); + ui->wt_naEdit->setValue(query.value(6).toDouble()); + ui->wt_clEdit->setValue(query.value(5).toDouble()); + ui->wt_so4Edit->setValue(query.value(4).toDouble()); + } + calcWater(); +} + + +void EditProduct::adjustWaters(double factor) +{ + int i; + double amount; + + if (product->mashs.size() == 0) + return; + + double mash_infuse = 0; + for (i = 0; i < product->mashs.size(); i++) { + if (product->mashs.at(i).step_type == 0) { // Infusion + amount = round(product->mashs.at(i).step_infuse_amount * factor * 1000000.0) / 1000000.0; + product->mashs[i].step_infuse_amount = amount; + mash_infuse += amount; + product->mashs[i].step_volume = mash_infuse; + } + } + + const QSignalBlocker blocker1(ui->w1_volEdit); + const QSignalBlocker blocker2(ui->w2_volEdit); + + if (product->w2_amount == 0) { + product->w1_amount = mash_infuse; + ui->w1_volEdit->setValue(mash_infuse); + } else { + double w1 = (product->w1_amount / (product->w1_amount + product->w2_amount)) * mash_infuse; + double w2 = (product->w2_amount / (product->w1_amount + product->w2_amount)) * mash_infuse; + product->w1_amount = w1; + product->w2_amount = w2; + ui->w1_volEdit->setValue(product->w1_amount); + ui->w2_volEdit->setValue(product->w2_amount); + } + product->wg_amount = mash_infuse; + ui->wg_volEdit->setValue(mash_infuse); +} + + +void EditProduct::wb_cacl2_changed(double val) { set_brewing_salt("CaCl2", val); } +void EditProduct::wb_caso4_changed(double val) { set_brewing_salt("CaSO4", val); } +void EditProduct::wb_mgso4_changed(double val) { set_brewing_salt("MgSO4", val); } +void EditProduct::wb_nacl_changed(double val) { set_brewing_salt("NaCl", val); } +void EditProduct::wb_mgcl2_changed(double val) { set_brewing_salt("MgCl2", val); } +void EditProduct::wb_nahco3_changed(double val) { set_brewing_salt("NaHCO3", val); } +void EditProduct::wb_caco3_changed(double val) { set_brewing_salt("CaCO3", val); } + + diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 src/EditProductTab9.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/EditProductTab9.cpp Thu Apr 28 22:49:13 2022 +0200 @@ -0,0 +1,20 @@ +/** + * EditProduct.cpp is part of bmsapp. + * + * Tab 9, brewday. + * + * 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 . + */ + + diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 src/PrinterDialog.cpp --- a/src/PrinterDialog.cpp Thu Apr 28 16:59:58 2022 +0200 +++ b/src/PrinterDialog.cpp Thu Apr 28 22:49:13 2022 +0200 @@ -17,6 +17,7 @@ #include "PrinterDialog.h" #include "Utils.h" #include "EditRecipe.h" +#include "EditProduct.h" #include "config.h" #include "global.h" diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 src/ProdInprod.cpp --- a/src/ProdInprod.cpp Thu Apr 28 16:59:58 2022 +0200 +++ b/src/ProdInprod.cpp Thu Apr 28 22:49:13 2022 +0200 @@ -16,6 +16,7 @@ */ #include "ProdInprod.h" #include "MainWindow.h" +#include "EditProduct.h" #include "config.h" #include "global.h" @@ -139,11 +140,11 @@ void ProdInprod::edit(int recno) { -// EditProfileMash dialog(recno, this); + EditProduct dialog(recno, this); /* Signal from editor if a refresh is needed */ -// connect(&dialog, SIGNAL(entry_changed()), this, SLOT(refreshTable())); -// dialog.setModal(true); -// dialog.exec(); + connect(&dialog, SIGNAL(entry_changed()), this, SLOT(refreshTable())); + dialog.setModal(true); + dialog.exec(); } diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 src/global.cpp --- a/src/global.cpp Thu Apr 28 16:59:58 2022 +0200 +++ b/src/global.cpp Thu Apr 28 22:49:13 2022 +0200 @@ -20,7 +20,7 @@ QString my_yeastlab = ""; Recipe *recipe; - +Product *product; const QStringList prod_stages({ QObject::tr("Plan"), diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 src/global.h --- a/src/global.h Thu Apr 28 16:59:58 2022 +0200 +++ b/src/global.h Thu Apr 28 22:49:13 2022 +0200 @@ -377,7 +377,7 @@ double package_volume; double package_infuse_amount; double package_infuse_abv; - double package_infuse_notes; + QString package_infuse_notes; double package_abv; double package_ph; double bottle_amount; @@ -535,6 +535,7 @@ extern Recipe *recipe; +extern Product *product; extern QString my_brewery_name; extern QByteArray my_logoByteArray; diff -r ceb8aa4ebd25 -r f1ed3a2a94e9 ui/EditProduct.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/EditProduct.ui Thu Apr 28 22:49:13 2022 +0200 @@ -0,0 +1,4995 @@ + + + EditProduct + + + + 0 + 0 + 1152 + 560 + + + + Dialog + + + + + + + + 90 + 510 + 80 + 23 + + + + + 0 + 0 + + + + Quit + + + + :icons/silk/door_out.png:icons/silk/door_out.png + + + + + true + + + + 940 + 510 + 80 + 23 + + + + Save + + + + :icons/silk/disk.png:icons/silk/disk.png + + + + + true + + + + 520 + 510 + 80 + 23 + + + + Delete + + + + :icons/silk/delete.png:icons/silk/delete.png + + + + + + 0 + 0 + 1128 + 501 + + + + QTabWidget::North + + + QTabWidget::Rounded + + + 0 + + + Qt::ElideNone + + + true + + + false + + + false + + + + + :/icons/bms/beerstyles.png:/icons/bms/beerstyles.png + + + Generic + + + + + 0 + 10 + 131 + 20 + + + + Brew name: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 0 + 100 + 131 + 20 + + + + Brew notes: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 140 + 10 + 591 + 23 + + + + + + + 140 + 100 + 881 + 51 + + + + + + + 740 + 10 + 131 + 20 + + + + Read only: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 880 + 10 + 61 + 21 + + + + Yes + + + + + + 0 + 160 + 131 + 20 + + + + Brew type: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 140 + 160 + 181 + 23 + + + + + + + 370 + 160 + 131 + 20 + + + + Efficiency: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 370 + 190 + 131 + 20 + + + + Boil time: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 0 + 190 + 131 + 20 + + + + Batch size: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 740 + 190 + 131 + 20 + + + + Boil size: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 510 + 160 + 101 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + % + + + 1 + + + 0.500000000000000 + + + + + + 510 + 190 + 101 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + min + + + 1536 + + + 5 + + + + + + 140 + 190 + 101 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + L + + + 1 + + + 100000.000000000000000 + + + 0.500000000000000 + + + + + + 880 + 190 + 87 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + true + + + L + + + 1 + + + 100000.000000000000000 + + + 0.500000000000000 + + + + + + 10 + 340 + 1101 + 121 + + + + Overview + + + + + 130 + 20 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + QAbstractSpinBox::UpDownArrows + + + true + + + 3 + + + 0.980000000000000 + + + 2.000000000000000 + + + 0.001000000000000 + + + + + + 870 + 20 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + % + + + 1 + + + 0.000000000000000 + + + 80.000000000000000 + + + 0.100000000000000 + + + + + + 500 + 20 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + QAbstractSpinBox::NoButtons + + + true + + + 3 + + + 0.980000000000000 + + + 2.000000000000000 + + + 0.001000000000000 + + + + + + 730 + 20 + 131 + 20 + + + + Alcohol Volume: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 360 + 20 + 131 + 20 + + + + Estimated FG: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 580 + 20 + 148 + 24 + + + + + + + 210 + 20 + 148 + 24 + + + + + + + 5 + 20 + 116 + 20 + + + + Estimated OG: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 950 + 20 + 148 + 24 + + + + + + + 500 + 50 + 141 + 23 + + + + + + + 210 + 50 + 148 + 24 + + + + + + + 870 + 50 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + % + + + 1 + + + 0.000000000000000 + + + 80.000000000000000 + + + 0.100000000000000 + + + + + + 130 + 50 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 0 + + + 0.000000000000000 + + + 400.000000000000000 + + + 1.000000000000000 + + + + + + 360 + 50 + 131 + 20 + + + + Color method: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 730 + 50 + 131 + 20 + + + + Carbonation: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 950 + 50 + 148 + 24 + + + + + + + 10 + 50 + 111 + 20 + + + + Color EBC: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 210 + 80 + 148 + 24 + + + + + + + 500 + 80 + 141 + 23 + + + + + + + 130 + 80 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 0 + + + 0.000000000000000 + + + 400.000000000000000 + + + 1.000000000000000 + + + + + + 360 + 80 + 131 + 20 + + + + IBU method: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 730 + 80 + 131 + 20 + + + + Energy kcal/l: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 80 + 111 + 20 + + + + Bitterness IBU: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 870 + 80 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 0 + + + 0.000000000000000 + + + 8000.000000000000000 + + + 0.100000000000000 + + + + + + + 10 + 210 + 1101 + 121 + + + + Beerstyle + + + + + 500 + 20 + 226 + 23 + + + + + + + 870 + 50 + 101 + 23 + + + + true + + + + + + 500 + 50 + 226 + 23 + + + + true + + + + + + 130 + 50 + 226 + 23 + + + + true + + + + + + 10 + 50 + 111 + 20 + + + + Style guide: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 730 + 50 + 131 + 20 + + + + Style group: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 360 + 50 + 131 + 20 + + + + Style name: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 360 + 20 + 131 + 20 + + + + Select style: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 80 + 111 + 20 + + + + Style type: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 130 + 80 + 226 + 23 + + + + true + + + + + + 360 + 80 + 131 + 20 + + + + Category: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 870 + 80 + 71 + 23 + + + + true + + + + + + 730 + 80 + 131 + 20 + + + + Category number: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 500 + 80 + 226 + 23 + + + + true + + + + + + + 0 + 40 + 131 + 20 + + + + Brew code: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 0 + 70 + 131 + 20 + + + + Split product: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 140 + 40 + 181 + 23 + + + + + + + 370 + 70 + 131 + 20 + + + + Split part: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 140 + 70 + 101 + 23 + + + + + + + 510 + 70 + 101 + 23 + + + + + + + 370 + 40 + 131 + 20 + + + + Plan start: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 510 + 40 + 101 + 23 + + + + + + + 740 + 40 + 131 + 20 + + + + Plan start: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 880 + 40 + 101 + 23 + + + + + + + 740 + 160 + 131 + 20 + + + + Ingredients present: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + :/icons/bms/mash.png:/icons/bms/mash.png + + + Equipment + + + + + + :/icons/bms/graan.png:/icons/bms/graan.png + + + Fermentables + + + + + 0 + 10 + 131 + 20 + + + + Color EBC: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 140 + 10 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 0 + + + 0.000000000000000 + + + 400.000000000000000 + + + 1.000000000000000 + + + + + + 0 + 40 + 131 + 20 + + + + Estimated OG: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 140 + 40 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + QAbstractSpinBox::UpDownArrows + + + true + + + 3 + + + 0.980000000000000 + + + 2.000000000000000 + + + 0.001000000000000 + + + + + + 440 + 10 + 131 + 20 + + + + Mash tun %: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 440 + 40 + 131 + 20 + + + + Sugars %: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 440 + 70 + 131 + 20 + + + + Cara/crystal %: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 440 + 100 + 131 + 20 + + + + Lintner: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 130 + 1101 + 331 + + + + + + + 580 + 100 + 381 + 20 + + + + 200 + + + 24 + + + Qt::Horizontal + + + %v lintner + + + + + + 580 + 10 + 381 + 20 + + + + 120 + + + 24 + + + Qt::Horizontal + + + %v% + + + + + + 580 + 40 + 381 + 20 + + + + 50 + + + 24 + + + Qt::Horizontal + + + %v% + + + + + + 580 + 70 + 381 + 20 + + + + 50 + + + 24 + + + Qt::Horizontal + + + %v% + + + + + + 140 + 100 + 80 + 23 + + + + Add + + + + :/icons/bms/graan.png:/icons/bms/graan.png + + + + + + + :/icons/bms/hop.png:/icons/bms/hop.png + + + Hops + + + + + 0 + 10 + 131 + 20 + + + + Bitterness IBU: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 140 + 10 + 81 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 0 + + + 0.000000000000000 + + + 400.000000000000000 + + + 1.000000000000000 + + + + + + 220 + 10 + 131 + 20 + + + + Hop taste: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 670 + 10 + 131 + 20 + + + + Hop aroma: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 360 + 10 + 301 + 20 + + + + 100 + + + 24 + + + Qt::Horizontal + + + %v% + + + + + + 810 + 10 + 301 + 20 + + + + 100 + + + 24 + + + Qt::Horizontal + + + %v% + + + + + + 10 + 100 + 1101 + 361 + + + + + + + 140 + 70 + 80 + 23 + + + + Add + + + + :/icons/bms/hop.png:/icons/bms/hop.png + + + + + + + :/icons/bms/peper.png:/icons/bms/peper.png + + + Miscs + + + + + 140 + 10 + 849 + 451 + + + + + + + 30 + 40 + 80 + 23 + + + + Add + + + + :/icons/bms/peper.png:/icons/bms/peper.png + + + + + + + :/icons/bms/erlenmeyer.png:/icons/bms/erlenmeyer.png + + + Yeasts + + + + + 50 + 10 + 131 + 20 + + + + Estimated OG: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 190 + 10 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + QAbstractSpinBox::NoButtons + + + true + + + 3 + + + 0.980000000000000 + + + 2.000000000000000 + + + 0.001000000000000 + + + + + + 50 + 40 + 131 + 20 + + + + Estimated FG: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 190 + 40 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + QAbstractSpinBox::NoButtons + + + true + + + 3 + + + 0.980000000000000 + + + 2.000000000000000 + + + 0.001000000000000 + + + + + + 50 + 70 + 131 + 20 + + + + Alcohol Volume: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 190 + 70 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + % + + + 1 + + + 0.000000000000000 + + + 80.000000000000000 + + + 0.100000000000000 + + + + + + 30 + 100 + 151 + 20 + + + + Appearant Attenuation: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 190 + 100 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + % + + + 1 + + + 0.000000000000000 + + + 120.000000000000000 + + + 0.100000000000000 + + + + + + 10 + 250 + 1101 + 211 + + + + + + + 290 + 10 + 821 + 231 + + + + 2 + + + + + + + 200 + 20 + 341 + 20 + + + + + 11 + 75 + true + + + + Liquid yeast advice + + + + + + 200 + 60 + 91 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 3 + + + 1000.000000000000000 + + + + + + 10 + 60 + 181 + 20 + + + + Pitchrate million cells/ml/°P: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 90 + 181 + 20 + + + + Initial billion cells: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 120 + 181 + 20 + + + + Target billion cells: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 200 + 90 + 91 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 0 + + + 100000.000000000000000 + + + + + + 200 + 120 + 91 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 0 + + + 100000.000000000000000 + + + + + + 10 + 150 + 181 + 20 + + + + Starter volume L: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 200 + 150 + 91 + 24 + + + + A very rough starter volume estimate. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 3 + + + 100000.000000000000000 + + + + + + + + 170 + 10 + 261 + 20 + + + + + 11 + 75 + true + + + + Dry yeast advice + + + + + + 40 + 60 + 121 + 20 + + + + Low grams/hl: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 40 + 90 + 121 + 20 + + + + High grams/hl: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 170 + 60 + 61 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 500 + + + + + + 170 + 90 + 61 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1000 + + + + + + 240 + 60 + 31 + 20 + + + + at + + + Qt::AlignCenter + + + + + + 240 + 90 + 31 + 20 + + + + at + + + Qt::AlignCenter + + + + + + 280 + 60 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 3 + + + 2.000000000000000 + + + 0.000000000000000 + + + + + + 280 + 90 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 3 + + + 2.000000000000000 + + + + + + 40 + 120 + 121 + 20 + + + + Pitch grams: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 170 + 120 + 61 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 1 + + + 1000.000000000000000 + + + + + + 40 + 150 + 121 + 20 + + + + Pitch grams/hl: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 170 + 150 + 61 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 2 + + + 1000.000000000000000 + + + + + + + + 180 + 210 + 80 + 23 + + + + Add + + + + :/icons/bms/erlenmeyer.png:/icons/bms/erlenmeyer.png + + + + + + + :/icons/bms/mash.png:/icons/bms/mash.png + + + Mash + + + + + 10 + 110 + 1101 + 351 + + + + + + + 140 + 40 + 80 + 23 + + + + Add + + + + :/icons/bms/mash.png:/icons/bms/mash.png + + + + + + 0 + 10 + 131 + 20 + + + + Mash name: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 530 + 10 + 171 + 20 + + + + Mash schedule: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 710 + 10 + 321 + 23 + + + + + + + 140 + 10 + 321 + 23 + + + + + + + 530 + 40 + 171 + 20 + + + + Mash time: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 710 + 40 + 71 + 23 + + + + true + + + + + + + :/icons/bms/water.png:/icons/bms/water.png + + + Water + + + + + 170 + 0 + 941 + 221 + + + + false + + + Water overview + + + false + + + false + + + false + + + + + 300 + 50 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 860 + 50 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 2 + + + 1000.000000000000000 + + + + + + 780 + 50 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 700 + 50 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 620 + 50 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 540 + 50 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 460 + 50 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 380 + 50 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 300 + 20 + 71 + 21 + + + + Ca + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 380 + 20 + 71 + 21 + + + + Mg + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 460 + 20 + 71 + 21 + + + + HCO3 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 540 + 20 + 71 + 21 + + + + CaCO3 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 620 + 20 + 71 + 21 + + + + Na + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 700 + 20 + 71 + 21 + + + + Cl + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 780 + 20 + 71 + 21 + + + + SO4 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 860 + 20 + 71 + 21 + + + + pH + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 200 + 50 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 2 + + + 100000.000000000000000 + + + + + + 200 + 20 + 71 + 21 + + + + Volume + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 460 + 80 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 860 + 80 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 2 + + + 1000.000000000000000 + + + + + + 540 + 80 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 620 + 80 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 780 + 80 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 300 + 80 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 700 + 80 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 200 + 80 + 85 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + QAbstractSpinBox::UpDownArrows + + + true + + + 2 + + + 100000.000000000000000 + + + 0.500000000000000 + + + + + + 380 + 80 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 460 + 110 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 860 + 110 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 2 + + + 1000.000000000000000 + + + + + + 540 + 110 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 620 + 110 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 780 + 110 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 300 + 110 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 700 + 110 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 200 + 110 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 2 + + + 100000.000000000000000 + + + + + + 380 + 110 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 460 + 180 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 860 + 140 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 2 + + + 1000.000000000000000 + + + + + + 540 + 180 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 620 + 180 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 780 + 180 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 300 + 180 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 700 + 180 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 380 + 180 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 380 + 140 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 460 + 140 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 780 + 140 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 300 + 140 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 700 + 140 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 540 + 140 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 620 + 140 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + 1 + + + 1000.000000000000000 + + + + + + 10 + 20 + 171 + 21 + + + + Water profile + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + 10 + 50 + 181 + 23 + + + + The main brewing water + + + + + + Choose water + + + + + + 10 + 80 + 181 + 23 + + + + Optional dilution water + + + Choose dilution + + + + + + 10 + 180 + 181 + 23 + + + + If needed, choose a target water profile. + + + + + + 10 + 110 + 171 + 21 + + + + Mixed water + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + 10 + 140 + 171 + 21 + + + + Treated water + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + 10 + 230 + 271 + 241 + + + + Water agents + + + + + 10 + 20 + 151 + 20 + + + + Calcium Chloride + + + CaCl2: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 50 + 151 + 20 + + + + Gypsym + + + CaSO4: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 80 + 151 + 20 + + + + Epsom + + + MgSO4: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 110 + 151 + 20 + + + + Table salt + + + NaCl: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 140 + 151 + 20 + + + + Magnesium Chloride + + + MgCl2: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 170 + 151 + 20 + + + + Baking soda + + + NaHCO3: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 200 + 151 + 20 + + + + Chalk undissolved + + + CaCO3: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 170 + 20 + 91 + 24 + + + + Calcium Chloride + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + gr + + + 2 + + + 1000.000000000000000 + + + 0.100000000000000 + + + + + + 170 + 50 + 91 + 24 + + + + Gypsym + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + gr + + + 2 + + + 1000.000000000000000 + + + 0.100000000000000 + + + + + + 170 + 80 + 91 + 24 + + + + Epsom + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + gr + + + 2 + + + 1000.000000000000000 + + + 0.100000000000000 + + + + + + 170 + 110 + 91 + 24 + + + + Table salt + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + gr + + + 2 + + + 1000.000000000000000 + + + 0.100000000000000 + + + + + + 170 + 140 + 91 + 24 + + + + Magnesium Chloride + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + gr + + + 2 + + + 1000.000000000000000 + + + 0.100000000000000 + + + + + + 170 + 170 + 91 + 24 + + + + Baking soda + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + gr + + + 2 + + + 1000.000000000000000 + + + 0.100000000000000 + + + + + + 170 + 200 + 91 + 24 + + + + Chalk undissolved + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + gr + + + 2 + + + 1000.000000000000000 + + + 0.100000000000000 + + + + + + + 350 + 230 + 421 + 91 + + + + Mash water + + + + + 10 + 20 + 111 + 20 + + + + Desired pH: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 235 + 20 + 141 + 20 + + + + Auto calculate: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 50 + 111 + 20 + + + + Acid to use: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 130 + 50 + 101 + 23 + + + + + + + 130 + 20 + 101 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + 2 + + + 14.000000000000000 + + + 0.100000000000000 + + + + + + 240 + 50 + 61 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + % + + + 0 + + + 100.000000000000000 + + + 1.000000000000000 + + + + + + 310 + 50 + 89 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + ml + + + 2 + + + 10000.000000000000000 + + + 0.100000000000000 + + + + + + 385 + 20 + 16 + 21 + + + + + + + + 840 + 230 + 271 + 241 + + + + Sparge water + + + + + 10 + 20 + 151 + 20 + + + + Sparge water supply: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 50 + 151 + 20 + + + + Sparge temperature: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 80 + 151 + 20 + + + + Water source: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 110 + 151 + 20 + + + + Desired pH: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 140 + 151 + 20 + + + + Acid type: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 170 + 151 + 20 + + + + Acid strength: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 200 + 151 + 20 + + + + Acid needed: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 170 + 20 + 91 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + 1 + + + 100000.000000000000000 + + + 0.500000000000000 + + + + + + 170 + 50 + 91 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + 1 + + + 100.000000000000000 + + + 0.500000000000000 + + + + + + 170 + 110 + 91 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + 2 + + + 14.000000000000000 + + + 0.100000000000000 + + + + + + 170 + 170 + 91 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + % + + + 0 + + + 100.000000000000000 + + + 1.000000000000000 + + + + + + 170 + 200 + 91 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + ml + + + 2 + + + 10000.000000000000000 + + + 0.100000000000000 + + + + + + 170 + 80 + 91 + 23 + + + + + + + 170 + 140 + 91 + 23 + + + + + + + + 290 + 340 + 181 + 20 + + + + Bitterness index: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 480 + 340 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 2 + + + 1000.000000000000000 + + + 0.100000000000000 + + + + + + 560 + 340 + 211 + 20 + + + + + true + + + + N/A + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + 290 + 370 + 181 + 20 + + + + Preffered SO4:Cl ratio: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 480 + 370 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 1 + + + 1000.000000000000000 + + + 0.100000000000000 + + + + + + 560 + 370 + 211 + 20 + + + + + true + + + + N/A + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + 290 + 400 + 181 + 20 + + + + Current SO4:Cl ratio: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 480 + 400 + 71 + 24 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 1 + + + 1000.000000000000000 + + + 0.100000000000000 + + + + + + 559 + 400 + 211 + 20 + + + + + + + Brewday + + + + + Fermenting + + + + + Package + + + + + Tasting + + + + + + + 300 + 510 + 80 + 23 + + + + Export + + + + :/icons/silk/disk_multiple.png:/icons/silk/disk_multiple.png + + + + + + 730 + 510 + 80 + 23 + + + + Print + + + + :/icons/silk/printer.png:/icons/silk/printer.png + + + + + + + + + RangedSlider + QWidget +
RangedSlider.h
+
+
+ + quitButton + deleteButton + saveButton + + + + + +