diff --git a/images/iconMash.svg b/images/iconMash.svg new file mode 100644 index 00000000..dfa126ce --- /dev/null +++ b/images/iconMash.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/resources.qrc b/resources.qrc index c60311a5..5bd80a6c 100644 --- a/resources.qrc +++ b/resources.qrc @@ -82,6 +82,7 @@ images/grain2glass.svg images/help-contents.png images/hydrometer.svg + images/iconMash.svg images/kbruch.png images/mashpaddle.svg images/merge.png diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index e97d60d8..e7d8ec32 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1671,7 +1671,7 @@ void MainWindow::treeActivated(const QModelIndex &index) { { Water * w = active->getItem(index); if (w) { - this->pimpl->m_waterEditor->setWater(ObjectStoreWrapper::getSharedFromRaw(w)); + this->pimpl->m_waterEditor->setEditItem(ObjectStoreWrapper::getSharedFromRaw(w)); this->pimpl->m_waterEditor->show(); } } @@ -2506,15 +2506,11 @@ void MainWindow::editYeastOfSelectedYeastAddition() { return; } -void MainWindow::newRecipe() -{ - QString name = QInputDialog::getText(this, tr("Recipe name"), - tr("Recipe name:")); - QVariant defEquipKey = PersistentSettings::value(PersistentSettings::Names::defaultEquipmentKey, -1); - QObject* selection = sender(); - - if( name.isEmpty() ) +void MainWindow::newRecipe() { + QString const name = QInputDialog::getText(this, tr("Recipe name"), tr("Recipe name:")); + if (name.isEmpty()) { return; + } std::shared_ptr newRec = std::make_shared(name); @@ -2526,9 +2522,22 @@ void MainWindow::newRecipe() return; } ObjectStoreWrapper::insert(newRec); - std::shared_ptr newBoil = std::make_shared(QString("Boil for").arg(name)); + + // + // .:TODO:. For the moment, we still assume that every Recipe has a Boil and a Fermentation. Also, for the moment, + // we also create a new boil and fermentation for every Recipe. In time, I'd like to extend the UI so that, when you + // input the name for your new Recipe, you also select Mash, Boil, Fermentation (all either from "List of existing" + // or "Make new"). + // + std::shared_ptr newBoil = std::make_shared(tr("Automatically-created Boil for %1").arg(name)); + // NB: Recipe::setBoil will ensure Boil is stored in the database newRec->setBoil(newBoil); - ObjectStoreWrapper::insert(newBoil); + // Since we're auto-creating a Boil, it might as well start out with the "standard" profile + newBoil->ensureStandardProfile(); + + std::shared_ptr newFermentation = std::make_shared(tr("Automatically-created Fermentation for %1").arg(name)); + // NB: Recipe::setFermentation will ensure Fermentation is stored in the database + newRec->setFermentation(newFermentation); // Set the following stuff so everything appears nice // and the calculations don't divide by zero... things like that. @@ -2536,7 +2545,8 @@ void MainWindow::newRecipe() newBoil->setPreBoilSize_l(23.47); // 6.2 gallons newRec->setEfficiency_pct(70.0); - // we need a valid key, so insert the recipe before we add equipment + // We need a valid key, so insert the recipe before we add equipment + QVariant const defEquipKey = PersistentSettings::value(PersistentSettings::Names::defaultEquipmentKey, -1); if (defEquipKey != -1) { auto equipment = ObjectStoreWrapper::getById(defEquipKey.toInt()); // I really want to do this before we've written the object to the @@ -2549,8 +2559,9 @@ void MainWindow::newRecipe() } } - // a new recipe will be put in a folder if you right click on a recipe or + // A new recipe will be put in a folder if you right click on a recipe or // folder. Otherwise, it goes into the main window? + QObject* selection = this->sender(); if (selection) { TreeView* sent = qobject_cast(tabWidget_Trees->currentWidget()->focusWidget()); if (sent) { diff --git a/src/WaterDialog.cpp b/src/WaterDialog.cpp index 270d2ca0..4cfabdee 100644 --- a/src/WaterDialog.cpp +++ b/src/WaterDialog.cpp @@ -294,12 +294,12 @@ void WaterDialog::setRecipe(Recipe *rec) { spinBox_spargeRO->setValue( QVariant(m_spargeRO * 100).toInt()); baseProfileButton->setWater(this->m_base); - m_base_editor->setWater(this->m_base); + m_base_editor->setEditItem(this->m_base); // all of the magic to set the sliders happens in newTotals(). So don't do it twice } if (this->m_target && this->m_target != this->m_base) { targetProfileButton->setWater(this->m_target); - m_target_editor->setWater(this->m_target); + m_target_editor->setEditItem(this->m_target); this->setDigits(); } @@ -327,7 +327,7 @@ void WaterDialog::update_baseProfile(int selected) { qDebug() << Q_FUNC_INFO << "Made base child" << *this->m_base << "from parent" << parent; baseProfileButton->setWater(this->m_base); - m_base_editor->setWater(this->m_base); + m_base_editor->setEditItem(this->m_base); newTotals(); } return; @@ -351,7 +351,7 @@ void WaterDialog::update_targetProfile(int selected) { qDebug() << Q_FUNC_INFO << "Made target child" << *this->m_target << "from parent" << parent; targetProfileButton->setWater(this->m_target); - m_target_editor->setWater(this->m_target); + m_target_editor->setEditItem(this->m_target); this->setDigits(); } diff --git a/src/database/DatabaseSchemaHelper.cpp b/src/database/DatabaseSchemaHelper.cpp index a1390926..e13edcbf 100644 --- a/src/database/DatabaseSchemaHelper.cpp +++ b/src/database/DatabaseSchemaHelper.cpp @@ -844,7 +844,7 @@ namespace { {QString("ALTER TABLE water ADD COLUMN iron_ppm %1").arg(db.getDbNativeTypeName())}, {QString("ALTER TABLE water ADD COLUMN nitrate_ppm %1").arg(db.getDbNativeTypeName())}, {QString("ALTER TABLE water ADD COLUMN nitrite_ppm %1").arg(db.getDbNativeTypeName())}, - {QString("ALTER TABLE water ADD COLUMN flouride_ppm %1").arg(db.getDbNativeTypeName())}, + {QString("ALTER TABLE water ADD COLUMN flouride_ppm %1").arg(db.getDbNativeTypeName())}, // Should have been fluoride_ppm! // // Equipment: Extended and additional fields for BeerJSON. This includes changing a lot of column names as // BeerJSON essentially has a record per vessel ("HLT", "Mash Tun", etc) @@ -2188,6 +2188,7 @@ namespace { case 12: ret &= migrate_to_13(database, sqlQuery); break; + // TODO: On next DB update, correct water.flouride_ppm to water.fluoride_ppm default: qCritical() << QString("Unknown version %1").arg(oldVersion); return false; @@ -2302,7 +2303,7 @@ bool DatabaseSchemaHelper::migrate(Database & database, int oldVersion, int newV // By the magic of RAII, this will abort if we exit this function (including by throwing an exception) without // having called dbTransaction.commit(). (It will also turn foreign keys back on either way -- whether the // transaction is committed or rolled back.) - DbTransaction dbTransaction{database, connection, DbTransaction::DISABLE_FOREIGN_KEYS}; + DbTransaction dbTransaction{database, connection, "Migrate", DbTransaction::DISABLE_FOREIGN_KEYS}; for ( ; oldVersion < newVersion && ret; ++oldVersion ) { ret &= migrateNext(database, oldVersion, connection); diff --git a/src/database/DbTransaction.cpp b/src/database/DbTransaction.cpp index 11eb3391..9e50c25b 100644 --- a/src/database/DbTransaction.cpp +++ b/src/database/DbTransaction.cpp @@ -1,5 +1,5 @@ /*====================================================================================================================== - * database/DbTransaction.cpp is part of Brewken, and is copyright the following authors 2021-2022: + * database/DbTransaction.cpp is part of Brewken, and is copyright the following authors 2021-2024: * • Matt Young * * Brewken is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -19,11 +19,15 @@ #include #include "database/Database.h" +#include "Logging.h" - -DbTransaction::DbTransaction(Database & database, QSqlDatabase & connection, DbTransaction::SpecialBehaviours specialBehaviours) : +DbTransaction::DbTransaction(Database & database, + QSqlDatabase & connection, + QString const nameForLogging, + DbTransaction::SpecialBehaviours specialBehaviours) : database{database}, connection{connection}, + nameForLogging{nameForLogging}, committed{false}, specialBehaviours{specialBehaviours} { // Note that, on SQLite at least, turning foreign keys on and off has to happen outside a transaction, so we have to @@ -33,9 +37,12 @@ DbTransaction::DbTransaction(Database & database, QSqlDatabase & connection, DbT } bool succeeded = this->connection.transaction(); - qDebug() << Q_FUNC_INFO << "Database transaction begin: " << (succeeded ? "succeeded" : "failed"); + qDebug() << + Q_FUNC_INFO << "Database transaction" << this->nameForLogging << "begin: " << (succeeded ? "succeeded" : "failed"); if (!succeeded) { - qCritical() << Q_FUNC_INFO << "Unable to start database transaction:" << connection.lastError().text(); + qCritical() << + Q_FUNC_INFO << "Unable to start database transaction" << this->nameForLogging << ":" << connection.lastError().text(); + qCritical().noquote() << Q_FUNC_INFO << Logging::getStackTrace(); } return; } @@ -44,9 +51,11 @@ DbTransaction::~DbTransaction() { qDebug() << Q_FUNC_INFO; if (!committed) { bool succeeded = this->connection.rollback(); - qDebug() << Q_FUNC_INFO << "Database transaction rollback: " << (succeeded ? "succeeded" : "failed"); + qDebug() << + Q_FUNC_INFO << "Database transaction" << this->nameForLogging << "rollback: " << (succeeded ? "succeeded" : "failed"); if (!succeeded) { - qCritical() << Q_FUNC_INFO << "Unable to rollback database transaction:" << connection.lastError().text(); + qCritical() << + Q_FUNC_INFO << "Unable to rollback database transaction" << this->nameForLogging << ":" << connection.lastError().text(); } } @@ -59,9 +68,11 @@ DbTransaction::~DbTransaction() { bool DbTransaction::commit() { this->committed = connection.commit(); - qDebug() << Q_FUNC_INFO << "Database transaction commit: " << (this->committed ? "succeeded" : "failed"); + qDebug() << + Q_FUNC_INFO << "Database transaction" << this->nameForLogging << "commit: " << (this->committed ? "succeeded" : "failed"); if (!this->committed) { - qCritical() << Q_FUNC_INFO << "Unable to commit database transaction:" << connection.lastError().text(); + qCritical() << + Q_FUNC_INFO << "Unable to commit database transaction" << this->nameForLogging << ":" << connection.lastError().text(); } return this->committed; } diff --git a/src/database/DbTransaction.h b/src/database/DbTransaction.h index 4d00546c..537f4ad1 100644 --- a/src/database/DbTransaction.h +++ b/src/database/DbTransaction.h @@ -1,5 +1,5 @@ /*====================================================================================================================== - * database/DbTransaction.h is part of Brewken, and is copyright the following authors 2021: + * database/DbTransaction.h is part of Brewken, and is copyright the following authors 2021-2024: * • Matt Young * * Brewken is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -34,7 +34,10 @@ class DbTransaction { /** * \brief Constructing a \c DbTransaction will start a DB transaction */ - DbTransaction(Database & database, QSqlDatabase & connection, SpecialBehaviours specialBehaviours = NONE); + DbTransaction(Database & database, + QSqlDatabase & connection, + QString const nameForLogging = "???", + SpecialBehaviours specialBehaviours = NONE); /** * \brief When a \c DbTransaction goes out of scope and its destructor is called, the transaction started in the @@ -53,6 +56,9 @@ class DbTransaction { Database & database; // This is intended to be a short-lived object, so it's OK to store a reference to a QSqlDatabase object QSqlDatabase & connection; + // This is useful for diagnosing problems such as + // 'Unable to start database transaction: "cannot start a transaction within a transaction Unable to begin transaction"' + QString const nameForLogging; bool committed; int specialBehaviours; diff --git a/src/database/DefaultContentLoader.cpp b/src/database/DefaultContentLoader.cpp index c95569a3..240eed94 100644 --- a/src/database/DefaultContentLoader.cpp +++ b/src/database/DefaultContentLoader.cpp @@ -156,6 +156,8 @@ DefaultContentLoader::UpdateResult DefaultContentLoader::updateContentIfNecessar allRecipesBeforeImport.begin(), allRecipesBeforeImport.end(), std::back_inserter(newlyImportedRecipes)); qDebug() << Q_FUNC_INFO << newlyImportedRecipes.size() << "newly imported Recipes"; + // TODO: It would be neat, at some point, to to have a mechanism for setting a property on multiple objects + // of the same type, so that we could do it in a single DB update. for (auto recipe : newlyImportedRecipes) { recipe->setFolder(FOLDER_FOR_SUPPLIED_RECIPES); } diff --git a/src/database/ObjectStore.cpp b/src/database/ObjectStore.cpp index 5cbc2871..208903cb 100644 --- a/src/database/ObjectStore.cpp +++ b/src/database/ObjectStore.cpp @@ -1409,7 +1409,9 @@ void ObjectStore::loadAll(Database * database) { // // .:TBD:. In theory we don't need a transaction if we're _only_ reading data... QSqlDatabase connection = this->pimpl->database->sqlDatabase(); - DbTransaction dbTransaction{*this->pimpl->database, connection}; + DbTransaction dbTransaction{*this->pimpl->database, + connection, + QString("Load All %1").arg(*this->pimpl->primaryTable.tableName)}; // // Using QSqlTableModel would save us having to write a SELECT statement, however it is a bit hard to use it to @@ -1710,7 +1712,9 @@ int ObjectStore::insert(std::shared_ptr object) { // Start transaction // (By the magic of RAII, this will abort if we return from this function without calling dbTransaction.commit() QSqlDatabase connection = this->pimpl->database->sqlDatabase(); - DbTransaction dbTransaction{*this->pimpl->database, connection}; + DbTransaction dbTransaction{*this->pimpl->database, + connection, + QString("Insert %1").arg(*this->pimpl->primaryTable.tableName)}; int primaryKey = this->pimpl->insertObjectInDb(connection, *object, false); @@ -1749,7 +1753,9 @@ void ObjectStore::update(std::shared_ptr object) { // Start transaction // (By the magic of RAII, this will abort if we return from this function without calling dbTransaction.commit() QSqlDatabase connection = this->pimpl->database->sqlDatabase(); - DbTransaction dbTransaction{*this->pimpl->database, connection}; + DbTransaction dbTransaction{*this->pimpl->database, + connection, + QString("Update %1").arg(*this->pimpl->primaryTable.tableName)}; // // Construct the SQL, which will be of the form @@ -1875,7 +1881,11 @@ void ObjectStore::updateProperty(QObject const & object, BtStringConst const & p // Start transaction // (By the magic of RAII, this will abort if we return from this function without calling dbTransaction.commit() QSqlDatabase connection = this->pimpl->database->sqlDatabase(); - DbTransaction dbTransaction{*this->pimpl->database, connection}; + DbTransaction dbTransaction{ + *this->pimpl->database, + connection, + QString("Update property %1 on %2").arg(*propertyName).arg(*this->pimpl->primaryTable.tableName) + }; if (!this->pimpl->updatePropertyInDb(connection, object, propertyName)) { // Something went wrong. Bailing out here will abort the transaction and avoid sending the signal. @@ -1918,7 +1928,9 @@ std::shared_ptr ObjectStore::defaultHardDelete(int id) { qDebug() << Q_FUNC_INFO << "Hard delete" << this->pimpl->m_className << "#" << id; auto object = this->pimpl->allObjects.value(id); QSqlDatabase connection = this->pimpl->database->sqlDatabase(); - DbTransaction dbTransaction{*this->pimpl->database, connection}; + DbTransaction dbTransaction{*this->pimpl->database, + connection, + QString("Hard delete %1").arg(*this->pimpl->primaryTable.tableName)}; // We'll use this in a couple of places below QVariant primaryKey{id}; diff --git a/src/database/ObjectStoreTyped.cpp b/src/database/ObjectStoreTyped.cpp index 6a44f538..354291b0 100644 --- a/src/database/ObjectStoreTyped.cpp +++ b/src/database/ObjectStoreTyped.cpp @@ -582,7 +582,8 @@ namespace { {ObjectStore::FieldType::Double, "iron_ppm" , PropertyNames::Water::iron_ppm }, {ObjectStore::FieldType::Double, "nitrate_ppm" , PropertyNames::Water::nitrate_ppm }, {ObjectStore::FieldType::Double, "nitrite_ppm" , PropertyNames::Water::nitrite_ppm }, - {ObjectStore::FieldType::Double, "flouride_ppm" , PropertyNames::Water::flouride_ppm }, + // .:TODO:. We should correct the typo in this column name (copy-and-paste from BeerJSON + {ObjectStore::FieldType::Double, "flouride_ppm" , PropertyNames::Water::fluoride_ppm }, } }; @@ -1041,7 +1042,7 @@ bool WriteAllObjectStoresToNewDb(Database & newDatabase, QSqlDatabase & connecti // having called dbTransaction.commit(). (It will also turn foreign keys back on either way -- whether the // transaction is committed or rolled back.) // - DbTransaction dbTransaction{newDatabase, connectionNew, DbTransaction::DISABLE_FOREIGN_KEYS}; + DbTransaction dbTransaction{newDatabase, connectionNew, "Write All", DbTransaction::DISABLE_FOREIGN_KEYS}; for (ObjectStore const * objectStore : getAllObjectStores()) { if (!objectStore->writeAllToNewDb(newDatabase, connectionNew)) { diff --git a/src/editors/BoilEditor.cpp b/src/editors/BoilEditor.cpp index c9cd0a48..1e444bfc 100644 --- a/src/editors/BoilEditor.cpp +++ b/src/editors/BoilEditor.cpp @@ -22,44 +22,23 @@ #include "model/Boil.h" #include "model/Recipe.h" -BoilEditor::BoilEditor(QWidget* parent) : +BoilEditor::BoilEditor(QWidget* parent, QString const editorName) : QDialog(parent), - EditorWithRecipeBase() { + EditorBase(editorName) { this->setupUi(this); this->postSetupUiInit( { - EDITOR_FIELD(Boil, label_name , lineEdit_name , PropertyNames::NamedEntity::name ), - EDITOR_FIELD(Boil, label_description, textEdit_description, PropertyNames::Boil::description ), - EDITOR_FIELD(Boil, label_preBoilSize, lineEdit_preBoilSize, PropertyNames::Boil::preBoilSize_l, 2), - EDITOR_FIELD(Boil, label_notes , textEdit_notes , PropertyNames::Boil::notes ) + EDITOR_FIELD_NORM(Boil, label_name , lineEdit_name , NamedEntity::name ), + EDITOR_FIELD_NORM(Boil, label_description, textEdit_description, Boil::description ), + EDITOR_FIELD_NORM(Boil, label_preBoilSize, lineEdit_preBoilSize, Boil::preBoilSize_l, 2), + EDITOR_FIELD_NORM(Boil, label_notes , textEdit_notes , Boil::notes ), } ); - // NB: label_description / textEdit_description don't need initialisation here as neither is a smart field - // NB: label_notes / textEdit_notes don't need initialisation here as neither is a smart field -/// SMART_FIELD_INIT(BoilEditor, label_name , lineEdit_name , Boil, PropertyNames::NamedEntity::name ); -/// SMART_FIELD_INIT(BoilEditor, label_preBoilSize, lineEdit_preBoilSize, Boil, PropertyNames::Boil::preBoilSize_l, 2); - -/// connect(this, &QDialog::accepted, this, &BoilEditor::saveAndClose); -/// connect(this, &QDialog::rejected, this, &BoilEditor::closeEditor ); - -/// this->connectSignalsAndSlots(); return; } BoilEditor::~BoilEditor() = default; -void BoilEditor::writeFieldsToEditItem() { - return; -} - -void BoilEditor::writeLateFieldsToEditItem() { - return; -} - -void BoilEditor::readFieldsFromEditItem([[maybe_unused]] std::optional propName) { - return; -} - // Insert the boilerplate stuff that we cannot do in EditorWithRecipeBase -EDITOR_WITH_RECIPE_COMMON_CODE(BoilEditor) +EDITOR_COMMON_CODE(Boil) diff --git a/src/editors/BoilEditor.h b/src/editors/BoilEditor.h index d18531b9..8564e13f 100644 --- a/src/editors/BoilEditor.h +++ b/src/editors/BoilEditor.h @@ -23,9 +23,10 @@ #include "ui_boilEditor.h" -#include "editors/EditorWithRecipeBase.h" +#include "editors/EditorBase.h" #include "model/Boil.h" +#define BoilEditorOptions EditorBaseOptions{ .recipe = true } /*! * \class BoilEditor * @@ -33,10 +34,12 @@ * * See also \c NamedBoilEditor */ -class BoilEditor : public QDialog, public Ui::boilEditor, public EditorWithRecipeBase { +class BoilEditor : public QDialog, + public Ui::boilEditor, + public EditorBase { Q_OBJECT - EDITOR_WITH_RECIPE_COMMON_DECL(Boil) + EDITOR_COMMON_DECL(Boil, BoilEditorOptions) }; #endif diff --git a/src/editors/BoilStepEditor.cpp b/src/editors/BoilStepEditor.cpp index 418f4ab6..bdbc68e8 100644 --- a/src/editors/BoilStepEditor.cpp +++ b/src/editors/BoilStepEditor.cpp @@ -18,67 +18,28 @@ #include "MainWindow.h" #include "measurement/Unit.h" -BoilStepEditor::BoilStepEditor(QWidget* parent) : +BoilStepEditor::BoilStepEditor(QWidget* parent, QString const editorName) : QDialog{parent}, - EditorBase() { + EditorBase(editorName) { this->setupUi(this); - - // NB: label_description / textEdit_description don't need initialisation here as neither is a smart field - SMART_FIELD_INIT(BoilStepEditor, label_name , lineEdit_name , BoilStep, PropertyNames:: NamedEntity::name ); - SMART_FIELD_INIT(BoilStepEditor, label_startTemp , lineEdit_startTemp , BoilStep, PropertyNames:: Step::startTemp_c , 1); - SMART_FIELD_INIT(BoilStepEditor, label_stepTime , lineEdit_stepTime , BoilStep, PropertyNames:: Step::stepTime_mins , 0); - SMART_FIELD_INIT(BoilStepEditor, label_rampTime , lineEdit_rampTime , BoilStep, PropertyNames:: Step::rampTime_mins , 0); - SMART_FIELD_INIT(BoilStepEditor, label_endTemp , lineEdit_endTemp , BoilStep, PropertyNames:: Step::endTemp_c , 1); - SMART_FIELD_INIT(BoilStepEditor, label_startAcidity , lineEdit_startAcidity , BoilStep, PropertyNames:: Step::startAcidity_pH, 1); - SMART_FIELD_INIT(BoilStepEditor, label_endAcidity , lineEdit_endAcidity , BoilStep, PropertyNames:: Step::endAcidity_pH , 1); - SMART_FIELD_INIT(BoilStepEditor, label_startGravity , lineEdit_startGravity , BoilStep, PropertyNames::StepExtended::startGravity_sg, 3); - SMART_FIELD_INIT(BoilStepEditor, label_endGravity , lineEdit_endGravity , BoilStep, PropertyNames::StepExtended::endGravity_sg , 3); - - BT_COMBO_BOX_INIT(BoilStepEditor, comboBox_boilStepChillingType, BoilStep, chillingType); - - this->connectSignalsAndSlots(); + this->postSetupUiInit({ + EDITOR_FIELD_NORM(BoilStep, label_name , lineEdit_name , NamedEntity::name ), + EDITOR_FIELD_NORM(BoilStep, label_description , textEdit_description , Step::description ), + EDITOR_FIELD_NORM(BoilStep, label_startTemp , lineEdit_startTemp , Step::startTemp_c , 1), + EDITOR_FIELD_NORM(BoilStep, label_stepTime , lineEdit_stepTime , Step::stepTime_mins , 0), + EDITOR_FIELD_NORM(BoilStep, label_rampTime , lineEdit_rampTime , Step::rampTime_mins , 0), + EDITOR_FIELD_NORM(BoilStep, label_endTemp , lineEdit_endTemp , Step::endTemp_c , 1), + EDITOR_FIELD_NORM(BoilStep, label_startAcidity , lineEdit_startAcidity , Step::startAcidity_pH , 1), + EDITOR_FIELD_NORM(BoilStep, label_endAcidity , lineEdit_endAcidity , Step::endAcidity_pH , 1), + EDITOR_FIELD_NORM(BoilStep, label_startGravity , lineEdit_startGravity , StepExtended::startGravity_sg, 3), + EDITOR_FIELD_NORM(BoilStep, label_endGravity , lineEdit_endGravity , StepExtended::endGravity_sg , 3), + EDITOR_FIELD_ENUM(BoilStep, label_boilStepChillingType, comboBox_boilStepChillingType, BoilStep::chillingType ), + }); return; } BoilStepEditor::~BoilStepEditor() = default; -void BoilStepEditor::readFieldsFromEditItem(std::optional propName) { - if (!propName || *propName == PropertyNames:: NamedEntity::name ) { this->lineEdit_name ->setTextCursor(m_editItem->name ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::description ) { this->textEdit_description ->setPlainText (m_editItem->description ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::startTemp_c ) { this->lineEdit_startTemp ->setQuantity (m_editItem->startTemp_c ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::stepTime_mins ) { this->lineEdit_stepTime ->setQuantity (m_editItem->stepTime_mins ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::rampTime_mins ) { this->lineEdit_rampTime ->setQuantity (m_editItem->rampTime_mins ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::endTemp_c ) { this->lineEdit_endTemp ->setQuantity (m_editItem->endTemp_c ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::startAcidity_pH) { this->lineEdit_startAcidity->setQuantity (m_editItem->startAcidity_pH()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::endAcidity_pH ) { this->lineEdit_endAcidity ->setQuantity (m_editItem->endAcidity_pH ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::StepExtended::startGravity_sg) { this->lineEdit_startGravity->setQuantity (m_editItem->startGravity_sg()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::StepExtended::endGravity_sg ) { this->lineEdit_endGravity ->setQuantity (m_editItem->endGravity_sg ()); if (propName) { return; } } - - if (!propName || *propName == PropertyNames::BoilStep::chillingType ) { this->comboBox_boilStepChillingType->setValue(m_editItem->chillingType()); if (propName) { return; } } - return; -} - -void BoilStepEditor::writeFieldsToEditItem() { - this->m_editItem->setName (this->lineEdit_name ->text ()); - this->m_editItem->setDescription (this->textEdit_description ->toPlainText ()); - this->m_editItem->setStartTemp_c (this->lineEdit_startTemp ->getOptCanonicalQty()); - this->m_editItem->setStepTime_mins (this->lineEdit_stepTime ->getOptCanonicalQty()); - this->m_editItem->setRampTime_mins (this->lineEdit_rampTime ->getOptCanonicalQty()); - this->m_editItem->setEndTemp_c (this->lineEdit_endTemp ->getOptCanonicalQty()); - this->m_editItem->setStartAcidity_pH(this->lineEdit_startAcidity->getOptCanonicalQty()); - this->m_editItem->setEndAcidity_pH (this->lineEdit_endAcidity ->getOptCanonicalQty()); - this->m_editItem->setStartGravity_sg(this->lineEdit_startGravity->getOptCanonicalQty()); - this->m_editItem->setEndGravity_sg (this->lineEdit_endGravity ->getOptCanonicalQty()); - - this->m_editItem->setChillingType(this->comboBox_boilStepChillingType->getOptValue()); - return; -} - -void BoilStepEditor::writeLateFieldsToEditItem() { - // Nothing to do here - return; -} - // Insert the boiler-plate stuff that we cannot do in EditorBase -EDITOR_COMMON_CODE(BoilStepEditor) +EDITOR_COMMON_CODE(BoilStep) diff --git a/src/editors/BoilStepEditor.h b/src/editors/BoilStepEditor.h index 7e64c29d..0d46f925 100644 --- a/src/editors/BoilStepEditor.h +++ b/src/editors/BoilStepEditor.h @@ -24,16 +24,18 @@ #include "editors/EditorBase.h" #include "model/BoilStep.h" +#define BoilStepEditorOptions EditorBaseOptions{ } /*! * \class BoilStepEditor * * \brief View/controller dialog for editing boil steps. */ -class BoilStepEditor : public QDialog, public Ui::boilStepEditor, public EditorBase { +class BoilStepEditor : public QDialog, + public Ui::boilStepEditor, + public EditorBase { Q_OBJECT - EDITOR_COMMON_DECL(BoilStep) - + EDITOR_COMMON_DECL(BoilStep, BoilStepEditorOptions) }; #endif diff --git a/src/editors/EditorBase.h b/src/editors/EditorBase.h index 80ca37dd..1e378718 100644 --- a/src/editors/EditorBase.h +++ b/src/editors/EditorBase.h @@ -24,113 +24,54 @@ #include #include #include +#include +#include "BtHorizontalTabs.h" #include "database/ObjectStoreWrapper.h" +#include "editors/EditorBaseField.h" #include "model/NamedEntity.h" +#include "model/Recipe.h" // Need to include this this to be able to cast Recipe to QObject #include "utils/CuriouslyRecurringTemplateBase.h" /** - * \brief Field info for a field of a subclass of \c EditorBase. + * \brief This is used as a template parameter to turn on and off various \b small features in \c EditorBase (in + * conjunction with the concepts defined below). * - * Note that we can't put this inside the \c EditorBase class declaration as we also want to use it there, and - * we'd get errors about "invalid use of incomplete type ‘class EditorBase’". + * \sa EditorBase */ -struct EditorBaseField { +struct EditorBaseOptions { /** - * \brief Most fields are written together. However, some are marked 'Late' because they need to be written after - * the object is created. + * \brief Enabling this turns on the temporary live copy of edit item, whose fields are updated straight away as + * edits are made. This is useful for showing calculated values or drawing charts. */ - enum class WhenToWrite { - Normal, - Late - }; - - char const * labelName; - std::variant label; - // We need to know what type the field is, partly because QLineEdit and QTextEdit don't have a useful common base - // class, and partly because we want to access member functions of SmartLineEdit that don't exist on QLineEdit or - // QTextEdit. - std::variant editField; - BtStringConst const & property; - // Both the next two fields have defaults, but precision is the one that more often needs something other than - // default to be specified, so we put it first. - std::optional precision = std::nullopt; - EditorBaseField::WhenToWrite whenToWrite = WhenToWrite::Normal; - - //! Constructor for when we don't have a SmartLineEdit - template - EditorBaseField([[maybe_unused]] char const * const editorClass, - char const * const labelName, - [[maybe_unused]] char const * const labelFqName, - LabelType * label, - [[maybe_unused]] char const * const editFieldName, - [[maybe_unused]] char const * const editFieldFqName, - EditFieldType * editField, - BtStringConst const & property, - [[maybe_unused]] TypeInfo const & typeInfo, - std::optional precision = std::nullopt, - EditorBaseField::WhenToWrite whenToWrite = WhenToWrite::Normal) : - labelName {labelName }, - label {label }, - editField {editField }, - property {property }, - precision {precision }, - whenToWrite{whenToWrite} { - return; - } - - //! Constructor for when we have a SmartLineEdit - template - EditorBaseField(char const * const editorClass, - char const * const labelName, - char const * const labelFqName, - LabelType * label, - char const * const editFieldName, - char const * const editFieldFqName, - SmartLineEdit * editField, - BtStringConst const & property, - TypeInfo const & typeInfo, - std::optional precision = std::nullopt, - EditorBaseField::WhenToWrite whenToWrite = WhenToWrite::Normal) : - labelName {labelName }, - label {label }, - editField {editField }, - property {property }, - precision {precision }, - whenToWrite{whenToWrite} { - SmartAmounts::Init(editorClass, - labelName, - labelFqName, - *label, - editFieldName, - editFieldFqName, - *editField, - typeInfo, - precision); - return; - } - + bool liveEditItem = false; + /** + * \brief Enabling this turns on the automatic update of the first tab to show the name of the item being edited. + * This relies on the \c Derived class having a \c QTabWidget called \c tabWidget_editor (and that the object + * name is provided by the \c PropertyNames::NamedEntity::name property). + */ + bool nameTab = false; + /** + * \brief Enabling this turns on the observation of (the current) \c Recipe. Typically it makes sense for us to be + * able to watch a \c Recipe when it can have at most one of the thing we are editing (\c Boil, \c Equipment, + * \c Fermentation, \c Mash, \c Style). + */ + bool recipe = false; + /** + * \brief Enabling this means we show the edit item's ID (obtained from the \c NamedEntity::key() function) in a + * label field called \c label_id_value. + */ + bool idDisplay = false; }; - -/** - * \brief This macro is similar to SMART_FIELD_INIT, but allows us to pass the EditorBaseField constructor. - * - * We assume that, where Foo is some subclass of NamedEntity, then the editor class for Foo is always called - * FooEditor. - */ -#define EDITOR_FIELD(modelClass, label, editField, property, ...) \ - EditorBaseField{\ - #modelClass "Editor", \ - #label, \ - #modelClass "Editor->" #label, \ - label, \ - #editField, \ - #modelClass "Editor->" #editField, \ - editField, \ - property, \ - modelClass ::typeLookup.getType(property) \ - __VA_OPT__(, __VA_ARGS__) \ - } +template struct has_LiveEditItem : public std::integral_constant{}; +template struct has_NameTab : public std::integral_constant{}; +template struct has_Recipe : public std::integral_constant{}; +template struct has_IdDisplay : public std::integral_constant{}; +// See comment in utils/TypeTraits.h for definition of CONCEPT_FIX_UP (and why, for now, we need it) +template concept CONCEPT_FIX_UP HasLiveEditItem = has_LiveEditItem::value; +template concept CONCEPT_FIX_UP HasNameTab = has_NameTab ::value; +template concept CONCEPT_FIX_UP HasRecipe = has_Recipe ::value; +template concept CONCEPT_FIX_UP HasIdDisplay = has_IdDisplay ::value; /** * \class EditorBase @@ -165,45 +106,128 @@ struct EditorBaseField { * Note that we cannot do the equivalent for the header file declarations because the Qt MOC does not expand * non-Qt macros. * - * The derived class also needs to implement the following substantive member functions that \c EditorBase will - * call: - * - \c void \c writeFieldsToEditItem -- Writes most fields from the editor GUI fields into the object being - * edited - * - \c void \c writeLateFieldsToEditItem -- Writes any fields that must wait until the object definitely - * exists in the DB - * - \c void \c readFieldsFromEditItem -- (Re)read one or all fields from the object into the relevant GUI - * field(s). + * Additionally, derived class needs to have the following \c QPushButton members (typically defined in the .ui + * file): + * \c pushButton_new, \c pushButton_save, \c pushButton_cancel * - * Finally, derived class needs to have the following QPushButton members (typically defined in the .ui file): - * pushButton_new, pushButton_save, pushButton_cancel + * The LiveEditItem template parameter determines whether we keep a "live" copy of whatever is being edited (ie + * a copy object to which edits will be applied in real time). This is useful to show fields calculated by the + * NE object itself or (as in the case of \c WaterEditor) to feed data to a chart. Subclasses that set + * \c editorBaseOptions.liveEditItem to \c true need the following additional private member functions: + * - \c void \c postInputFieldModified -- Update any chart following input field modification. */ template class EditorPhantom; -template +template class EditorBase : public CuriouslyRecurringTemplateBase { public: - /** * \brief Constructor * + * Often with CRTP it's good to make the constructor private and Derived a friend, so that only Derived can + * call the CRTP base constructor. This stops errors with incorrect inheritance - eg makes a compile error if + * we write `class FooEditor : ... public EditorBase` instead of + * `class FooEditor : ... public EditorBase`. However, since we want EditorWithRecipeBase to + * inherit from EditorBase, we can't do that trick here. + * * Note that we cannot initialise this->m_fields here, as the parameters themselves won't get constructed * until Derived calls setupUi(). */ - EditorBase() : + EditorBase(QString const editorName) : + m_editorName{editorName}, m_fields{nullptr}, - m_editItem{nullptr} { + m_editItem{nullptr}, + m_liveEditItem{nullptr} { + return; + } + ~EditorBase() = default; + + //! No-op version + void setupTabs() requires (!HasNameTab) { return; } + //! Substantive version + void setupTabs() requires (HasNameTab) { + this->derived().tabWidget_editor->tabBar()->setStyle(new BtHorizontalTabs); return; } - virtual ~EditorBase() = default; /** * \brief Derived should call this after calling setupUi + * + * NOTE that where two fields are linked to the same property (typically where we have an amount field and a + * combo box that controls whether that amount is mass/volume/etc) they \b must be adjacent in + * initializer_list. (This is because of how we do early break out when only */ - void postSetupUiInit(std::initializer_list fields) { - this->m_fields = std::make_unique>(fields); + void postSetupUiInit(std::initializer_list fields) { + this->m_fields = std::make_unique>(fields); + this->setupTabs(); this->connectSignalsAndSlots(); return; } + //! \brief No-op version + void connectLiveEditSignalsAndSlots() requires (!HasLiveEditItem) { + return; + } + + /** + * \brief When we have a live edit item, we want to know each time an input field has been modified so that we can + * update the corresponding property of the live edit item. We connect the relevant signal to + * Derived::inputFieldModified, which then calls doInputFieldModified + */ + void connectLiveEditSignalsAndSlots() requires HasLiveEditItem { + if (this->m_fields) { + for (auto const & field : *this->m_fields) { + // Using std::visit and a lambda allows us to do generic things on std::variant + std::visit( + [this](auto&& fieldInfo) { + fieldInfo.connectFieldChanged(&this->derived(), &Derived::inputFieldModified); + }, + field + ); + } + } + return; + } + + //! \brief No-op version + void doInputFieldModified([[maybe_unused]] QObject const * const signalSender) + requires (!HasLiveEditItem) { + return; + } + + //! \brief Substantive version + void doInputFieldModified(QObject const * const signalSender) requires (HasLiveEditItem) { + if (this->m_fields && this->m_liveEditItem && signalSender && signalSender->parent() == &this->derived()) { + bool foundMatch = false; + for (auto const & field : *this->m_fields) { + // Using std::visit and a lambda allows us to do generic things on std::variant + if (std::visit( + // Lambda returns true if we matched the signal sender to this EditorBaseField, false otherwise + [this, signalSender](auto&& fieldInfo) { + if (signalSender == fieldInfo.editField) { + fieldInfo.setPropertyFromEditField(*this->m_liveEditItem); + return true; + } + return false; + }, + field + )) { + foundMatch = true; + break; + } + } + + if (!foundMatch) { + // If we get here, it's probably a coding error but there's no harm in soldiering on + qWarning() << Q_FUNC_INFO << "Unrecognised signal sender"; + return; + } + + this->derived().postInputFieldModified(); + } + return; + } + /** * \brief Call this at the end of derived class's constructor (in particular, after the call to \c setupUi). * @@ -216,6 +240,18 @@ class EditorBase : public CuriouslyRecurringTemplateBase this->derived().connect(this->derived().pushButton_new , &QAbstractButton::clicked, &this->derived(), &Derived::clickedNew ); this->derived().connect(this->derived().pushButton_save , &QAbstractButton::clicked, &this->derived(), &Derived::saveAndClose ); this->derived().connect(this->derived().pushButton_cancel, &QAbstractButton::clicked, &this->derived(), &Derived::clearAndClose); + // + this->connectLiveEditSignalsAndSlots(); + return; + } + + //! \brief No-op version + void makeLiveEditItem() requires (!HasLiveEditItem) { + return; + } + + void makeLiveEditItem() requires HasLiveEditItem { + this->m_liveEditItem = std::make_unique(*this->m_editItem); return; } @@ -231,8 +267,22 @@ class EditorBase : public CuriouslyRecurringTemplateBase this->m_editItem = editItem; if (this->m_editItem) { this->derived().connect(this->m_editItem.get(), &NamedEntity::changed, &this->derived(), &Derived::changed); - this->readFromEditItem(std::nullopt); + this->readFieldsFromEditItem(std::nullopt); } + + this->makeLiveEditItem(); + + // Comment below about calling this->derived().validateBeforeSave() also applies here + this->derived().postSetEditItem(); + return; + } + + /** + * \brief \c Derived can override this if there is additional processing to do at the end of \c setEditItem + * + * This is used, eg, in \c WaterEditor to set up the \c RadarChart + */ + void postSetEditItem() { return; } @@ -274,56 +324,6 @@ class EditorBase : public CuriouslyRecurringTemplateBase return; } - /** - * \brief If \c fromEditItem is \c true supplied, sets the \c field.editField from the \c field.property of - * \c this->m_editItem -- ie populates/updates the UI input field from the model object. - * If \c fromEditItem is \c false, clears the edit field. - */ - void getProperty(EditorBaseField const & field, bool const fromEditItem = true) { - QVariant value; - if (fromEditItem) { - value = this->m_editItem->property(*field.property); - } else { - value = QString{""}; - } - - // Usually leave this debug log commented out unless trouble-shooting as it generates a lot of logging -// qDebug() << Q_FUNC_INFO << field.labelName << "read from" << field.property << "as" << value; - - if (std::holds_alternative(field.editField)) { - std::get(field.editField)->setPlainText(value.toString()); - } else if (std::holds_alternative(field.editField)) { - std::get(field.editField)->setText(value.toString()); - } else { - auto sle = std::get(field.editField); - if (fromEditItem) { - sle->setFromVariant(value); - } else { - sle->setText(value.toString()); - } - } - return; - } - - /** - * \brief Sets \c field.property on \c this->m_editItem to the \c field.editField value -- ie writes back the UI - * value into the model object. - */ - void setProperty(EditorBaseField const & field) { - QVariant val; - if (std::holds_alternative(field.editField)) { - val = QVariant::fromValue(std::get(field.editField)->toPlainText()); - } else if (std::holds_alternative(field.editField)) { - val = QVariant::fromValue(std::get(field.editField)->text()); - } else { - auto sle = std::get(field.editField); - val = sle->getAsVariant(); - } - - this->m_editItem->setProperty(*field.property, val); - return; - } - /** * \brief Subclass should override this if it needs to validate the form before saving happens. * @@ -348,11 +348,11 @@ class EditorBase : public CuriouslyRecurringTemplateBase return; } - this->writeNormalFields(); + this->writeNormalFieldsToEditItem(); if (this->m_editItem->key() < 0) { ObjectStoreWrapper::insert(this->m_editItem); } - this->writeLateFields(); + this->writeLateFieldsToEditItem(); this->derived().setVisible(false); return; @@ -367,36 +367,111 @@ class EditorBase : public CuriouslyRecurringTemplateBase return; } + //! \brief No-op version + void updateNameTabIfNeeded([[maybe_unused]] std::optional propName) + requires (!HasNameTab) { + return; + } + //! \brief Substantive version + void updateNameTabIfNeeded(std::optional propName) requires (HasNameTab) { + if (!propName || *propName == PropertyNames::NamedEntity::name) { + this->derived().tabWidget_editor->setTabText(0, this->m_editItem->name()); + } + return; + } + + //! \brief No-op version + void showId() requires (!HasIdDisplay) { return; } + //! \brief Substantive version + void showId() requires (HasIdDisplay) { + // This label does not have an input field; it just shows the ID of the item + this->derived().label_id_value->setText(QString::number(this->m_editItem->key())); + return; + } + + //! Derived classes can override this for any extra behaviour + void postReadFieldsFromEditItem([[maybe_unused]] std::optional propName) { return; } + /** - * \brief Read either one field (if \c propName specified) or all (if it is \c std::nullopt) into the UI from the + * \brief (Re)read either one field (if \c propName specified) or all (if it is \c std::nullopt) into the UI from the * model item. */ - void readFromEditItem(std::optional propName) { - if (this->m_fields) { + void readFieldsFromEditItem(std::optional propName) { + if (this->m_editItem && this->m_fields) { + bool matched = false; for (auto const & field : *this->m_fields) { - if (!propName || *propName == field.property) { - this->getProperty(field); - if (propName) { - // Break out here if we were only updating one property - break; - } + if (std::visit( + // + // This lambda returns true if we should stop subsequent loop processing, or false if we should carry on + // looking at subsequent fields. Because the code inside the lambda cannot directly break out of the + // for loop, we have to return this boolean to tell the calling code whether to break out. + // + [this, &propName, &matched](auto&& fieldInfo) { + // + // The update rule is simple -- we either update all fields (because no property name is supplied) or + // only the field(s) for the supplied property name. + // + // In most cases, there will only be one field per property name. However, we also have to handle the + // case where we have a combo-box that is controlling the physical quantity for another field (eg + // whether an input field is mass or volume). By convention, where there is more than one field for a + // property name, the records must be adjacent in the m_fields vector. This makes our "break" + // criteria relatively simple: + // - We have a property name + // - We already matched it at least once + // - The current field does not match + // + if (!propName || *propName == fieldInfo.property) { + // Normally leave this log statement commented out as it generates too many lines in the log file +// qDebug() << Q_FUNC_INFO << "Reading" << fieldInfo.property; + fieldInfo.setEditFieldFromProperty(*this->m_editItem); + if (propName) { + matched = true; + } + } else if (!propName && matched) { + return true; + } + return false; + }, + field + )) { + break; } } } - // TODO: For the moment, we still do this call, but ultimately we'll eliminate it. - this->derived().readFieldsFromEditItem(propName); + // The ID is not going to change unless we're reading in all fields + if (!propName) { + this->showId(); + } + this->updateNameTabIfNeeded(propName); + // Note the need for derived() here to allow Derived to override + this->derived().postReadFieldsFromEditItem(propName); return; } + //! No-op version + bool handleChangeFromRecipe([[maybe_unused]] QObject * sender) requires (!HasRecipe) { + return false; + } + //! Substantive version + bool handleChangeFromRecipe(QObject * sender) requires (HasRecipe) { + if (this->m_recipeObs && sender == static_cast(this->m_recipeObs)) { + this->readAllFields(); + return true; + } + return false; + } /** * \brief Subclass should call this from its \c changed slot * * Note that \c QObject::sender has \c protected access specifier, so we can't call it from here, not even * via the derived class pointer. Therefore we have derived class call it and pass us the result. */ - virtual void doChanged(QObject * sender, QMetaProperty prop, [[maybe_unused]] QVariant val) { + void doChanged(QObject * sender, QMetaProperty prop, [[maybe_unused]] QVariant val) { + if (this->handleChangeFromRecipe(sender)) { + return; + } if (this->m_editItem && sender == this->m_editItem.get()) { - this->readFromEditItem(prop.name()); + this->readFieldsFromEditItem(prop.name()); } return; } @@ -404,7 +479,12 @@ class EditorBase : public CuriouslyRecurringTemplateBase void doClearFields() { if (this->m_fields) { for (auto const & field : *this->m_fields) { - this->getProperty(field, false); + std::visit( + [](auto&& fieldInfo) { + fieldInfo.clearEditField(); + }, + field + ); } } return; @@ -412,44 +492,85 @@ class EditorBase : public CuriouslyRecurringTemplateBase void readAllFields() { if (this->m_editItem) { - this->readFromEditItem(std::nullopt); + this->readFieldsFromEditItem(std::nullopt); } else { this->doClearFields(); } return; } - void writeFields(EditorBaseField::WhenToWrite const normalOrLate) { - if (this->m_fields) { + void writeFields(WhenToWriteField const normalOrLate) { + if (this->m_editItem && this->m_fields) { for (auto const & field : *this->m_fields) { - if (normalOrLate == field.whenToWrite) { - this->setProperty(field); - } + std::visit( + [this, normalOrLate](auto&& fieldInfo) { + if (normalOrLate == fieldInfo.whenToWrite) { + fieldInfo.setPropertyFromEditField(*this->m_editItem); + } + }, + field + ); } } return; } - void writeNormalFields() { - this->writeFields(EditorBaseField::WhenToWrite::Normal); - // TODO: For the moment, we still do this call, but ultimately we'll eliminate it. - this->derived().writeFieldsToEditItem(); + //! Derived classes can override this for any extra behaviour + void postWriteNormalFieldsToEditItem() { return; } + + //! Write most fields from the editor GUI fields into the object being edited + void writeNormalFieldsToEditItem() { + this->writeFields(WhenToWriteField::Normal); + // Note the need for derived() here to allow Derived to override + this->derived().postWriteNormalFieldsToEditItem(); + return; + } + + //! Derived classes can override this for any extra behaviour + void postWriteLateFieldsToEditItem() { return; } + + //! Write any fields that must wait until the object definitely exists in the DB + void writeLateFieldsToEditItem() { + this->writeFields(WhenToWriteField::Late); + // Note the need for derived() here to allow Derived to override + this->derived().postWriteLateFieldsToEditItem(); + return; + } + + void setRecipe(Recipe * recipe) requires HasRecipe { + this->m_recipeObs = recipe; + // TBD: We could automatically set the edit item as follows: +// if (this->m_recipeObs) { +// this->m_editItem = this->m_recipeObs->get() +// } + return; + } + + //! \brief Show the editor and re-read the edit item. Used for editors with Recipe. + void doShowEditor() { + this->readAllFields(); + this->derived().setVisible(true); return; } - void writeLateFields() { - this->writeFields(EditorBaseField::WhenToWrite::Late); - // TODO: For the moment, we still do this call, but ultimately we'll eliminate it. - this->derived().writeLateFieldsToEditItem(); + //! \brief Close (hide) the editor without saving anything. Used for editors with Recipe. + void doCloseEditor() { + this->derived().setVisible(false); return; } protected: + /** + * \brief Optionally an editor can have a "name" to add some context. Eg for the Water editor, the water chemistry + * dialog allows you to have two of them open at once -- one "Base" and one "Target". + */ + QString const m_editorName; + /** * \brief Info about fields in this editor */ - std::unique_ptr> m_fields; + std::unique_ptr> m_fields; /** * \brief This is the \c NamedEntity subclass object we are creating or editing. We are also "observing" it in the @@ -458,6 +579,25 @@ class EditorBase : public CuriouslyRecurringTemplateBase * of the editor classes. */ std::shared_ptr m_editItem; + + /** + * \brief Optionally, an editor can create a temporary copy of \c m_editItem to which to apply edits immediately. + * This is useful if we want to be able to show calculated values or if (as in the case of \c WaterEditor) we + * want to use a copy object to as input to a chart or graph showing live edits. This object is discarded + * when the user clicks Save or Cancel. (In the former case, the form values are applied to \c m_editItem; in + * the latter they are not. + * + * There are various tricks where we could make the existence or type of this member variable depend on the + * LEI template parameter (see https://brevzin.github.io/c++/2021/11/21/conditional-members/) but it's + * currently a bit complicated, and should become easier with future reflection features. So, for now, we + * we don't worry about the overhead of unnecessarily having this member when LEI is LiveEditItem::Disabled. + */ + std::unique_ptr m_liveEditItem; + + /** + * \brief The \c Recipe, if any, that we are "observing". + */ + Recipe * m_recipeObs = nullptr; }; /** @@ -465,16 +605,14 @@ class EditorBase : public CuriouslyRecurringTemplateBase * * Note we have to be careful about comment formats in macro definitions */ -#define EDITOR_COMMON_DECL(NeName) \ +#define EDITOR_COMMON_DECL(NeName, Options) \ /* This allows EditorBase to call protected and private members of Derived */ \ - friend class EditorBase; \ + friend class EditorBase; \ \ public: \ - NeName##Editor(QWidget * parent = nullptr); \ + NeName##Editor(QWidget * parent = nullptr, QString const editorName = ""); \ virtual ~NeName##Editor(); \ \ - void writeFieldsToEditItem(); \ - void writeLateFieldsToEditItem(); \ void readFieldsFromEditItem(std::optional propName); \ \ public slots: \ @@ -483,14 +621,21 @@ class EditorBase : public CuriouslyRecurringTemplateBase void clearAndClose(); \ void changed(QMetaProperty, QVariant); \ void clickedNew(); \ + void inputFieldModified(); \ + /* Additional standard slots for editors with recipe */ \ + void showEditor(); \ + void closeEditor(); \ /** * \brief Derived classes should include this in their implementation file */ -#define EDITOR_COMMON_CODE(EditorName) \ - void EditorName::saveAndClose() { this->doSaveAndClose(); return; } \ - void EditorName::clearAndClose() { this->doClearAndClose(); return; } \ - void EditorName::changed(QMetaProperty prop, QVariant val) { this->doChanged(this->sender(), prop, val); return; } \ - void EditorName::clickedNew() { this->newEditItem(); return;} +#define EDITOR_COMMON_CODE(NeName) \ + void NeName##Editor::saveAndClose() { this->doSaveAndClose(); return; } \ + void NeName##Editor::clearAndClose() { this->doClearAndClose(); return; } \ + void NeName##Editor::changed(QMetaProperty prop, QVariant val) { this->doChanged(this->sender(), prop, val); return; } \ + void NeName##Editor::clickedNew() { this->newEditItem(); return; } \ + void NeName##Editor::inputFieldModified() { this->doInputFieldModified(this->sender()); return; }; \ + void NeName##Editor::showEditor() { this->doShowEditor(); } \ + void NeName##Editor::closeEditor() { this->doCloseEditor(); } \ #endif diff --git a/src/editors/EditorBaseField.h b/src/editors/EditorBaseField.h new file mode 100644 index 00000000..ab4785a8 --- /dev/null +++ b/src/editors/EditorBaseField.h @@ -0,0 +1,414 @@ +/*====================================================================================================================== + * editors/EditorBaseField.h is part of Brewken, and is copyright the following authors 2024: + * • Matt Young + * + * Brewken 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. + * + * Brewken 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 + * . + =====================================================================================================================*/ +#ifndef EDITORS_EDITORBASEFIELD_H +#define EDITORS_EDITORBASEFIELD_H +#pragma once + +#include + +#include "widgets/BtBoolComboBox.h" +#include "widgets/BtComboBox.h" + +// +// This is only included from one place -- editors/EditorBase.h -- but I think it's a big enough block that there is +// benefit in having it as a separate file. +// + +/** + * \brief Most fields are written together. However, some are marked 'Late' because they need to be written after + * the object is created. + * + * Logically this belongs inside EditorBaseField, but that's templated, so it would get a bit hard to refer to if + * we put it there. + */ +enum class WhenToWriteField { + Normal, + Late, + Never +}; + +/** + * \brief Field info for a field of a subclass of \c EditorBase. + * + * Note that we can't put this inside the \c EditorBase class declaration as we also want to use it there, and + * we'd get errors about "invalid use of incomplete type ‘class EditorBase’". + * + * We template on both label and edit field types. This is partly so we call the right overload of + * \c SmartAmounts::Init, partly because \c QLineEdit and \c QTextEdit don't have a useful common base class, and + * partly because we want to access member functions of \c SmartLineEdit that don't exist on \c QLineEdit or + * \c QTextEdit. + * + * Note that the member functions are mostly \c const because they are not modifying this struct -- merely things + * referenced by the struct. + */ +template +struct EditorBaseField { + + char const * labelName; + LabelType * label; + EditFieldType * editField; + BtStringConst const & property; + // This field isn't used for all values of EditFieldType, but we don't try to make it conditional for the same + // reasons as EditorBase::m_liveEditItem below. + std::optional precision = std::nullopt; + WhenToWriteField whenToWrite = WhenToWriteField::Normal; + bool hasControlledField = false; + + /** + * \brief Constructor for when we don't have a SmartLineEdit or similar + * + * NB: Both \c precision and \c whenToWrite have defaults, but precision is the one that more often needs + * something other than default to be specified, so we put it first in the argument list. + */ + EditorBaseField([[maybe_unused]] char const * const editorClass, + char const * const labelName, + [[maybe_unused]] char const * const labelFqName, + LabelType * label, + [[maybe_unused]] char const * const editFieldName, + [[maybe_unused]] char const * const editFieldFqName, + EditFieldType * editField, + BtStringConst const & property, + [[maybe_unused]] TypeInfo const & typeInfo, + std::optional precision = std::nullopt, + WhenToWriteField whenToWrite = WhenToWriteField::Normal) + requires (!std::same_as && + !std::same_as && + !std::same_as) : + labelName {labelName }, + label {label }, + editField {editField }, + property {property }, + precision {precision }, + whenToWrite{whenToWrite} { + return; + } + + //! Constructor for when we have a SmartLineEdit + EditorBaseField(char const * const editorClass, + char const * const labelName, + char const * const labelFqName, + LabelType * label, + char const * const editFieldName, + char const * const editFieldFqName, + SmartLineEdit * editField, + BtStringConst const & property, + TypeInfo const & typeInfo, + std::optional precision = std::nullopt, + WhenToWriteField whenToWrite = WhenToWriteField::Normal) + requires (std::same_as) : + labelName {labelName }, + label {label }, + editField {editField }, + property {property }, + precision {precision }, + whenToWrite{whenToWrite} { + SmartAmounts::Init(editorClass, + labelName, + labelFqName, + *label, + editFieldName, + editFieldFqName, + *editField, + typeInfo, + precision); + return; + } + + //! Constructor for when we have a BtComboBox + EditorBaseField(char const * const editorClass, + char const * const labelName, + [[maybe_unused]] char const * const labelFqName, + LabelType * label, + char const * const editFieldName, + char const * const editFieldFqName, + BtComboBox * editField, + BtStringConst const & property, + TypeInfo const & typeInfo, + EnumStringMapping const & nameMapping, + EnumStringMapping const & displayNameMapping, + std::vector const * restrictTo = nullptr, + SmartLineEdit * controlledField = nullptr, + WhenToWriteField whenToWrite = WhenToWriteField::Normal) + requires (std::same_as) : + labelName {labelName }, + label {label }, + editField {editField }, + property {property }, + precision {std::nullopt}, + whenToWrite{whenToWrite} { + editField->init(editorClass, + editFieldName, + editFieldFqName, + nameMapping, + displayNameMapping, + typeInfo, + restrictTo, + controlledField); + if (controlledField) { + this->hasControlledField = true; + } + return; + } + + //! Constructor for when we have a BtBoolComboBox + EditorBaseField(char const * const editorClass, + char const * const labelName, + [[maybe_unused]] char const * const labelFqName, + LabelType * label, + char const * const editFieldName, + char const * const editFieldFqName, + BtBoolComboBox * editField, + BtStringConst const & property, + TypeInfo const & typeInfo, + QString const & unsetDisplay = QObject::tr("No"), + QString const & setDisplay = QObject::tr("Yes"), + WhenToWriteField whenToWrite = WhenToWriteField::Normal) + requires (std::same_as) : + labelName {labelName }, + label {label }, + editField {editField }, + property {property }, + precision {std::nullopt}, + whenToWrite{whenToWrite} { + // We could use BT_BOOL_COMBO_BOX_INIT here, but we'd be repeating a bunch of work we already did in EDITOR_FIELD + editField->init(editorClass, + editFieldName, + editFieldFqName, + unsetDisplay, + setDisplay, + typeInfo); + return; + } + + // + // You might think that in these connectFieldChanged, it would suffice to use QObject * as the type of context, but + // this gave an error about "invalid conversion from ‘QObject*’ to + // ‘const QtPrivate::FunctionPointer::Object*’ {aka ‘const WaterEditor*’} [-fpermissive]". + // Rather than fight this, we just add another template parameter. + // + + //! Simple case - the field tells us editing finished via the \c editingFinished signal + template + void connectFieldChanged(Derived * context, Functor functor) const + requires (std::same_as) { + // We ignore the defaulted parameter on connect, and its return value, as we don't need them + context->connect(this->editField, &EditFieldType::editingFinished, context, functor, Qt::AutoConnection); + return; + } + + //! I don't know \c QPlainTextEdit does not have an \c editingFinished signal + template + void connectFieldChanged(Derived * context, Functor functor) const + requires (std::same_as || + std::same_as) { + context->connect(this->editField, &EditFieldType::textChanged, context, functor, Qt::AutoConnection); + return; + } + + //! \c SmartLineEdit uses \c editingFinished itself, and subsequently emits \c textModified after text corrections + template + void connectFieldChanged(Derived * context, Functor functor) const + requires (std::same_as) { + context->connect(this->editField, &EditFieldType::textModified, context, functor, Qt::AutoConnection); + return; + } + + //! Combo boxes are slightly different + template + void connectFieldChanged(Derived * context, Functor functor) const + requires (std::same_as || + std::same_as) { + // QOverload is needed on next line because the signal currentIndexChanged is overloaded in QComboBox - see + // https://doc.qt.io/qt-5/qcombobox.html#currentIndexChanged + context->connect(this->editField, QOverload::of(&QComboBox::currentIndexChanged), context, functor, Qt::AutoConnection); + return; + } + + QVariant getFieldValue() const requires (std::same_as || + std::same_as) { + return this->editField->toPlainText(); + } + + QVariant getFieldValue() const requires (std::same_as) { + return this->editField->text(); + } + + QVariant getFieldValue() const requires (std::same_as || + std::same_as || + std::same_as) { + // Through the magic of templates, and naming conventions, one line suffices for all three types + return this->editField->getAsVariant(); + } + + /** + * \brief Set property on supplied object from edit field + */ + void setPropertyFromEditField(QObject & object) const { + // + // The only "special case" we can't handle with template specialisation is where we have a combo-box that is + // controlling the physical quantity for another field (eg whether an input field is mass or volume), there is + // nothing to do here (because the Amount returned from the controlled field holds the units that we need to pass + // to the object setter). + // + if (!this->hasControlledField) { + object.setProperty(*property, this->getFieldValue()); + } + return; + } + + void setEditFieldText(QString const & val) const requires (std::same_as || + std::same_as) { + this->editField->setPlainText(val); + return; + } + + void setEditFieldText(QString const & val) const requires (std::same_as || + std::same_as) { + this->editField->setText(val); + return; + } + + void setEditField(QVariant const & val) const requires (std::same_as || + std::same_as || + std::same_as) { + this->setEditFieldText(val.toString()); + return; + } + + void setEditField(QVariant const & val) const requires (std::same_as || + std::same_as || + std::same_as) { + this->editField->setFromVariant(val); + return; + } + + //! This clears the field, or sets it to the default value + void clearEditField() const requires (std::same_as || + std::same_as || + std::same_as) { + this->setEditFieldText(""); + return; + } + void clearEditField() const requires (std::same_as || + std::same_as || + std::same_as) { + this->editField->setDefault(); + return; + } + + /** + * \brief Set edit field from property on supplied object + */ + void setEditFieldFromProperty(QObject & object) const requires (std::same_as) { + // + // Similarly to setPropertyFromEditField, in the case of a combo-box that is controlling the physical quantity for + // another field, we want to initialise from that controlled field. + // + if (this->hasControlledField) { + this->editField->autoSetFromControlledField(); + } else { + this->setEditField(object.property(*property)); + } + return; + } + void setEditFieldFromProperty(QObject & object) const requires (!std::same_as) { + this->setEditField(object.property(*property)); + return; + } + +}; + +using EditorBaseFieldVariant = std::variant< + // Not all permutations are valid, hence why some are commented out + EditorBaseField, + EditorBaseField, + EditorBaseField, + EditorBaseField, + EditorBaseField, + EditorBaseField, + EditorBaseField, // This is for tabs such as tab_notes containing a single QTextEdit with no separate QLabel + EditorBaseField, +// EditorBaseField, +// EditorBaseField, + EditorBaseField, + EditorBaseField, + EditorBaseField +>; + +/** + * \brief These macros are similar to SMART_FIELD_INIT, but allows us to pass the EditorBaseField constructor. + * + * We assume that, where Foo is some subclass of NamedEntity, then the editor class for Foo is always called + * FooEditor. + * + * Note that we can't just write decltype(*label) because (as explained at + * https://stackoverflow.com/questions/34231547/decltype-a-dereferenced-pointer-in-c), *label is actually a + * reference, and we can't have a member of EditorBaseField be a pointer to a reference. Fortunately + * std::remove_pointer does what we want. + * + * \c EDITOR_FIELD_NORM should be used for most fields + * \c EDITOR_FIELD_ENUM is for a combo box for an enum + * \c EDITOR_FIELD_COPQ is for a combo box that controls the physical quantity (eg mass/volume) of another field + * + * To minimise errors, we try to keep the invocations of \c EDITOR_FIELD_NORM, \c EDITOR_FIELD_ENUM and + * \c EDITOR_FIELD_COPQ similar to each other -- within the limits of what we can do in macros. This pushes us + * to using \c NamedEntity::name etc rather than \c PropertyNames::NamedEntity::name for the fourth parameter, + * because in the _ENUM version, we're also going to use this to get the StringMapping and DisplayNames lookups + * via the naming conventions we use for those. (I did think about whether we should go beyond just a naming + * convention for those sets of look-ups, but it feels like it would be adding a reasonable amount of extra + * complexity for very small benefit.) + * + * NOTE that we cannot completely check the first parameter at compile-time. If you, eg, accidentally put + * \c Fermentation when you mean \c FermentationStep, the code will compile, but you will get an assert at + * run-time (when we try to look up a \c FermentationStep property type info on \c Fermentation). + */ +#define EDITOR_FIELD_BEGIN(modelClass, label, editField, property) \ + EditorBaseFieldVariant{ \ + EditorBaseField::type, std::remove_pointer::type>{\ + #modelClass "Editor", \ + #label, \ + #modelClass "Editor->" #label, \ + label, \ + #editField, \ + #modelClass "Editor->" #editField, \ + editField, \ + PropertyNames::property, \ + modelClass ::typeLookup.getType(PropertyNames::property) + +#define EDITOR_FIELD_END(...) \ + __VA_OPT__(, __VA_ARGS__) \ + } \ + } + +#define EDITOR_FIELD_NORM(modelClass, label, editField, property, ...) \ + EDITOR_FIELD_BEGIN(modelClass, label, editField, property) \ + EDITOR_FIELD_END(__VA_ARGS__) + +#define EDITOR_FIELD_ENUM(modelClass, label, editField, property, ...) \ + EDITOR_FIELD_BEGIN(modelClass, label, editField, property), \ + property##StringMapping, \ + property##DisplayNames \ + EDITOR_FIELD_END(__VA_ARGS__) + +#define EDITOR_FIELD_COPQ(modelClass, label, editField, property, controlledField, ...) \ + EDITOR_FIELD_BEGIN(modelClass, label, editField, property), \ + Measurement::physicalQuantityStringMapping, \ + Measurement::physicalQuantityDisplayNames, \ + &Measurement::allPossibilitiesAsInt(modelClass::validMeasures), \ + controlledField \ + EDITOR_FIELD_END(__VA_ARGS__) + +#endif diff --git a/src/editors/EditorWithRecipeBase.h b/src/editors/EditorWithRecipeBase.h deleted file mode 100644 index 5da6e9b6..00000000 --- a/src/editors/EditorWithRecipeBase.h +++ /dev/null @@ -1,119 +0,0 @@ -/*====================================================================================================================== - * editors/EditorWithRecipeBase.h is part of Brewken, and is copyright the following authors 2024: - * • Matt Young - * - * Brewken 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. - * - * Brewken 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 - * . - =====================================================================================================================*/ -#ifndef EDITORS_EDITORWITHRECIPEBASE_H -#define EDITORS_EDITORWITHRECIPEBASE_H -#pragma once - -#include "editors/EditorBase.h" -#include "model/Recipe.h" - -/** - * \brief Extends \c EditorBase for editors where it makes sense for us to be able to watch a \c Recipe, because a - * \c Recipe can have at most one of the thing we are editing (\c Boil, \c Equipment, \c Fermentation, \c Mash, - * \c Style). - * - * Classes deriving from this one have the same requirements as those deriving directly from \c EditorBase, - * except that: - * EDITOR_WITH_RECIPE_COMMON_DECL should be placed in the header file instead of EDITOR_COMMON_DECL - * EDITOR_WITH_RECIPE_COMMON_CODE should be placed in the .cpp file instead of EDITOR_COMMON_CODE - */ -template -class EditorWithRecipeBase : public EditorBase { -public: - EditorWithRecipeBase() : - EditorBase{}, - m_recipeObs{nullptr} { - return; - } - virtual ~EditorWithRecipeBase() = default; - - void setRecipe(Recipe * recipe) { - this->m_recipeObs = recipe; - // TBD: We could automatically set the edit item as follows: -// if (this->m_recipeObs) { -// this->m_editItem = this->m_recipeObs->get() -// } - return; - } - - /** - * \brief Override \c EditorBase::doChanged - */ - virtual void doChanged(QObject * sender, QMetaProperty prop, QVariant val) { - // Extra handling if sender is Recipe we are observing... - if (this->m_recipeObs && sender == this->m_recipeObs) { - this->readAllFields(); - return; - } - // ...otherwise we fall back to the base class handling - this->EditorBase::doChanged(sender, prop, val); - return; - } - - /** - * \brief - */ - void doShowEditor() { - this->readAllFields(); - this->derived().setVisible(true); - return; - } - - /** - * \brief - */ - void doCloseEditor() { - this->derived().setVisible(true); - return; - } - -protected: - - /** - * \brief The \c Recipe, if any, that we are "observing". - */ - Recipe * m_recipeObs; -}; - -/** - * \brief Derived classes should include this in their header file, right after Q_OBJECT, instead of EDITOR_COMMON_DECL - * (which this macro also pulls in). - * - * Note we have to be careful about comment formats in macro definitions - */ -#define EDITOR_WITH_RECIPE_COMMON_DECL(NeName) \ - EDITOR_COMMON_DECL(NeName) \ - \ - /* This allows EditorWithRecipeBase to call protected and private members of Derived */ \ - friend class EditorWithRecipeBase; \ - \ - public slots: \ - /* Additional standard slots for editors with recipe */ \ - void showEditor(); \ - void closeEditor(); \ - -/** - * \brief Derived classes should include this in their implementation file, usually at the end, instead of - * EDITOR_COMMON_CODE (which this macro also pulls in). - */ -#define EDITOR_WITH_RECIPE_COMMON_CODE(EditorName) \ - EDITOR_COMMON_CODE(EditorName) \ - void EditorName::showEditor() { this->doShowEditor(); return; } \ - void EditorName::closeEditor() { this->doCloseEditor(); return; } \ - - - -#endif diff --git a/src/editors/EquipmentEditor.cpp b/src/editors/EquipmentEditor.cpp index 2e8e21e0..7f5c7e5d 100644 --- a/src/editors/EquipmentEditor.cpp +++ b/src/editors/EquipmentEditor.cpp @@ -47,68 +47,67 @@ // user to grab this value (and that of other common materials if we can find them). // -EquipmentEditor::EquipmentEditor(QWidget* parent/*, bool singleEquipEditor*/) : +EquipmentEditor::EquipmentEditor(QWidget* parent, QString const editorName) : QDialog(parent), - EditorBase() { + EditorBase(editorName) { this->setupUi(this); - this->tabWidget_editor->tabBar()->setStyle(new BtHorizontalTabs); - - SMART_FIELD_INIT(EquipmentEditor, label_name , lineEdit_name , Equipment, PropertyNames::NamedEntity::name ); - SMART_FIELD_INIT(EquipmentEditor, label_mashTunSpecificHeat , lineEdit_mashTunSpecificHeat , Equipment, PropertyNames::Equipment::mashTunSpecificHeat_calGC ); - SMART_FIELD_INIT(EquipmentEditor, label_mashTunGrainAbsorption , lineEdit_mashTunGrainAbsorption , Equipment, PropertyNames::Equipment::mashTunGrainAbsorption_LKg ); - SMART_FIELD_INIT(EquipmentEditor, label_hopUtilization , lineEdit_hopUtilization , Equipment, PropertyNames::Equipment::hopUtilization_pct , 0); - SMART_FIELD_INIT(EquipmentEditor, label_mashTunWeight , lineEdit_mashTunWeight , Equipment, PropertyNames::Equipment::mashTunWeight_kg ); - SMART_FIELD_INIT(EquipmentEditor, label_boilingPoint , lineEdit_boilingPoint , Equipment, PropertyNames::Equipment::boilingPoint_c , 1); - SMART_FIELD_INIT(EquipmentEditor, label_boilTime , lineEdit_boilTime , Equipment, PropertyNames::Equipment::boilTime_min ); - SMART_FIELD_INIT(EquipmentEditor, label_fermenterBatchSize , lineEdit_fermenterBatchSize , Equipment, PropertyNames::Equipment::fermenterBatchSize_l ); - SMART_FIELD_INIT(EquipmentEditor, label_kettleBoilSize , lineEdit_kettleBoilSize , Equipment, PropertyNames::Equipment::kettleBoilSize_l ); - SMART_FIELD_INIT(EquipmentEditor, label_kettleEvaporationPerHour, lineEdit_kettleEvaporationPerHour, Equipment, PropertyNames::Equipment::kettleEvaporationPerHour_l ); - SMART_FIELD_INIT(EquipmentEditor, label_lauterTunDeadspaceLoss , lineEdit_lauterTunDeadspaceLoss , Equipment, PropertyNames::Equipment::lauterTunDeadspaceLoss_l ); - SMART_FIELD_INIT(EquipmentEditor, label_topUpKettle , lineEdit_topUpKettle , Equipment, PropertyNames::Equipment::topUpKettle_l ); - SMART_FIELD_INIT(EquipmentEditor, label_topUpWater , lineEdit_topUpWater , Equipment, PropertyNames::Equipment::topUpWater_l ); - SMART_FIELD_INIT(EquipmentEditor, label_kettleTrubChillerLoss , lineEdit_kettleTrubChillerLoss , Equipment, PropertyNames::Equipment::kettleTrubChillerLoss_l ); - SMART_FIELD_INIT(EquipmentEditor, label_mashTunVolume , lineEdit_mashTunVolume , Equipment, PropertyNames::Equipment::mashTunVolume_l ); - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - SMART_FIELD_INIT(EquipmentEditor, label_hltType , lineEdit_hltType , Equipment, PropertyNames::Equipment::hltType ); - SMART_FIELD_INIT(EquipmentEditor, label_mashTunType , lineEdit_mashTunType , Equipment, PropertyNames::Equipment::mashTunType ); - SMART_FIELD_INIT(EquipmentEditor, label_lauterTunType , lineEdit_lauterTunType , Equipment, PropertyNames::Equipment::lauterTunType ); - SMART_FIELD_INIT(EquipmentEditor, label_kettleType , lineEdit_kettleType , Equipment, PropertyNames::Equipment::kettleType ); - SMART_FIELD_INIT(EquipmentEditor, label_fermenterType , lineEdit_fermenterType , Equipment, PropertyNames::Equipment::fermenterType ); - SMART_FIELD_INIT(EquipmentEditor, label_agingVesselType , lineEdit_agingVesselType , Equipment, PropertyNames::Equipment::agingVesselType ); - SMART_FIELD_INIT(EquipmentEditor, label_packagingVesselType , lineEdit_packagingVesselType , Equipment, PropertyNames::Equipment::packagingVesselType ); - SMART_FIELD_INIT(EquipmentEditor, label_hltVolume , lineEdit_hltVolume , Equipment, PropertyNames::Equipment::hltVolume_l ); - SMART_FIELD_INIT(EquipmentEditor, label_lauterTunVolume , lineEdit_lauterTunVolume , Equipment, PropertyNames::Equipment::lauterTunVolume_l ); - SMART_FIELD_INIT(EquipmentEditor, label_agingVesselVolume , lineEdit_agingVesselVolume , Equipment, PropertyNames::Equipment::agingVesselVolume_l ); - SMART_FIELD_INIT(EquipmentEditor, label_packagingVesselVolume , lineEdit_packagingVesselVolume , Equipment, PropertyNames::Equipment::packagingVesselVolume_l ); - SMART_FIELD_INIT(EquipmentEditor, label_hltLoss , lineEdit_hltLoss , Equipment, PropertyNames::Equipment::hltLoss_l ); - SMART_FIELD_INIT(EquipmentEditor, label_mashTunLoss , lineEdit_mashTunLoss , Equipment, PropertyNames::Equipment::mashTunLoss_l ); - SMART_FIELD_INIT(EquipmentEditor, label_fermenterLoss , lineEdit_fermenterLoss , Equipment, PropertyNames::Equipment::fermenterLoss_l ); - SMART_FIELD_INIT(EquipmentEditor, label_agingVesselLoss , lineEdit_agingVesselLoss , Equipment, PropertyNames::Equipment::agingVesselLoss_l ); - SMART_FIELD_INIT(EquipmentEditor, label_packagingVesselLoss , lineEdit_packagingVesselLoss , Equipment, PropertyNames::Equipment::packagingVesselLoss_l ); - SMART_FIELD_INIT(EquipmentEditor, label_kettleOutflowPerMinute , lineEdit_kettleOutflowPerMinute , Equipment, PropertyNames::Equipment::kettleOutflowPerMinute_l ); - SMART_FIELD_INIT(EquipmentEditor, label_hltWeight , lineEdit_hltWeight , Equipment, PropertyNames::Equipment::hltWeight_kg ); - SMART_FIELD_INIT(EquipmentEditor, label_lauterTunWeight , lineEdit_lauterTunWeight , Equipment, PropertyNames::Equipment::lauterTunWeight_kg ); - SMART_FIELD_INIT(EquipmentEditor, label_kettleWeight , lineEdit_kettleWeight , Equipment, PropertyNames::Equipment::kettleWeight_kg ); - SMART_FIELD_INIT(EquipmentEditor, label_hltSpecificHeat , lineEdit_hltSpecificHeat , Equipment, PropertyNames::Equipment::hltSpecificHeat_calGC ); - SMART_FIELD_INIT(EquipmentEditor, label_lauterTunSpecificHeat , lineEdit_lauterTunSpecificHeat , Equipment, PropertyNames::Equipment::lauterTunSpecificHeat_calGC); - SMART_FIELD_INIT(EquipmentEditor, label_kettleSpecificHeat , lineEdit_kettleSpecificHeat , Equipment, PropertyNames::Equipment::kettleSpecificHeat_calGC ); + this->postSetupUiInit({ + EDITOR_FIELD_NORM(Equipment, label_name , lineEdit_name , NamedEntity::name ), + EDITOR_FIELD_NORM(Equipment, label_mashTunSpecificHeat , lineEdit_mashTunSpecificHeat , Equipment::mashTunSpecificHeat_calGC ), + EDITOR_FIELD_NORM(Equipment, label_mashTunGrainAbsorption , lineEdit_mashTunGrainAbsorption , Equipment::mashTunGrainAbsorption_LKg ), + EDITOR_FIELD_NORM(Equipment, label_hopUtilization , lineEdit_hopUtilization , Equipment::hopUtilization_pct , 0), + EDITOR_FIELD_NORM(Equipment, label_mashTunWeight , lineEdit_mashTunWeight , Equipment::mashTunWeight_kg ), + EDITOR_FIELD_NORM(Equipment, label_boilingPoint , lineEdit_boilingPoint , Equipment::boilingPoint_c , 1), + EDITOR_FIELD_NORM(Equipment, label_boilTime , lineEdit_boilTime , Equipment::boilTime_min ), + EDITOR_FIELD_NORM(Equipment, label_fermenterBatchSize , lineEdit_fermenterBatchSize , Equipment::fermenterBatchSize_l ), + EDITOR_FIELD_NORM(Equipment, label_kettleBoilSize , lineEdit_kettleBoilSize , Equipment::kettleBoilSize_l ), + EDITOR_FIELD_NORM(Equipment, label_kettleEvaporationPerHour, lineEdit_kettleEvaporationPerHour, Equipment::kettleEvaporationPerHour_l ), + EDITOR_FIELD_NORM(Equipment, label_lauterTunDeadspaceLoss , lineEdit_lauterTunDeadspaceLoss , Equipment::lauterTunDeadspaceLoss_l ), + EDITOR_FIELD_NORM(Equipment, label_topUpKettle , lineEdit_topUpKettle , Equipment::topUpKettle_l ), + EDITOR_FIELD_NORM(Equipment, label_topUpWater , lineEdit_topUpWater , Equipment::topUpWater_l ), + EDITOR_FIELD_NORM(Equipment, label_kettleTrubChillerLoss , lineEdit_kettleTrubChillerLoss , Equipment::kettleTrubChillerLoss_l ), + EDITOR_FIELD_NORM(Equipment, label_mashTunVolume , lineEdit_mashTunVolume , Equipment::mashTunVolume_l ), + // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ + EDITOR_FIELD_NORM(Equipment, label_hltType , lineEdit_hltType , Equipment::hltType ), + EDITOR_FIELD_NORM(Equipment, label_mashTunType , lineEdit_mashTunType , Equipment::mashTunType ), + EDITOR_FIELD_NORM(Equipment, label_lauterTunType , lineEdit_lauterTunType , Equipment::lauterTunType ), + EDITOR_FIELD_NORM(Equipment, label_kettleType , lineEdit_kettleType , Equipment::kettleType ), + EDITOR_FIELD_NORM(Equipment, label_fermenterType , lineEdit_fermenterType , Equipment::fermenterType ), + EDITOR_FIELD_NORM(Equipment, label_agingVesselType , lineEdit_agingVesselType , Equipment::agingVesselType ), + EDITOR_FIELD_NORM(Equipment, label_packagingVesselType , lineEdit_packagingVesselType , Equipment::packagingVesselType ), + EDITOR_FIELD_NORM(Equipment, label_hltVolume , lineEdit_hltVolume , Equipment::hltVolume_l ), + EDITOR_FIELD_NORM(Equipment, label_lauterTunVolume , lineEdit_lauterTunVolume , Equipment::lauterTunVolume_l ), + EDITOR_FIELD_NORM(Equipment, label_agingVesselVolume , lineEdit_agingVesselVolume , Equipment::agingVesselVolume_l ), + EDITOR_FIELD_NORM(Equipment, label_packagingVesselVolume , lineEdit_packagingVesselVolume , Equipment::packagingVesselVolume_l ), + EDITOR_FIELD_NORM(Equipment, label_hltLoss , lineEdit_hltLoss , Equipment::hltLoss_l ), + EDITOR_FIELD_NORM(Equipment, label_mashTunLoss , lineEdit_mashTunLoss , Equipment::mashTunLoss_l ), + EDITOR_FIELD_NORM(Equipment, label_fermenterLoss , lineEdit_fermenterLoss , Equipment::fermenterLoss_l ), + EDITOR_FIELD_NORM(Equipment, label_agingVesselLoss , lineEdit_agingVesselLoss , Equipment::agingVesselLoss_l ), + EDITOR_FIELD_NORM(Equipment, label_packagingVesselLoss , lineEdit_packagingVesselLoss , Equipment::packagingVesselLoss_l ), + EDITOR_FIELD_NORM(Equipment, label_kettleOutflowPerMinute , lineEdit_kettleOutflowPerMinute , Equipment::kettleOutflowPerMinute_l ), + EDITOR_FIELD_NORM(Equipment, label_hltWeight , lineEdit_hltWeight , Equipment::hltWeight_kg ), + EDITOR_FIELD_NORM(Equipment, label_lauterTunWeight , lineEdit_lauterTunWeight , Equipment::lauterTunWeight_kg ), + EDITOR_FIELD_NORM(Equipment, label_kettleWeight , lineEdit_kettleWeight , Equipment::kettleWeight_kg ), + EDITOR_FIELD_NORM(Equipment, label_hltSpecificHeat , lineEdit_hltSpecificHeat , Equipment::hltSpecificHeat_calGC ), + EDITOR_FIELD_NORM(Equipment, label_lauterTunSpecificHeat , lineEdit_lauterTunSpecificHeat , Equipment::lauterTunSpecificHeat_calGC), + EDITOR_FIELD_NORM(Equipment, label_kettleSpecificHeat , lineEdit_kettleSpecificHeat , Equipment::kettleSpecificHeat_calGC ), + }); // Connect all the boxen - connect(this->checkBox_showHlt , &QCheckBox::stateChanged , this, &EquipmentEditor::hideOrShowOptionalVessels ); - connect(this->checkBox_showLauterTun , &QCheckBox::stateChanged , this, &EquipmentEditor::hideOrShowOptionalVessels ); - connect(this->checkBox_showAgingVessel , &QCheckBox::stateChanged , this, &EquipmentEditor::hideOrShowOptionalVessels ); - connect(this->checkBox_showPackagingVessel, &QCheckBox::stateChanged , this, &EquipmentEditor::hideOrShowOptionalVessels ); - connect(this->checkBox_defaultEquipment , &QCheckBox::stateChanged , this, &EquipmentEditor::updateDefaultEquipment); - connect(this->checkBox_calcBoilVolume , &QCheckBox::stateChanged , this, &EquipmentEditor::updateCalcBoilVolume ); - connect(lineEdit_boilTime , &SmartLineEdit::textModified, this, &EquipmentEditor::updateCalcBoilVolume ); - connect(lineEdit_kettleEvaporationPerHour , &SmartLineEdit::textModified, this, &EquipmentEditor::updateCalcBoilVolume ); - connect(lineEdit_topUpWater , &SmartLineEdit::textModified, this, &EquipmentEditor::updateCalcBoilVolume ); - connect(lineEdit_kettleTrubChillerLoss , &SmartLineEdit::textModified, this, &EquipmentEditor::updateCalcBoilVolume ); - connect(lineEdit_fermenterBatchSize , &SmartLineEdit::textModified, this, &EquipmentEditor::updateCalcBoilVolume ); - connect(pushButton_absorption , &QAbstractButton::clicked , this, &EquipmentEditor::resetAbsorption ); - - this->connectSignalsAndSlots(); + connect(this->checkBox_showHlt , &QCheckBox::stateChanged , this, &EquipmentEditor::hideOrShowOptionalVessels); + connect(this->checkBox_showLauterTun , &QCheckBox::stateChanged , this, &EquipmentEditor::hideOrShowOptionalVessels); + connect(this->checkBox_showAgingVessel , &QCheckBox::stateChanged , this, &EquipmentEditor::hideOrShowOptionalVessels); + connect(this->checkBox_showPackagingVessel , &QCheckBox::stateChanged , this, &EquipmentEditor::hideOrShowOptionalVessels); + connect(this->checkBox_defaultEquipment , &QCheckBox::stateChanged , this, &EquipmentEditor::updateDefaultEquipment ); + connect(this->checkBox_calcBoilVolume , &QCheckBox::stateChanged , this, &EquipmentEditor::updateCalcBoilVolume ); + connect(this->lineEdit_boilTime , &SmartLineEdit::textModified, this, &EquipmentEditor::updateCalcBoilVolume ); + connect(this->lineEdit_kettleEvaporationPerHour , &SmartLineEdit::textModified, this, &EquipmentEditor::updateCalcBoilVolume ); + connect(this->lineEdit_topUpWater , &SmartLineEdit::textModified, this, &EquipmentEditor::updateCalcBoilVolume ); + connect(this->lineEdit_kettleTrubChillerLoss , &SmartLineEdit::textModified, this, &EquipmentEditor::updateCalcBoilVolume ); + connect(this->lineEdit_fermenterBatchSize , &SmartLineEdit::textModified, this, &EquipmentEditor::updateCalcBoilVolume ); + connect(this->pushButton_absorption , &QAbstractButton::clicked , this, &EquipmentEditor::resetAbsorption ); + return; } @@ -226,119 +225,14 @@ bool EquipmentEditor::validateBeforeSave() { } -void EquipmentEditor::writeFieldsToEditItem() { - - m_editItem->setName (lineEdit_name ->text ()); - m_editItem->setKettleBoilSize_l (lineEdit_kettleBoilSize ->getNonOptCanonicalQty()); - m_editItem->setFermenterBatchSize_l (lineEdit_fermenterBatchSize ->getNonOptCanonicalQty()); - m_editItem->setMashTunVolume_l (lineEdit_mashTunVolume ->getNonOptCanonicalQty()); - m_editItem->setMashTunWeight_kg (lineEdit_mashTunWeight ->getOptCanonicalQty ()); - m_editItem->setMashTunSpecificHeat_calGC (lineEdit_mashTunSpecificHeat ->getOptCanonicalQty ()); - m_editItem->setBoilTime_min (lineEdit_boilTime ->getOptCanonicalQty ()); - m_editItem->setKettleEvaporationPerHour_l (lineEdit_kettleEvaporationPerHour->getOptCanonicalQty ()); - m_editItem->setTopUpKettle_l (lineEdit_topUpKettle ->getOptCanonicalQty ()); - m_editItem->setTopUpWater_l (lineEdit_topUpWater ->getOptCanonicalQty ()); - m_editItem->setKettleTrubChillerLoss_l (lineEdit_kettleTrubChillerLoss ->getNonOptCanonicalQty()); - m_editItem->setLauterTunDeadspaceLoss_l (lineEdit_lauterTunDeadspaceLoss ->getNonOptCanonicalQty()); - m_editItem->setMashTunGrainAbsorption_LKg (lineEdit_mashTunGrainAbsorption ->getOptCanonicalQty ()); - m_editItem->setBoilingPoint_c (lineEdit_boilingPoint ->getNonOptCanonicalQty()); - m_editItem->setHopUtilization_pct (lineEdit_hopUtilization ->getOptValue ()); - m_editItem->setKettleNotes (textEdit_kettleNotes ->toPlainText ()); - m_editItem->setCalcBoilVolume (checkBox_calcBoilVolume ->isChecked ()); - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - m_editItem->setHltType (lineEdit_hltType ->text ()); - m_editItem->setMashTunType (lineEdit_mashTunType ->text ()); - m_editItem->setLauterTunType (lineEdit_lauterTunType ->text ()); - m_editItem->setKettleType (lineEdit_kettleType ->text ()); - m_editItem->setFermenterType (lineEdit_fermenterType ->text ()); - m_editItem->setAgingVesselType (lineEdit_agingVesselType ->text ()); - m_editItem->setPackagingVesselType (lineEdit_packagingVesselType ->text ()); - m_editItem->setHltVolume_l (lineEdit_hltVolume ->getNonOptCanonicalQty()); - m_editItem->setLauterTunVolume_l (lineEdit_lauterTunVolume ->getNonOptCanonicalQty()); - m_editItem->setAgingVesselVolume_l (lineEdit_agingVesselVolume ->getNonOptCanonicalQty()); - m_editItem->setPackagingVesselVolume_l (lineEdit_packagingVesselVolume ->getNonOptCanonicalQty()); - m_editItem->setHltLoss_l (lineEdit_hltLoss ->getNonOptCanonicalQty()); - m_editItem->setMashTunLoss_l (lineEdit_mashTunLoss ->getNonOptCanonicalQty()); - m_editItem->setFermenterLoss_l (lineEdit_fermenterLoss ->getNonOptCanonicalQty()); - m_editItem->setAgingVesselLoss_l (lineEdit_agingVesselLoss ->getNonOptCanonicalQty()); - m_editItem->setPackagingVesselLoss_l (lineEdit_packagingVesselLoss ->getNonOptCanonicalQty()); - m_editItem->setKettleOutflowPerMinute_l (lineEdit_kettleOutflowPerMinute ->getOptCanonicalQty ()); - m_editItem->setHltWeight_kg (lineEdit_hltWeight ->getOptCanonicalQty ()); - m_editItem->setLauterTunWeight_kg (lineEdit_lauterTunWeight ->getOptCanonicalQty ()); - m_editItem->setKettleWeight_kg (lineEdit_kettleWeight ->getOptCanonicalQty ()); - m_editItem->setHltSpecificHeat_calGC (lineEdit_hltSpecificHeat ->getOptCanonicalQty ()); - m_editItem->setLauterTunSpecificHeat_calGC(lineEdit_lauterTunSpecificHeat ->getOptCanonicalQty ()); - m_editItem->setKettleSpecificHeat_calGC (lineEdit_kettleSpecificHeat ->getOptCanonicalQty ()); - m_editItem->setHltNotes (textEdit_hltNotes ->toPlainText ()); - m_editItem->setMashTunNotes (textEdit_mashTunNotes ->toPlainText ()); - m_editItem->setLauterTunNotes (textEdit_lauterTunNotes ->toPlainText ()); - m_editItem->setFermenterNotes (textEdit_fermenterNotes ->toPlainText ()); - m_editItem->setAgingVesselNotes (textEdit_agingVesselNotes ->toPlainText ()); - m_editItem->setPackagingVesselNotes (textEdit_packagingVesselNotes ->toPlainText ()); - - return; -} - -void EquipmentEditor::writeLateFieldsToEditItem() { - // Nothing to do here for Equipment - return; -} - -void EquipmentEditor::readFieldsFromEditItem(std::optional propName) { - - if (!propName || *propName == PropertyNames::NamedEntity::name ) { this->lineEdit_name ->setTextCursor(m_editItem->name ()); // Continues to next line - /* this->tabWidget_editor->setTabText(0, m_editItem->name()); */ if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::kettleBoilSize_l ) { this->lineEdit_kettleBoilSize ->setQuantity (m_editItem->kettleBoilSize_l ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::fermenterBatchSize_l ) { this->lineEdit_fermenterBatchSize ->setQuantity (m_editItem->fermenterBatchSize_l ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::mashTunVolume_l ) { this->lineEdit_mashTunVolume ->setQuantity (m_editItem->mashTunVolume_l ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::mashTunWeight_kg ) { this->lineEdit_mashTunWeight ->setQuantity (m_editItem->mashTunWeight_kg ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::mashTunSpecificHeat_calGC ) { this->lineEdit_mashTunSpecificHeat ->setQuantity (m_editItem->mashTunSpecificHeat_calGC ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::boilTime_min ) { this->lineEdit_boilTime ->setQuantity (m_editItem->boilTime_min ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::kettleEvaporationPerHour_l ) { this->lineEdit_kettleEvaporationPerHour->setQuantity (m_editItem->kettleEvaporationPerHour_l ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::topUpKettle_l ) { this->lineEdit_topUpKettle ->setQuantity (m_editItem->topUpKettle_l ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::topUpWater_l ) { this->lineEdit_topUpWater ->setQuantity (m_editItem->topUpWater_l ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::kettleTrubChillerLoss_l ) { this->lineEdit_kettleTrubChillerLoss ->setQuantity (m_editItem->kettleTrubChillerLoss_l ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::lauterTunDeadspaceLoss_l ) { this->lineEdit_lauterTunDeadspaceLoss ->setQuantity (m_editItem->lauterTunDeadspaceLoss_l ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::kettleNotes ) { this->textEdit_kettleNotes ->setText (m_editItem->kettleNotes ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::mashTunGrainAbsorption_LKg ) { this->lineEdit_mashTunGrainAbsorption ->setQuantity (m_editItem->mashTunGrainAbsorption_LKg ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::boilingPoint_c ) { this->lineEdit_boilingPoint ->setQuantity (m_editItem->boilingPoint_c ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::hopUtilization_pct ) { this->lineEdit_hopUtilization ->setQuantity (m_editItem->hopUtilization_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::calcBoilVolume ) { this->checkBox_calcBoilVolume ->setChecked (m_editItem->calcBoilVolume ()); if (propName) { return; } } - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - if (!propName || *propName == PropertyNames::Equipment::hltType ) { this->lineEdit_hltType ->setTextCursor(m_editItem->hltType ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::mashTunType ) { this->lineEdit_mashTunType ->setTextCursor(m_editItem->mashTunType ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::lauterTunType ) { this->lineEdit_lauterTunType ->setTextCursor(m_editItem->lauterTunType ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::kettleType ) { this->lineEdit_kettleType ->setTextCursor(m_editItem->kettleType ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::fermenterType ) { this->lineEdit_fermenterType ->setTextCursor(m_editItem->fermenterType ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::agingVesselType ) { this->lineEdit_agingVesselType ->setTextCursor(m_editItem->agingVesselType ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::packagingVesselType ) { this->lineEdit_packagingVesselType ->setTextCursor(m_editItem->packagingVesselType ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::hltVolume_l ) { this->lineEdit_hltVolume ->setQuantity (m_editItem->hltVolume_l ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::lauterTunVolume_l ) { this->lineEdit_lauterTunVolume ->setQuantity (m_editItem->lauterTunVolume_l ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::agingVesselVolume_l ) { this->lineEdit_agingVesselVolume ->setQuantity (m_editItem->agingVesselVolume_l ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::packagingVesselVolume_l ) { this->lineEdit_packagingVesselVolume ->setQuantity (m_editItem->packagingVesselVolume_l ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::hltLoss_l ) { this->lineEdit_hltLoss ->setQuantity (m_editItem->hltLoss_l ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::mashTunLoss_l ) { this->lineEdit_mashTunLoss ->setQuantity (m_editItem->mashTunLoss_l ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::fermenterLoss_l ) { this->lineEdit_fermenterLoss ->setQuantity (m_editItem->fermenterLoss_l ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::agingVesselLoss_l ) { this->lineEdit_agingVesselLoss ->setQuantity (m_editItem->agingVesselLoss_l ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::packagingVesselLoss_l ) { this->lineEdit_packagingVesselLoss ->setQuantity (m_editItem->packagingVesselLoss_l ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::kettleOutflowPerMinute_l ) { this->lineEdit_kettleOutflowPerMinute ->setQuantity (m_editItem->kettleOutflowPerMinute_l ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::hltWeight_kg ) { this->lineEdit_hltWeight ->setQuantity (m_editItem->hltWeight_kg ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::lauterTunWeight_kg ) { this->lineEdit_lauterTunWeight ->setQuantity (m_editItem->lauterTunWeight_kg ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::kettleWeight_kg ) { this->lineEdit_kettleWeight ->setQuantity (m_editItem->kettleWeight_kg ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::hltSpecificHeat_calGC ) { this->lineEdit_hltSpecificHeat ->setQuantity (m_editItem->hltSpecificHeat_calGC ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::lauterTunSpecificHeat_calGC) { this->lineEdit_lauterTunSpecificHeat ->setQuantity (m_editItem->lauterTunSpecificHeat_calGC()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::kettleSpecificHeat_calGC ) { this->lineEdit_kettleSpecificHeat ->setQuantity (m_editItem->kettleSpecificHeat_calGC ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::hltNotes ) { this->textEdit_hltNotes ->setText (m_editItem->hltNotes ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::mashTunNotes ) { this->textEdit_mashTunNotes ->setText (m_editItem->mashTunNotes ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::lauterTunNotes ) { this->textEdit_lauterTunNotes ->setText (m_editItem->lauterTunNotes ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::fermenterNotes ) { this->textEdit_fermenterNotes ->setText (m_editItem->fermenterNotes ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::agingVesselNotes ) { this->textEdit_agingVesselNotes ->setText (m_editItem->agingVesselNotes ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Equipment::packagingVesselNotes ) { this->textEdit_packagingVesselNotes ->setText (m_editItem->packagingVesselNotes ()); if (propName) { return; } } +void EquipmentEditor::postReadFieldsFromEditItem([[maybe_unused]] std::optional propName) { + // These aren't fields we store in the DB, rather they control which bits of the editor are visible this->checkBox_showHlt ->setChecked(m_editItem->hltVolume_l () != 0); this->checkBox_showLauterTun ->setChecked(m_editItem->lauterTunVolume_l () != 0); this->checkBox_showAgingVessel ->setChecked(m_editItem->agingVesselVolume_l () != 0); this->checkBox_showPackagingVessel->setChecked(m_editItem->packagingVesselVolume_l() != 0); + this->hideOrShowOptionalVessels(); this->checkBox_defaultEquipment->blockSignals(true); @@ -418,4 +312,4 @@ void EquipmentEditor::updateDefaultEquipment() { } // Insert the boiler-plate stuff that we cannot do in EditorBase -EDITOR_COMMON_CODE(EquipmentEditor) +EDITOR_COMMON_CODE(Equipment) diff --git a/src/editors/EquipmentEditor.h b/src/editors/EquipmentEditor.h index a0a682d8..05d98c46 100644 --- a/src/editors/EquipmentEditor.h +++ b/src/editors/EquipmentEditor.h @@ -30,6 +30,7 @@ #include "editors/EditorBase.h" #include "model/Equipment.h" +#define EquipmentEditorOptions EditorBaseOptions{ } /*! * \class EquipmentEditor * @@ -38,10 +39,12 @@ * See comment on EditorBase::connectSignalsAndSlots for why we need to have \c public, not \c private * inheritance from the Ui base. */ -class EquipmentEditor : public QDialog, public Ui::equipmentEditor, public EditorBase { +class EquipmentEditor : public QDialog, + public Ui::equipmentEditor, + public EditorBase { Q_OBJECT - EDITOR_COMMON_DECL(Equipment) + EDITOR_COMMON_DECL(Equipment, EquipmentEditorOptions) public slots: void hideOrShowOptionalVessels(); @@ -53,8 +56,8 @@ public slots: bool validateBeforeSave(); private: + void postReadFieldsFromEditItem(std::optional propName); double calcBatchSize(); - }; #endif diff --git a/src/editors/FermentableEditor.cpp b/src/editors/FermentableEditor.cpp index 28c90489..6965bb0c 100644 --- a/src/editors/FermentableEditor.cpp +++ b/src/editors/FermentableEditor.cpp @@ -24,166 +24,63 @@ #include #include -#include "BtHorizontalTabs.h" #include "database/ObjectStoreWrapper.h" #include "measurement/Unit.h" // TODO: Need a separate editor for inventory -FermentableEditor::FermentableEditor(QWidget* parent) : +FermentableEditor::FermentableEditor(QWidget* parent, QString const editorName) : QDialog(parent), - EditorBase() { + EditorBase(editorName) { setupUi(this); - - this->tabWidget_editor->tabBar()->setStyle(new BtHorizontalTabs); - - SMART_FIELD_INIT(FermentableEditor, label_name , lineEdit_name , Fermentable, PropertyNames::NamedEntity::name ); - SMART_FIELD_INIT(FermentableEditor, label_color , lineEdit_color , Fermentable, PropertyNames::Fermentable::color_srm , 0); - SMART_FIELD_INIT(FermentableEditor, label_diastaticPower, lineEdit_diastaticPower, Fermentable, PropertyNames::Fermentable::diastaticPower_lintner ); - SMART_FIELD_INIT(FermentableEditor, label_coarseFineDiff, lineEdit_coarseFineDiff, Fermentable, PropertyNames::Fermentable::coarseFineDiff_pct , 0); - SMART_FIELD_INIT(FermentableEditor, label_ibuGalPerLb , lineEdit_ibuGalPerLb , Fermentable, PropertyNames::Fermentable::ibuGalPerLb , 0); - SMART_FIELD_INIT(FermentableEditor, label_maxInBatch , lineEdit_maxInBatch , Fermentable, PropertyNames::Fermentable::maxInBatch_pct , 0); - SMART_FIELD_INIT(FermentableEditor, label_moisture , lineEdit_moisture , Fermentable, PropertyNames::Fermentable::moisture_pct , 0); - SMART_FIELD_INIT(FermentableEditor, label_protein , lineEdit_protein , Fermentable, PropertyNames::Fermentable::protein_pct , 0); - SMART_FIELD_INIT(FermentableEditor, label_inventory , lineEdit_inventory , Fermentable, PropertyNames::Ingredient::totalInventory , 1); - SMART_FIELD_INIT(FermentableEditor, label_origin , lineEdit_origin , Fermentable, PropertyNames::Fermentable::origin ); - SMART_FIELD_INIT(FermentableEditor, label_supplier , lineEdit_supplier , Fermentable, PropertyNames::Fermentable::supplier ); - - BT_COMBO_BOX_INIT(FermentableEditor, comboBox_type , Fermentable, type ); - - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - BT_COMBO_BOX_INIT(FermentableEditor, comboBox_grainGroup, Fermentable, grainGroup); - - SMART_FIELD_INIT(FermentableEditor, label_producer , lineEdit_producer , Fermentable, PropertyNames::Fermentable::producer ); - SMART_FIELD_INIT(FermentableEditor, label_productId , lineEdit_productId , Fermentable, PropertyNames::Fermentable::productId ); - SMART_FIELD_INIT(FermentableEditor, label_fineGrindYield_pct , lineEdit_fineGrindYield_pct , Fermentable, PropertyNames::Fermentable::fineGrindYield_pct , 1); - SMART_FIELD_INIT(FermentableEditor, label_coarseGrindYield_pct , lineEdit_coarseGrindYield_pct , Fermentable, PropertyNames::Fermentable::coarseGrindYield_pct , 1); - SMART_FIELD_INIT(FermentableEditor, label_potentialYield_sg , lineEdit_potentialYield_sg , Fermentable, PropertyNames::Fermentable::potentialYield_sg ); - SMART_FIELD_INIT(FermentableEditor, label_alphaAmylase_dextUnits, lineEdit_alphaAmylase_dextUnits, Fermentable, PropertyNames::Fermentable::alphaAmylase_dextUnits ); - SMART_FIELD_INIT(FermentableEditor, label_kolbachIndex_pct , lineEdit_kolbachIndex_pct , Fermentable, PropertyNames::Fermentable::kolbachIndex_pct , 1); - SMART_FIELD_INIT(FermentableEditor, label_hardnessPrpGlassy_pct , lineEdit_hardnessPrpGlassy_pct , Fermentable, PropertyNames::Fermentable::hardnessPrpGlassy_pct , 1); - SMART_FIELD_INIT(FermentableEditor, label_hardnessPrpHalf_pct , lineEdit_hardnessPrpHalf_pct , Fermentable, PropertyNames::Fermentable::hardnessPrpHalf_pct , 1); - SMART_FIELD_INIT(FermentableEditor, label_hardnessPrpMealy_pct , lineEdit_hardnessPrpMealy_pct , Fermentable, PropertyNames::Fermentable::hardnessPrpMealy_pct , 1); - SMART_FIELD_INIT(FermentableEditor, label_kernelSizePrpPlump_pct, lineEdit_kernelSizePrpPlump_pct, Fermentable, PropertyNames::Fermentable::kernelSizePrpPlump_pct, 1); - SMART_FIELD_INIT(FermentableEditor, label_kernelSizePrpThin_pct , lineEdit_kernelSizePrpThin_pct , Fermentable, PropertyNames::Fermentable::kernelSizePrpThin_pct , 1); - SMART_FIELD_INIT(FermentableEditor, label_friability_pct , lineEdit_friability_pct , Fermentable, PropertyNames::Fermentable::friability_pct , 1); - SMART_FIELD_INIT(FermentableEditor, label_di_ph , lineEdit_di_ph , Fermentable, PropertyNames::Fermentable::di_ph , 1); - SMART_FIELD_INIT(FermentableEditor, label_viscosity_cP , lineEdit_viscosity_cP , Fermentable, PropertyNames::Fermentable::viscosity_cP ); - SMART_FIELD_INIT(FermentableEditor, label_dmsP , lineEdit_dmsP , Fermentable, PropertyNames::Fermentable::dmsP_ppm , 1); - SMART_FIELD_INIT(FermentableEditor, label_fan , lineEdit_fan , Fermentable, PropertyNames::Fermentable::fan_ppm , 1); - SMART_FIELD_INIT(FermentableEditor, label_fermentability_pct , lineEdit_fermentability_pct , Fermentable, PropertyNames::Fermentable::fermentability_pct , 1); - SMART_FIELD_INIT(FermentableEditor, label_betaGlucan , lineEdit_betaGlucan , Fermentable, PropertyNames::Fermentable::betaGlucan_ppm , 1); - -/// SMART_CHECK_BOX_INIT(FermentableEditor, checkBox_amountIsWeight , label_amountIsWeight , lineEdit_inventory , Fermentable, amountIsWeight ); - - BT_COMBO_BOX_INIT_COPQ(FermentableEditor, comboBox_amountType, Fermentable, PropertyNames::Ingredient::totalInventory, lineEdit_inventory); - - this->connectSignalsAndSlots(); + this->postSetupUiInit({ + // + // Write inventory late to make sure we've the row in the inventory table (because total inventory amount isn't + // really an attribute of the Fermentable). + // + // Note that we do not need to store the value of comboBox_amountType. It merely controls the available unit for + // lineEdit_inventory + // + EDITOR_FIELD_NORM(Fermentable, label_name , lineEdit_name , NamedEntity::name ), + EDITOR_FIELD_NORM(Fermentable, tab_notes , textEdit_notes , Fermentable::notes ), + EDITOR_FIELD_NORM(Fermentable, label_color , lineEdit_color , Fermentable::color_srm , 0), + EDITOR_FIELD_NORM(Fermentable, label_diastaticPower , lineEdit_diastaticPower , Fermentable::diastaticPower_lintner ), + EDITOR_FIELD_NORM(Fermentable, label_coarseFineDiff , lineEdit_coarseFineDiff , Fermentable::coarseFineDiff_pct , 0), + EDITOR_FIELD_NORM(Fermentable, label_ibuGalPerLb , lineEdit_ibuGalPerLb , Fermentable::ibuGalPerLb , 0), + EDITOR_FIELD_NORM(Fermentable, label_maxInBatch , lineEdit_maxInBatch , Fermentable::maxInBatch_pct , 0), + EDITOR_FIELD_NORM(Fermentable, label_moisture , lineEdit_moisture , Fermentable::moisture_pct , 0), + EDITOR_FIELD_NORM(Fermentable, label_protein , lineEdit_protein , Fermentable::protein_pct , 0), + EDITOR_FIELD_NORM(Fermentable, label_inventory , lineEdit_inventory , Ingredient::totalInventory , 1, WhenToWriteField::Late), + EDITOR_FIELD_COPQ(Fermentable, label_amountType , comboBox_amountType , Ingredient::totalInventory, lineEdit_inventory, WhenToWriteField::Never), + EDITOR_FIELD_NORM(Fermentable, label_origin , lineEdit_origin , Fermentable::origin ), + EDITOR_FIELD_NORM(Fermentable, label_supplier , lineEdit_supplier , Fermentable::supplier ), + EDITOR_FIELD_ENUM(Fermentable, label_fermentableType , comboBox_type , Fermentable::type ), + // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ + EDITOR_FIELD_ENUM(Fermentable, label_grainGroup , comboBox_grainGroup , Fermentable::grainGroup ), + EDITOR_FIELD_NORM(Fermentable, label_producer , lineEdit_producer , Fermentable::producer ), + EDITOR_FIELD_NORM(Fermentable, label_productId , lineEdit_productId , Fermentable::productId ), + EDITOR_FIELD_NORM(Fermentable, label_fineGrindYield_pct , lineEdit_fineGrindYield_pct , Fermentable::fineGrindYield_pct , 1), + EDITOR_FIELD_NORM(Fermentable, label_coarseGrindYield_pct , lineEdit_coarseGrindYield_pct , Fermentable::coarseGrindYield_pct , 1), + EDITOR_FIELD_NORM(Fermentable, label_potentialYield_sg , lineEdit_potentialYield_sg , Fermentable::potentialYield_sg ), + EDITOR_FIELD_NORM(Fermentable, label_alphaAmylase_dextUnits, lineEdit_alphaAmylase_dextUnits, Fermentable::alphaAmylase_dextUnits ), + EDITOR_FIELD_NORM(Fermentable, label_kolbachIndex_pct , lineEdit_kolbachIndex_pct , Fermentable::kolbachIndex_pct , 1), + EDITOR_FIELD_NORM(Fermentable, label_hardnessPrpGlassy_pct , lineEdit_hardnessPrpGlassy_pct , Fermentable::hardnessPrpGlassy_pct , 1), + EDITOR_FIELD_NORM(Fermentable, label_hardnessPrpHalf_pct , lineEdit_hardnessPrpHalf_pct , Fermentable::hardnessPrpHalf_pct , 1), + EDITOR_FIELD_NORM(Fermentable, label_hardnessPrpMealy_pct , lineEdit_hardnessPrpMealy_pct , Fermentable::hardnessPrpMealy_pct , 1), + EDITOR_FIELD_NORM(Fermentable, label_kernelSizePrpPlump_pct, lineEdit_kernelSizePrpPlump_pct, Fermentable::kernelSizePrpPlump_pct, 1), + EDITOR_FIELD_NORM(Fermentable, label_kernelSizePrpThin_pct , lineEdit_kernelSizePrpThin_pct , Fermentable::kernelSizePrpThin_pct , 1), + EDITOR_FIELD_NORM(Fermentable, label_friability_pct , lineEdit_friability_pct , Fermentable::friability_pct , 1), + EDITOR_FIELD_NORM(Fermentable, label_di_ph , lineEdit_di_ph , Fermentable::di_ph , 1), + EDITOR_FIELD_NORM(Fermentable, label_viscosity_cP , lineEdit_viscosity_cP , Fermentable::viscosity_cP ), + EDITOR_FIELD_NORM(Fermentable, label_dmsP , lineEdit_dmsP , Fermentable::dmsP_ppm , 1), + EDITOR_FIELD_NORM(Fermentable, label_fan , lineEdit_fan , Fermentable::fan_ppm , 1), + EDITOR_FIELD_NORM(Fermentable, label_fermentability_pct , lineEdit_fermentability_pct , Fermentable::fermentability_pct , 1), + EDITOR_FIELD_NORM(Fermentable, label_betaGlucan , lineEdit_betaGlucan , Fermentable::betaGlucan_ppm , 1), + }); return; } FermentableEditor::~FermentableEditor() = default; -void FermentableEditor::writeFieldsToEditItem() { - this->m_editItem->setType(this->comboBox_type ->getNonOptValue()); - - this->m_editItem->setName (this->lineEdit_name ->text ()); - this->m_editItem->setColor_srm (this->lineEdit_color ->getNonOptCanonicalQty()); - this->m_editItem->setOrigin (this->lineEdit_origin ->text ()); - this->m_editItem->setSupplier (this->lineEdit_supplier ->text ()); - this->m_editItem->setCoarseFineDiff_pct (this->lineEdit_coarseFineDiff->getOptValue ()); - this->m_editItem->setMoisture_pct (this->lineEdit_moisture ->getOptValue ()); - this->m_editItem->setDiastaticPower_lintner(this->lineEdit_diastaticPower->getOptCanonicalQty ()); - this->m_editItem->setProtein_pct (this->lineEdit_protein ->getOptValue ()); - // See below for call to setTotalInventory, which needs to be done "late" - this->m_editItem->setMaxInBatch_pct (this->lineEdit_maxInBatch ->getOptValue ()); - this->m_editItem->setRecommendMash (this->checkBox_recommendMash ->checkState() == Qt::Checked); - this->m_editItem->setIbuGalPerLb (this->lineEdit_ibuGalPerLb ->getOptValue()); // .:TBD:. No metric measure? - this->m_editItem->setNotes (this->textEdit_notes ->toPlainText ()); - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - this->m_editItem->setGrainGroup (this->comboBox_grainGroup ->getOptValue()); - this->m_editItem->setProducer (this->lineEdit_producer ->text ()); - this->m_editItem->setProductId (this->lineEdit_productId ->text ()); - this->m_editItem->setFineGrindYield_pct (this->lineEdit_fineGrindYield_pct ->getOptValue()); - this->m_editItem->setCoarseGrindYield_pct (this->lineEdit_coarseGrindYield_pct ->getOptValue()); - this->m_editItem->setPotentialYield_sg (this->lineEdit_potentialYield_sg ->getOptValue()); - this->m_editItem->setAlphaAmylase_dextUnits (this->lineEdit_alphaAmylase_dextUnits ->getOptValue()); - this->m_editItem->setKolbachIndex_pct (this->lineEdit_kolbachIndex_pct ->getOptValue()); - this->m_editItem->setHardnessPrpGlassy_pct (this->lineEdit_hardnessPrpGlassy_pct ->getOptValue()); - this->m_editItem->setHardnessPrpHalf_pct (this->lineEdit_hardnessPrpHalf_pct ->getOptValue()); - this->m_editItem->setHardnessPrpMealy_pct (this->lineEdit_hardnessPrpMealy_pct ->getOptValue()); - this->m_editItem->setKernelSizePrpPlump_pct (this->lineEdit_kernelSizePrpPlump_pct ->getOptValue()); - this->m_editItem->setKernelSizePrpThin_pct (this->lineEdit_kernelSizePrpThin_pct ->getOptValue()); - this->m_editItem->setFriability_pct (this->lineEdit_friability_pct ->getOptValue()); - this->m_editItem->setDi_ph (this->lineEdit_di_ph ->getOptValue()); - this->m_editItem->setViscosity_cP (this->lineEdit_viscosity_cP ->getOptValue()); - this->m_editItem->setDmsP_ppm (this->lineEdit_dmsP ->getOptCanonicalQty()); - this->m_editItem->setFan_ppm (this->lineEdit_fan ->getOptCanonicalQty()); - this->m_editItem->setFermentability_pct (this->lineEdit_fermentability_pct ->getOptValue()); - this->m_editItem->setBetaGlucan_ppm (this->lineEdit_betaGlucan ->getOptCanonicalQty()); - - return; -} - -void FermentableEditor::writeLateFieldsToEditItem() { - // - // Do this late to make sure we've the row in the inventory table (because total inventory amount isn't really an - // attribute of the Fermentable). - // - // Note that we do not need to store the value of comboBox_amountType. It merely controls the available unit for - // lineEdit_inventory - // - // Note that, if the inventory field is blank, we'll treat that as meaning "don't change the inventory" - // - if (!this->lineEdit_inventory->isEmptyOrBlank()) { - this->m_editItem->setTotalInventory(lineEdit_inventory->getNonOptCanonicalAmt()); - } - return; -} - -void FermentableEditor::readFieldsFromEditItem(std::optional propName) { - if (!propName || *propName == PropertyNames::NamedEntity::name ) { this->lineEdit_name ->setTextCursor(m_editItem->name ()); // Continues to next line - this->tabWidget_editor->setTabText(0, m_editItem->name()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::type ) { this->comboBox_type ->setValue (m_editItem->type ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::color_srm ) { this->lineEdit_color ->setQuantity (m_editItem->color_srm ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::origin ) { this->lineEdit_origin ->setTextCursor(m_editItem->origin ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::supplier ) { this->lineEdit_supplier ->setTextCursor(m_editItem->supplier ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::coarseFineDiff_pct ) { this->lineEdit_coarseFineDiff->setQuantity (m_editItem->coarseFineDiff_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::moisture_pct ) { this->lineEdit_moisture ->setQuantity (m_editItem->moisture_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::diastaticPower_lintner) { this->lineEdit_diastaticPower->setQuantity (m_editItem->diastaticPower_lintner()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::protein_pct ) { this->lineEdit_protein ->setQuantity (m_editItem->protein_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Ingredient::totalInventory ) { this->lineEdit_inventory ->setAmount (m_editItem->totalInventory ()); - this->comboBox_amountType ->autoSetFromControlledField(); - if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::maxInBatch_pct ) { this->lineEdit_maxInBatch ->setQuantity (m_editItem->maxInBatch_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::recommendMash ) { this->checkBox_recommendMash ->setCheckState(m_editItem->recommendMash() ? Qt::Checked : Qt::Unchecked); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::ibuGalPerLb ) { this->lineEdit_ibuGalPerLb ->setQuantity (m_editItem->ibuGalPerLb ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::notes ) { this->textEdit_notes ->setPlainText (m_editItem->notes ()); if (propName) { return; } } - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - if (!propName || *propName == PropertyNames::Fermentable::grainGroup ) { this->comboBox_grainGroup ->setValue (m_editItem->grainGroup ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::producer ) { this->lineEdit_producer ->setTextCursor(m_editItem->producer ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::productId ) { this->lineEdit_productId ->setTextCursor(m_editItem->productId ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::fineGrindYield_pct ) { this->lineEdit_fineGrindYield_pct ->setQuantity (m_editItem->fineGrindYield_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::coarseGrindYield_pct ) { this->lineEdit_coarseGrindYield_pct ->setQuantity (m_editItem->coarseGrindYield_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::potentialYield_sg ) { this->lineEdit_potentialYield_sg ->setQuantity (m_editItem->potentialYield_sg ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::alphaAmylase_dextUnits ) { this->lineEdit_alphaAmylase_dextUnits ->setQuantity (m_editItem->alphaAmylase_dextUnits ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::kolbachIndex_pct ) { this->lineEdit_kolbachIndex_pct ->setQuantity (m_editItem->kolbachIndex_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::hardnessPrpGlassy_pct ) { this->lineEdit_hardnessPrpGlassy_pct ->setQuantity (m_editItem->hardnessPrpGlassy_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::hardnessPrpHalf_pct ) { this->lineEdit_hardnessPrpHalf_pct ->setQuantity (m_editItem->hardnessPrpHalf_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::hardnessPrpMealy_pct ) { this->lineEdit_hardnessPrpMealy_pct ->setQuantity (m_editItem->hardnessPrpMealy_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::kernelSizePrpPlump_pct ) { this->lineEdit_kernelSizePrpPlump_pct ->setQuantity (m_editItem->kernelSizePrpPlump_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::kernelSizePrpThin_pct ) { this->lineEdit_kernelSizePrpThin_pct ->setQuantity (m_editItem->kernelSizePrpThin_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::friability_pct ) { this->lineEdit_friability_pct ->setQuantity (m_editItem->friability_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::di_ph ) { this->lineEdit_di_ph ->setQuantity (m_editItem->di_ph ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::viscosity_cP ) { this->lineEdit_viscosity_cP ->setQuantity (m_editItem->viscosity_cP ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::dmsP_ppm ) { this->lineEdit_dmsP ->setQuantity (m_editItem->dmsP_ppm ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::fan_ppm ) { this->lineEdit_fan ->setQuantity (m_editItem->fan_ppm ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::fermentability_pct ) { this->lineEdit_fermentability_pct ->setQuantity (m_editItem->fermentability_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Fermentable::betaGlucan_ppm ) { this->lineEdit_betaGlucan ->setQuantity (m_editItem->betaGlucan_ppm ()); if (propName) { return; } } - - this->label_id_value->setText(QString::number(m_editItem->key())); - return; -} - // Insert the boiler-plate stuff that we cannot do in EditorBase -EDITOR_COMMON_CODE(FermentableEditor) +EDITOR_COMMON_CODE(Fermentable) diff --git a/src/editors/FermentableEditor.h b/src/editors/FermentableEditor.h index b5a166f5..2c123ac3 100644 --- a/src/editors/FermentableEditor.h +++ b/src/editors/FermentableEditor.h @@ -30,6 +30,7 @@ #include "editors/EditorBase.h" #include "model/Fermentable.h" +#define FermentableEditorOptions EditorBaseOptions{ .nameTab = true, .idDisplay = true } /*! * \class FermentableEditor * @@ -38,10 +39,12 @@ * See comment on EditorBase::connectSignalsAndSlots for why we need to have \c public, not \c private * inheritance from the Ui base. */ -class FermentableEditor : public QDialog, public Ui::fermentableEditor, public EditorBase { +class FermentableEditor : public QDialog, + public Ui::fermentableEditor, + public EditorBase { Q_OBJECT - EDITOR_COMMON_DECL(Fermentable) + EDITOR_COMMON_DECL(Fermentable, FermentableEditorOptions) }; #endif diff --git a/src/editors/FermentationEditor.cpp b/src/editors/FermentationEditor.cpp index c96f9b77..d9d35731 100644 --- a/src/editors/FermentationEditor.cpp +++ b/src/editors/FermentationEditor.cpp @@ -22,141 +22,19 @@ #include "model/Fermentation.h" #include "model/Recipe.h" -FermentationEditor::FermentationEditor(QWidget* parent) : +FermentationEditor::FermentationEditor(QWidget* parent, QString const editorName) : QDialog(parent), - EditorWithRecipeBase() { + EditorBase(editorName) { this->setupUi(this); - this->postSetupUiInit( - { - EDITOR_FIELD(Fermentation, label_name , lineEdit_name , PropertyNames::NamedEntity::name ), - EDITOR_FIELD(Fermentation, label_description, textEdit_description, PropertyNames::Fermentation::description ), - EDITOR_FIELD(Fermentation, label_notes , textEdit_notes , PropertyNames::Fermentation::notes ) - } - ); - -/// // NB: label_description / textEdit_description don't need initialisation here as neither is a smart field -/// // NB: label_notes / textEdit_notes don't need initialisation here as neither is a smart field -/// SMART_FIELD_INIT(FermentationEditor, label_name , lineEdit_name , Fermentation, PropertyNames::NamedEntity::name ); -/// -/// connect(this, &QDialog::accepted, this, &FermentationEditor::saveAndClose); -/// connect(this, &QDialog::rejected, this, &FermentationEditor::closeEditor ); + this->postSetupUiInit({ + EDITOR_FIELD_NORM(Fermentation, label_name , lineEdit_name , NamedEntity::name ), + EDITOR_FIELD_NORM(Fermentation, label_description, textEdit_description, Fermentation::description), + EDITOR_FIELD_NORM(Fermentation, label_notes , textEdit_notes , Fermentation::notes ), + }); return; } FermentationEditor::~FermentationEditor() = default; -void FermentationEditor::writeFieldsToEditItem() { - return; -} - -void FermentationEditor::writeLateFieldsToEditItem() { - return; -} - -void FermentationEditor::readFieldsFromEditItem([[maybe_unused]] std::optional propName) { - return; -} - // Insert the boilerplate stuff that we cannot do in EditorWithRecipeBase -EDITOR_WITH_RECIPE_COMMON_CODE(FermentationEditor) - -///void FermentationEditor::showEditor() { -/// showChanges(); -/// setVisible(true); -/// return; -///} -/// -///void FermentationEditor::closeEditor() { -/// setVisible(false); -/// return; -///} -/// -///void FermentationEditor::saveAndClose() { -/// bool isNew = false; -/// -/// if (!this->m_fermentationObs) { -/// this->m_fermentationObs = std::make_shared(lineEdit_name->text()); -/// isNew = true; -/// } -/// qDebug() << Q_FUNC_INFO << "Saving" << (isNew ? "new" : "existing") << "fermentation (#" << this->m_fermentationObs->key() << ")"; -/// -/// this->m_fermentationObs->setName (this->lineEdit_name ->text ()); -/// this->m_fermentationObs->setDescription (this->textEdit_description->toPlainText ()); -/// this->m_fermentationObs->setNotes (this->textEdit_notes ->toPlainText ()); -/// -/// if (isNew) { -/// ObjectStoreWrapper::insert(this->m_fermentationObs); -/// this->m_rec->setFermentation(this->m_fermentationObs); -/// } -/// -/// return; -///} -/// -///void FermentationEditor::setFermentation(std::shared_ptr fermentation) { -/// if (this->m_fermentationObs) { -/// disconnect(this->m_fermentationObs.get(), nullptr, this, nullptr); -/// } -/// -/// this->m_fermentationObs = fermentation; -/// if (this->m_fermentationObs) { -/// connect(this->m_fermentationObs.get(), &NamedEntity::changed, this, &FermentationEditor::changed); -/// showChanges(); -/// } -/// return; -///} -/// -///void FermentationEditor::setRecipe(Recipe * recipe) { -/// if (!recipe) { -/// return; -/// } -/// -/// this->m_rec = recipe; -/// -/// return; -///} -/// -///void FermentationEditor::changed(QMetaProperty prop, QVariant /*val*/) { -/// if (!this->m_fermentationObs) { -/// return; -/// } -/// -/// -/// if (sender() == this->m_fermentationObs.get()) { -/// this->showChanges(&prop); -/// } -/// -/// if (sender() == this->m_rec) { -/// this->showChanges(); -/// } -/// return; -///} -/// -///void FermentationEditor::showChanges(QMetaProperty* prop) { -/// if (!this->m_fermentationObs) { -/// this->clear(); -/// return; -/// } -/// -/// QString propName; -/// bool updateAll = false; -/// if (prop == nullptr) { -/// updateAll = true; -/// } else { -/// propName = prop->name(); -/// } -/// qDebug() << Q_FUNC_INFO << "Updating" << (updateAll ? "all" : "property") << propName; -/// -/// if (updateAll || propName == PropertyNames::NamedEntity::name ) {this->lineEdit_name ->setText (m_fermentationObs->name ()); if (!updateAll) { return; } } -/// if (updateAll || propName == PropertyNames::Fermentation::description) {this->textEdit_description->setPlainText(m_fermentationObs->description()); if (!updateAll) { return; } } -/// if (updateAll || propName == PropertyNames::Fermentation::notes ) {this->textEdit_notes ->setPlainText(m_fermentationObs->notes ()); if (!updateAll) { return; } } -/// return; -///} -/// -///void FermentationEditor::clear() { -/// this->lineEdit_name ->setText (""); -/// this->textEdit_description ->setText (""); -/// this->lineEdit_preFermentationSize->setText (""); -/// this->lineEdit_fermentationTime ->setText (""); -/// this->textEdit_notes ->setPlainText(""); -/// return; -///} +EDITOR_COMMON_CODE(Fermentation) diff --git a/src/editors/FermentationEditor.h b/src/editors/FermentationEditor.h index 5338f515..6a66414b 100644 --- a/src/editors/FermentationEditor.h +++ b/src/editors/FermentationEditor.h @@ -23,9 +23,10 @@ #include "ui_fermentationEditor.h" -#include "editors/EditorWithRecipeBase.h" +#include "editors/EditorBase.h" #include "model/Fermentation.h" +#define FermentationEditorOptions EditorBaseOptions{ .recipe = true } /*! * \class FermentationEditor * @@ -35,32 +36,10 @@ */ class FermentationEditor : public QDialog, public Ui::fermentationEditor, - public EditorWithRecipeBase { + public EditorBase { Q_OBJECT - EDITOR_WITH_RECIPE_COMMON_DECL(Fermentation) + EDITOR_COMMON_DECL(Fermentation, FermentationEditorOptions) }; -///class FermentationEditor : public QDialog, public Ui::fermentationEditor { -/// Q_OBJECT -///public: -/// FermentationEditor(QWidget * parent = nullptr); -/// ~FermentationEditor(); -/// -///public slots: -/// void showEditor(); -/// void closeEditor(); -/// void saveAndClose(); -/// //! Set the fermentation we wish to view/edit. -/// void setFermentation(std::shared_ptr fermentation); -/// void setRecipe(Recipe* r); -/// -/// void changed(QMetaProperty,QVariant); -///private: -/// void showChanges(QMetaProperty* prop = nullptr); -/// void clear(); -/// Recipe* m_rec; -/// std::shared_ptr m_fermentationObs; -///}; - #endif diff --git a/src/editors/FermentationStepEditor.cpp b/src/editors/FermentationStepEditor.cpp index 14393f3c..b9e18d95 100644 --- a/src/editors/FermentationStepEditor.cpp +++ b/src/editors/FermentationStepEditor.cpp @@ -18,74 +18,31 @@ #include "MainWindow.h" #include "measurement/Unit.h" -FermentationStepEditor::FermentationStepEditor(QWidget* parent) : +FermentationStepEditor::FermentationStepEditor(QWidget* parent, QString const editorName) : QDialog{parent}, - EditorBase() { + EditorBase(editorName) { this->setupUi(this); - - // NB: Although FermentationStep inherits (via StepExtended) from Step, the rampTime_mins field is not used and - // should not be stored in the DB or serialised. See comment in model/Step.h. There should therefore not be - // any label_rampTime or lineEdit_rampTime fields in the .ui file! - // - // NB: label_description / textEdit_description don't need initialisation here as neither is a smart field - SMART_FIELD_INIT(FermentationStepEditor, label_name , lineEdit_name , FermentationStep, PropertyNames:: NamedEntity::name ); - SMART_FIELD_INIT(FermentationStepEditor, label_startTemp , lineEdit_startTemp , FermentationStep, PropertyNames:: Step::startTemp_c , 1); - SMART_FIELD_INIT(FermentationStepEditor, label_stepTime , lineEdit_stepTime , FermentationStep, PropertyNames:: Step::stepTime_mins , 0); - SMART_FIELD_INIT(FermentationStepEditor, label_endTemp , lineEdit_endTemp , FermentationStep, PropertyNames:: Step::endTemp_c , 1); - SMART_FIELD_INIT(FermentationStepEditor, label_startAcidity , lineEdit_startAcidity , FermentationStep, PropertyNames:: Step::startAcidity_pH, 1); - SMART_FIELD_INIT(FermentationStepEditor, label_endAcidity , lineEdit_endAcidity , FermentationStep, PropertyNames:: Step::endAcidity_pH , 1); - SMART_FIELD_INIT(FermentationStepEditor, label_startGravity , lineEdit_startGravity , FermentationStep, PropertyNames:: StepExtended::startGravity_sg, 3); - SMART_FIELD_INIT(FermentationStepEditor, label_endGravity , lineEdit_endGravity , FermentationStep, PropertyNames:: StepExtended::endGravity_sg , 3); - SMART_FIELD_INIT(FermentationStepEditor, label_vessel , lineEdit_vessel , FermentationStep, PropertyNames::FermentationStep::vessel ); - BT_BOOL_COMBO_BOX_INIT(FermentationStepEditor, boolCombo_freeRise, FermentationStep, freeRise); - - this->connectSignalsAndSlots(); + this->postSetupUiInit({ + // NB: Although FermentationStep inherits (via StepExtended) from Step, the rampTime_mins field is not used and + // should not be stored in the DB or serialised. See comment in model/Step.h. There should therefore not be + // any label_rampTime or lineEdit_rampTime fields in the .ui file! + EDITOR_FIELD_NORM(FermentationStep, label_name , lineEdit_name , NamedEntity::name ), + EDITOR_FIELD_NORM(FermentationStep, label_description , textEdit_description , Step::description ), + EDITOR_FIELD_NORM(FermentationStep, label_startTemp , lineEdit_startTemp , Step::startTemp_c , 1), + EDITOR_FIELD_NORM(FermentationStep, label_stepTime , lineEdit_stepTime , Step::stepTime_mins , 0), + EDITOR_FIELD_NORM(FermentationStep, label_endTemp , lineEdit_endTemp , Step::endTemp_c , 1), + EDITOR_FIELD_NORM(FermentationStep, label_startAcidity, lineEdit_startAcidity , Step::startAcidity_pH, 1), + EDITOR_FIELD_NORM(FermentationStep, label_endAcidity , lineEdit_endAcidity , Step::endAcidity_pH , 1), + EDITOR_FIELD_NORM(FermentationStep, label_startGravity, lineEdit_startGravity , StepExtended::startGravity_sg, 3), + EDITOR_FIELD_NORM(FermentationStep, label_endGravity , lineEdit_endGravity , StepExtended::endGravity_sg , 3), + EDITOR_FIELD_NORM(FermentationStep, label_vessel , lineEdit_vessel , FermentationStep::vessel ), + EDITOR_FIELD_NORM(FermentationStep, label_freeRise , boolCombo_freeRise , FermentationStep::freeRise ), + }); return; } FermentationStepEditor::~FermentationStepEditor() = default; -void FermentationStepEditor::readFieldsFromEditItem(std::optional propName) { - // NB: Although FermentationStep inherits (via StepExtended) from Step, the rampTime_mins field is not used and - // should not be stored in the DB or serialised. See comment in model/Step.h. - if (!propName || *propName == PropertyNames:: NamedEntity::name ) { this->lineEdit_name ->setTextCursor(m_editItem->name ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::description ) { this->textEdit_description ->setPlainText (m_editItem->description ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::startTemp_c ) { this->lineEdit_startTemp ->setQuantity (m_editItem->startTemp_c ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::stepTime_mins ) { this->lineEdit_stepTime ->setQuantity (m_editItem->stepTime_mins ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::endTemp_c ) { this->lineEdit_endTemp ->setQuantity (m_editItem->endTemp_c ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::startAcidity_pH) { this->lineEdit_startAcidity->setQuantity (m_editItem->startAcidity_pH()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::endAcidity_pH ) { this->lineEdit_endAcidity ->setQuantity (m_editItem->endAcidity_pH ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::StepExtended::startGravity_sg) { this->lineEdit_startGravity->setQuantity (m_editItem->startGravity_sg()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::StepExtended::endGravity_sg ) { this->lineEdit_endGravity ->setQuantity (m_editItem->endGravity_sg ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::FermentationStep::vessel ) { this->lineEdit_vessel ->setTextCursor(m_editItem->vessel ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::FermentationStep::freeRise ) { this->boolCombo_freeRise ->setValue (m_editItem->freeRise ()); if (propName) { return; } } - - return; -} - -void FermentationStepEditor::writeFieldsToEditItem() { - // NB: Although FermentationStep inherits (via StepExtended) from Step, the rampTime_mins field is not used and - // should not be stored in the DB or serialised. See comment in model/Step.h. - this->m_editItem->setName (this->lineEdit_name ->text ()); - this->m_editItem->setDescription (this->textEdit_description ->toPlainText ()); - this->m_editItem->setStartTemp_c (this->lineEdit_startTemp ->getOptCanonicalQty()); - this->m_editItem->setStepTime_mins (this->lineEdit_stepTime ->getOptCanonicalQty()); - this->m_editItem->setEndTemp_c (this->lineEdit_endTemp ->getOptCanonicalQty()); - this->m_editItem->setStartAcidity_pH(this->lineEdit_startAcidity->getOptCanonicalQty()); - this->m_editItem->setEndAcidity_pH (this->lineEdit_endAcidity ->getOptCanonicalQty()); - this->m_editItem->setStartGravity_sg(this->lineEdit_startGravity->getOptCanonicalQty()); - this->m_editItem->setEndGravity_sg (this->lineEdit_endGravity ->getOptCanonicalQty()); - this->m_editItem->setVessel (this->lineEdit_vessel ->text ()); - this->m_editItem->setFreeRise (this->boolCombo_freeRise ->getOptBoolValue ()); - - return; -} - -void FermentationStepEditor::writeLateFieldsToEditItem() { - // Nothing to do here - return; -} - // Insert the boilerplate stuff that we cannot do in EditorBase -EDITOR_COMMON_CODE(FermentationStepEditor) +EDITOR_COMMON_CODE(FermentationStep) diff --git a/src/editors/FermentationStepEditor.h b/src/editors/FermentationStepEditor.h index 73d06f6b..f4755caf 100644 --- a/src/editors/FermentationStepEditor.h +++ b/src/editors/FermentationStepEditor.h @@ -24,6 +24,7 @@ #include "editors/EditorBase.h" #include "model/FermentationStep.h" +#define FermentationStepEditorOptions EditorBaseOptions{ } /*! * \class FermentationStepEditor * @@ -31,10 +32,10 @@ */ class FermentationStepEditor : public QDialog, public Ui::fermentationStepEditor, - public EditorBase { + public EditorBase { Q_OBJECT - EDITOR_COMMON_DECL(FermentationStep) + EDITOR_COMMON_DECL(FermentationStep, FermentationStepEditorOptions) }; diff --git a/src/editors/HopEditor.cpp b/src/editors/HopEditor.cpp index 3088871c..a66a0437 100644 --- a/src/editors/HopEditor.cpp +++ b/src/editors/HopEditor.cpp @@ -31,138 +31,51 @@ // TODO: Need a separate editor for inventory -HopEditor::HopEditor(QWidget * parent) : +HopEditor::HopEditor(QWidget * parent, QString const editorName) : QDialog(parent), - EditorBase() { + EditorBase(editorName) { setupUi(this); - - this->tabWidget_editor->tabBar()->setStyle(new BtHorizontalTabs); - - SMART_FIELD_INIT(HopEditor, label_name , lineEdit_name , Hop, PropertyNames::NamedEntity::name ); - SMART_FIELD_INIT(HopEditor, label_alpha , lineEdit_alpha , Hop, PropertyNames::Hop::alpha_pct , 1); - SMART_FIELD_INIT(HopEditor, label_inventory , lineEdit_inventory , Hop, PropertyNames::Ingredient::totalInventory, 1); - SMART_FIELD_INIT(HopEditor, label_beta , lineEdit_beta , Hop, PropertyNames::Hop::beta_pct , 1); - SMART_FIELD_INIT(HopEditor, label_HSI , lineEdit_HSI , Hop, PropertyNames::Hop::hsi_pct , 0); - SMART_FIELD_INIT(HopEditor, label_origin , lineEdit_origin , Hop, PropertyNames::Hop::origin ); - SMART_FIELD_INIT(HopEditor, label_humulene , lineEdit_humulene , Hop, PropertyNames::Hop::humulene_pct , 2); - SMART_FIELD_INIT(HopEditor, label_caryophyllene , lineEdit_caryophyllene , Hop, PropertyNames::Hop::caryophyllene_pct , 2); - SMART_FIELD_INIT(HopEditor, label_cohumulone , lineEdit_cohumulone , Hop, PropertyNames::Hop::cohumulone_pct , 2); - SMART_FIELD_INIT(HopEditor, label_myrcene , lineEdit_myrcene , Hop, PropertyNames::Hop::myrcene_pct , 2); - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - SMART_FIELD_INIT(HopEditor, label_producer , lineEdit_producer , Hop, PropertyNames::Hop::producer ); - SMART_FIELD_INIT(HopEditor, label_productId , lineEdit_productId , Hop, PropertyNames::Hop::productId ); - SMART_FIELD_INIT(HopEditor, label_year , lineEdit_year , Hop, PropertyNames::Hop::year ); - SMART_FIELD_INIT(HopEditor, label_totalOil_mlPer100g, lineEdit_totalOil_mlPer100g, Hop, PropertyNames::Hop::totalOil_mlPer100g ); - SMART_FIELD_INIT(HopEditor, label_farnesene , lineEdit_farnesene , Hop, PropertyNames::Hop::farnesene_pct , 2); - SMART_FIELD_INIT(HopEditor, label_geraniol , lineEdit_geraniol , Hop, PropertyNames::Hop::geraniol_pct , 2); - SMART_FIELD_INIT(HopEditor, label_bPinene , lineEdit_bPinene , Hop, PropertyNames::Hop::bPinene_pct , 2); - SMART_FIELD_INIT(HopEditor, label_linalool , lineEdit_linalool , Hop, PropertyNames::Hop::linalool_pct , 2); - SMART_FIELD_INIT(HopEditor, label_limonene , lineEdit_limonene , Hop, PropertyNames::Hop::limonene_pct , 2); - SMART_FIELD_INIT(HopEditor, label_nerol , lineEdit_nerol , Hop, PropertyNames::Hop::nerol_pct , 2); - SMART_FIELD_INIT(HopEditor, label_pinene , lineEdit_pinene , Hop, PropertyNames::Hop::pinene_pct , 2); - SMART_FIELD_INIT(HopEditor, label_polyphenols , lineEdit_polyphenols , Hop, PropertyNames::Hop::polyphenols_pct , 2); - SMART_FIELD_INIT(HopEditor, label_xanthohumol , lineEdit_xanthohumol , Hop, PropertyNames::Hop::xanthohumol_pct , 2); - -/// SMART_CHECK_BOX_INIT(HopEditor, checkBox_amountIsWeight , label_amountIsWeight , lineEdit_inventory , Hop, amountIsWeight ); - - BT_COMBO_BOX_INIT_COPQ(HopEditor, comboBox_amountType, Hop, PropertyNames::Ingredient::totalInventory, lineEdit_inventory); - - BT_COMBO_BOX_INIT(HopEditor, comboBox_hopType, Hop, type); - BT_COMBO_BOX_INIT(HopEditor, comboBox_hopForm, Hop, form); - - this->connectSignalsAndSlots(); + this->postSetupUiInit({ + // + // Write inventory late to make sure we've the row in the inventory table (because total inventory amount isn't + // really an attribute of the Fermentable). + // + // Note that we do not need to store the value of comboBox_amountType. It merely controls the available unit for + // lineEdit_inventory + // + EDITOR_FIELD_NORM(Hop, label_name , lineEdit_name , NamedEntity::name ), + EDITOR_FIELD_NORM(Hop, tab_notes , textEdit_notes , Hop::notes ), + EDITOR_FIELD_NORM(Hop, label_alpha , lineEdit_alpha , Hop::alpha_pct , 1), + EDITOR_FIELD_NORM(Hop, label_inventory , lineEdit_inventory , Ingredient::totalInventory, 1, WhenToWriteField::Late), + EDITOR_FIELD_COPQ(Hop, label_amountType , comboBox_amountType , Ingredient::totalInventory, lineEdit_inventory, WhenToWriteField::Never), + EDITOR_FIELD_NORM(Hop, label_beta , lineEdit_beta , Hop::beta_pct , 1), + EDITOR_FIELD_NORM(Hop, label_HSI , lineEdit_HSI , Hop::hsi_pct , 0), + EDITOR_FIELD_NORM(Hop, label_origin , lineEdit_origin , Hop::origin ), + EDITOR_FIELD_NORM(Hop, label_humulene , lineEdit_humulene , Hop::humulene_pct , 2), + EDITOR_FIELD_NORM(Hop, label_caryophyllene , lineEdit_caryophyllene , Hop::caryophyllene_pct , 2), + EDITOR_FIELD_NORM(Hop, label_cohumulone , lineEdit_cohumulone , Hop::cohumulone_pct , 2), + EDITOR_FIELD_NORM(Hop, label_myrcene , lineEdit_myrcene , Hop::myrcene_pct , 2), + EDITOR_FIELD_ENUM(Hop, label_type , comboBox_hopType , Hop::type ), + EDITOR_FIELD_ENUM(Hop, label_form , comboBox_hopForm , Hop::form ), + // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ + EDITOR_FIELD_NORM(Hop, label_producer , lineEdit_producer , Hop::producer ), + EDITOR_FIELD_NORM(Hop, label_productId , lineEdit_productId , Hop::productId ), + EDITOR_FIELD_NORM(Hop, label_year , lineEdit_year , Hop::year ), + EDITOR_FIELD_NORM(Hop, label_totalOil_mlPer100g, lineEdit_totalOil_mlPer100g, Hop::totalOil_mlPer100g ), + EDITOR_FIELD_NORM(Hop, label_farnesene , lineEdit_farnesene , Hop::farnesene_pct , 2), + EDITOR_FIELD_NORM(Hop, label_geraniol , lineEdit_geraniol , Hop::geraniol_pct , 2), + EDITOR_FIELD_NORM(Hop, label_bPinene , lineEdit_bPinene , Hop::bPinene_pct , 2), + EDITOR_FIELD_NORM(Hop, label_linalool , lineEdit_linalool , Hop::linalool_pct , 2), + EDITOR_FIELD_NORM(Hop, label_limonene , lineEdit_limonene , Hop::limonene_pct , 2), + EDITOR_FIELD_NORM(Hop, label_nerol , lineEdit_nerol , Hop::nerol_pct , 2), + EDITOR_FIELD_NORM(Hop, label_pinene , lineEdit_pinene , Hop::pinene_pct , 2), + EDITOR_FIELD_NORM(Hop, label_polyphenols , lineEdit_polyphenols , Hop::polyphenols_pct , 2), + EDITOR_FIELD_NORM(Hop, label_xanthohumol , lineEdit_xanthohumol , Hop::xanthohumol_pct , 2), + }); return; } HopEditor::~HopEditor() = default; -void HopEditor::writeFieldsToEditItem() { - this->m_editItem->setName (this->lineEdit_name ->text ()); - this->m_editItem->setAlpha_pct (this->lineEdit_alpha ->getNonOptValue()); - this->m_editItem->setBeta_pct (this->lineEdit_beta ->getOptValue ()); - this->m_editItem->setHsi_pct (this->lineEdit_HSI ->getOptValue ()); - this->m_editItem->setOrigin (this->lineEdit_origin ->text ()); - this->m_editItem->setHumulene_pct (this->lineEdit_humulene ->getOptValue ()); - this->m_editItem->setCaryophyllene_pct(this->lineEdit_caryophyllene->getOptValue ()); - this->m_editItem->setCohumulone_pct (this->lineEdit_cohumulone ->getOptValue ()); - this->m_editItem->setMyrcene_pct (this->lineEdit_myrcene ->getOptValue ()); - this->m_editItem->setSubstitutes (this->textEdit_substitutes ->toPlainText ()); - this->m_editItem->setNotes (this->textEdit_notes ->toPlainText ()); - - this->m_editItem->setType (this->comboBox_hopType ->getOptValue()); - this->m_editItem->setForm (this->comboBox_hopForm ->getOptValue()); - - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ -/// this->m_editItem->setAmountIsWeight (this->checkBox_amountIsWeight ->isChecked ()); - this->m_editItem->setProducer (this->lineEdit_producer ->text ()); - this->m_editItem->setProductId (this->lineEdit_productId ->text ()); - this->m_editItem->setYear (this->lineEdit_year ->text ()); - this->m_editItem->setTotalOil_mlPer100g(this->lineEdit_totalOil_mlPer100g->getOptValue()); - this->m_editItem->setFarnesene_pct (this->lineEdit_farnesene ->getOptValue()); - this->m_editItem->setGeraniol_pct (this->lineEdit_geraniol ->getOptValue()); - this->m_editItem->setBPinene_pct (this->lineEdit_bPinene ->getOptValue()); - this->m_editItem->setLinalool_pct (this->lineEdit_linalool ->getOptValue()); - this->m_editItem->setLimonene_pct (this->lineEdit_limonene ->getOptValue()); - this->m_editItem->setNerol_pct (this->lineEdit_nerol ->getOptValue()); - this->m_editItem->setPinene_pct (this->lineEdit_pinene ->getOptValue()); - this->m_editItem->setPolyphenols_pct (this->lineEdit_polyphenols ->getOptValue()); - this->m_editItem->setXanthohumol_pct (this->lineEdit_xanthohumol ->getOptValue()); - return; -} - -void HopEditor::writeLateFieldsToEditItem() { - // - // Do this late to make sure we've the row in the inventory table (because total inventory amount isn't really an - // attribute of the Hop). - // - // Note that we do not need to store the value of comboBox_amountType. It merely controls the available unit for - // lineEdit_inventory - // - // Note that, if the inventory field is blank, we'll treat that as meaning "don't change the inventory" - // - if (!this->lineEdit_inventory->isEmptyOrBlank()) { - this->m_editItem->setTotalInventory(lineEdit_inventory->getNonOptCanonicalAmt()); - } - return; -} - -void HopEditor::readFieldsFromEditItem(std::optional propName) { - if (!propName || *propName == PropertyNames::Hop::type ) { this->comboBox_hopType ->setValue (m_editItem->type ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::form ) { this->comboBox_hopForm ->setValue (m_editItem->form ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::NamedEntity::name ) { this->lineEdit_name ->setTextCursor(m_editItem->name ()); // Continues to next line - this->tabWidget_editor->setTabText(0, m_editItem->name()); - if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::origin ) { this->lineEdit_origin ->setTextCursor(m_editItem->origin ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::alpha_pct ) { this->lineEdit_alpha ->setQuantity (m_editItem->alpha_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::beta_pct ) { this->lineEdit_beta ->setQuantity (m_editItem->beta_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::hsi_pct ) { this->lineEdit_HSI ->setQuantity (m_editItem->hsi_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::humulene_pct ) { this->lineEdit_humulene ->setQuantity (m_editItem->humulene_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::caryophyllene_pct) { this->lineEdit_caryophyllene->setQuantity (m_editItem->caryophyllene_pct()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::cohumulone_pct ) { this->lineEdit_cohumulone ->setQuantity (m_editItem->cohumulone_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::myrcene_pct ) { this->lineEdit_myrcene ->setQuantity (m_editItem->myrcene_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::substitutes ) { this->textEdit_substitutes ->setPlainText (m_editItem->substitutes ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::notes ) { this->textEdit_notes ->setPlainText (m_editItem->notes ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Ingredient::totalInventory) { this->lineEdit_inventory->setAmount (m_editItem->totalInventory ()); - this->comboBox_amountType->autoSetFromControlledField(); - if (propName) { return; } } - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - if (!propName || *propName == PropertyNames::Hop::producer ) { this->lineEdit_producer ->setText (m_editItem->producer ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::productId ) { this->lineEdit_productId ->setText (m_editItem->productId ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::year ) { this->lineEdit_year ->setText (m_editItem->year ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::totalOil_mlPer100g) { this->lineEdit_totalOil_mlPer100g->setQuantity (m_editItem->totalOil_mlPer100g()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::farnesene_pct ) { this->lineEdit_farnesene ->setQuantity(m_editItem->farnesene_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::geraniol_pct ) { this->lineEdit_geraniol ->setQuantity(m_editItem->geraniol_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::bPinene_pct ) { this->lineEdit_bPinene ->setQuantity(m_editItem->bPinene_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::linalool_pct ) { this->lineEdit_linalool ->setQuantity(m_editItem->linalool_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::limonene_pct ) { this->lineEdit_limonene ->setQuantity(m_editItem->limonene_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::nerol_pct ) { this->lineEdit_nerol ->setQuantity(m_editItem->nerol_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::pinene_pct ) { this->lineEdit_pinene ->setQuantity(m_editItem->pinene_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::polyphenols_pct) { this->lineEdit_polyphenols->setQuantity(m_editItem->polyphenols_pct()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Hop::xanthohumol_pct) { this->lineEdit_xanthohumol->setQuantity(m_editItem->xanthohumol_pct()); if (propName) { return; } } - - this->label_id_value->setText(QString::number(m_editItem->key())); - return; -} - // Insert the boiler-plate stuff that we cannot do in EditorBase -EDITOR_COMMON_CODE(HopEditor) +EDITOR_COMMON_CODE(Hop) diff --git a/src/editors/HopEditor.h b/src/editors/HopEditor.h index fac4e2a8..d23f7c49 100644 --- a/src/editors/HopEditor.h +++ b/src/editors/HopEditor.h @@ -26,6 +26,7 @@ #include "editors/EditorBase.h" #include "model/Hop.h" +#define HopEditorOptions EditorBaseOptions{ .nameTab = true, .idDisplay = true } /*! * \class HopEditor * @@ -34,10 +35,12 @@ * See comment on EditorBase::connectSignalsAndSlots for why we need to have \c public, not \c private * inheritance from the Ui base. */ -class HopEditor : public QDialog, public Ui::hopEditor, public EditorBase { +class HopEditor : public QDialog, + public Ui::hopEditor, + public EditorBase { Q_OBJECT - EDITOR_COMMON_DECL(Hop) + EDITOR_COMMON_DECL(Hop, HopEditorOptions) }; #endif diff --git a/src/editors/MashStepEditor.cpp b/src/editors/MashStepEditor.cpp index 2943d8f0..e710d7bd 100644 --- a/src/editors/MashStepEditor.cpp +++ b/src/editors/MashStepEditor.cpp @@ -21,74 +21,39 @@ #include "MainWindow.h" #include "measurement/Unit.h" -MashStepEditor::MashStepEditor(QWidget* parent) : +MashStepEditor::MashStepEditor(QWidget* parent, QString const editorName) : QDialog{parent}, - EditorBase() { + EditorBase(editorName) { this->setupUi(this); - - // NB: label_description / textEdit_description don't need initialisation here as neither is a smart field - SMART_FIELD_INIT(MashStepEditor, label_name , lineEdit_name , MashStep, PropertyNames::NamedEntity::name ); - SMART_FIELD_INIT(MashStepEditor, label_amount , lineEdit_amount , MashStep, PropertyNames:: MashStep::amount_l ); - SMART_FIELD_INIT(MashStepEditor, label_infuseTemp , lineEdit_infuseTemp , MashStep, PropertyNames:: MashStep::infuseTemp_c , 1); - SMART_FIELD_INIT(MashStepEditor, label_stepTemp , lineEdit_stepTemp , MashStep, PropertyNames:: Step::startTemp_c , 1); - SMART_FIELD_INIT(MashStepEditor, label_stepTime , lineEdit_stepTime , MashStep, PropertyNames:: Step::stepTime_mins , 0); - SMART_FIELD_INIT(MashStepEditor, label_rampTime , lineEdit_rampTime , MashStep, PropertyNames:: Step::rampTime_mins , 0); - SMART_FIELD_INIT(MashStepEditor, label_endTemp , lineEdit_endTemp , MashStep, PropertyNames:: Step::endTemp_c , 1); - // ⮜⮜⮜ All three below added for BeerJSON support ⮞⮞⮞ - SMART_FIELD_INIT(MashStepEditor, label_thickness , lineEdit_thickness , MashStep, PropertyNames:: MashStep::liquorToGristRatio_lKg, 1); - SMART_FIELD_INIT(MashStepEditor, label_startAcidity , lineEdit_startAcidity , MashStep, PropertyNames:: Step::startAcidity_pH , 1); - SMART_FIELD_INIT(MashStepEditor, label_endAcidity , lineEdit_endAcidity , MashStep, PropertyNames:: Step::endAcidity_pH , 1); - - BT_COMBO_BOX_INIT(MashStepEditor, comboBox_mashStepType, MashStep, type); - - this->connectSignalsAndSlots(); + this->postSetupUiInit({ + // + // As explained in model/MashStep.h, there is only one amount for a MashStep, and it is accessed via + // MashStep::amount_l. The decoctionAmount_l and infuseAmount_l properties are only used for reading and writing + // BeerXML. + // + // We retain infuseTemp_c for now, even though it is not part of BeerJSON. TBD whether it is needed longer-term. + // + EDITOR_FIELD_NORM(MashStep, label_name , lineEdit_name , NamedEntity::name ), + EDITOR_FIELD_NORM(MashStep, label_description , textEdit_description , Step::description ), + EDITOR_FIELD_NORM(MashStep, label_amount , lineEdit_amount , MashStep::amount_l ), + EDITOR_FIELD_NORM(MashStep, label_stepTemp , lineEdit_stepTemp , Step::startTemp_c , 1), + EDITOR_FIELD_NORM(MashStep, label_stepTime , lineEdit_stepTime , Step::stepTime_mins , 0), + EDITOR_FIELD_NORM(MashStep, label_rampTime , lineEdit_rampTime , Step::rampTime_mins , 0), + EDITOR_FIELD_NORM(MashStep, label_endTemp , lineEdit_endTemp , Step::endTemp_c , 1), + EDITOR_FIELD_NORM(MashStep, label_infuseTemp , lineEdit_infuseTemp , MashStep::infuseTemp_c , 1), + EDITOR_FIELD_NORM(MashStep, label_startAcidity, lineEdit_startAcidity, Step::startAcidity_pH , 1), + EDITOR_FIELD_NORM(MashStep, label_endAcidity , lineEdit_endAcidity , Step::endAcidity_pH , 1), + EDITOR_FIELD_NORM(MashStep, label_thickness , lineEdit_thickness , MashStep::liquorToGristRatio_lKg, 1), + EDITOR_FIELD_ENUM(MashStep, label_mashStepType, comboBox_mashStepType, MashStep::type ), + }); // This is extra for this editor - connect(this->comboBox_mashStepType, &QComboBox::currentTextChanged, this, &MashStepEditor::grayOutStuff); + this->connect(this->comboBox_mashStepType, &QComboBox::currentTextChanged, this, &MashStepEditor::grayOutStuff); return; } MashStepEditor::~MashStepEditor() = default; -void MashStepEditor::readFieldsFromEditItem(std::optional propName) { - if (!propName || *propName == PropertyNames::NamedEntity::name ) { this->lineEdit_name ->setTextCursor(m_editItem->name ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::MashStep::type ) { this->comboBox_mashStepType->setValue (m_editItem->type ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::MashStep::infuseAmount_l ) { this->lineEdit_amount ->setQuantity (m_editItem->amount_l ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::MashStep::infuseTemp_c ) { this->lineEdit_infuseTemp ->setQuantity (m_editItem->infuseTemp_c ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::startTemp_c ) { this->lineEdit_stepTemp ->setQuantity (m_editItem->startTemp_c ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::stepTime_mins ) { this->lineEdit_stepTime ->setQuantity (m_editItem->stepTime_mins ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::rampTime_mins ) { this->lineEdit_rampTime ->setQuantity (m_editItem->rampTime_mins ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::endTemp_c ) { this->lineEdit_endTemp ->setQuantity (m_editItem->endTemp_c ()); if (propName) { return; } } - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - if (!propName || *propName == PropertyNames::MashStep::liquorToGristRatio_lKg) { this->lineEdit_thickness ->setQuantity (m_editItem->liquorToGristRatio_lKg()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::startAcidity_pH ) { this->lineEdit_startAcidity->setQuantity (m_editItem->startAcidity_pH ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::endAcidity_pH ) { this->lineEdit_endAcidity ->setQuantity (m_editItem->endAcidity_pH ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames:: Step::description ) { this->textEdit_description ->setPlainText (m_editItem->description ()); if (propName) { return; } } - return; -} - -void MashStepEditor::writeFieldsToEditItem() { - this->m_editItem->setName (this->lineEdit_name->text()); - this->m_editItem->setType (this->comboBox_mashStepType->getNonOptValue()); - this->m_editItem->setAmount_l (this->lineEdit_amount ->getNonOptCanonicalQty()); - this->m_editItem->setInfuseTemp_c (this->lineEdit_infuseTemp ->getOptCanonicalQty ()); - this->m_editItem->setStartTemp_c (this->lineEdit_stepTemp ->getNonOptCanonicalQty()); - this->m_editItem->setStepTime_mins (this->lineEdit_stepTime ->getNonOptCanonicalQty()); - this->m_editItem->setRampTime_mins (this->lineEdit_rampTime ->getOptCanonicalQty ()); - this->m_editItem->setEndTemp_c (this->lineEdit_endTemp ->getOptCanonicalQty ()); - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - this->m_editItem->setLiquorToGristRatio_lKg(this->lineEdit_thickness ->getOptCanonicalQty ()); - this->m_editItem->setStartAcidity_pH (this->lineEdit_startAcidity->getOptCanonicalQty ()); - this->m_editItem->setEndAcidity_pH (this->lineEdit_endAcidity ->getOptCanonicalQty ()); - this->m_editItem->setDescription (this->textEdit_description ->toPlainText ()); - return; -} - -void MashStepEditor::writeLateFieldsToEditItem() { - // Nothing to do here - return; -} - void MashStepEditor::grayOutStuff([[maybe_unused]] QString const & text) { auto const msType = this->comboBox_mashStepType->getNonOptValue(); switch (msType) { @@ -111,4 +76,4 @@ void MashStepEditor::grayOutStuff([[maybe_unused]] QString const & text) { } // Insert the boiler-plate stuff that we cannot do in EditorBase -EDITOR_COMMON_CODE(MashStepEditor) +EDITOR_COMMON_CODE(MashStep) diff --git a/src/editors/MashStepEditor.h b/src/editors/MashStepEditor.h index b82d6a3b..fddfd3bc 100644 --- a/src/editors/MashStepEditor.h +++ b/src/editors/MashStepEditor.h @@ -27,15 +27,18 @@ #include "editors/EditorBase.h" #include "model/MashStep.h" +#define MashStepEditorOptions EditorBaseOptions{ } /*! * \class MashStepEditor * * \brief View/controller dialog for editing mash steps. */ -class MashStepEditor : public QDialog, public Ui::mashStepEditor, public EditorBase { +class MashStepEditor : public QDialog, + public Ui::mashStepEditor, + public EditorBase { Q_OBJECT - EDITOR_COMMON_DECL(MashStep) + EDITOR_COMMON_DECL(MashStep, MashStepEditorOptions) public slots: /*! diff --git a/src/editors/MiscEditor.cpp b/src/editors/MiscEditor.cpp index 1743bbea..e3f9a6ce 100644 --- a/src/editors/MiscEditor.cpp +++ b/src/editors/MiscEditor.cpp @@ -28,73 +28,32 @@ #include "database/ObjectStoreWrapper.h" #include "measurement/Unit.h" -MiscEditor::MiscEditor(QWidget * parent) : +MiscEditor::MiscEditor(QWidget * parent, QString const editorName) : QDialog(parent), - EditorBase() { + EditorBase(editorName) { setupUi(this); - - tabWidget_editor->tabBar()->setStyle(new BtHorizontalTabs); - - SMART_FIELD_INIT(MiscEditor, label_name , lineEdit_name , Misc, PropertyNames::NamedEntity::name); - SMART_FIELD_INIT(MiscEditor, label_inventory, lineEdit_inventory, Misc, PropertyNames::Ingredient::totalInventory, 1); - - BT_COMBO_BOX_INIT(MiscEditor, comboBox_type, Misc, type); - - BT_COMBO_BOX_INIT_COPQ(MiscEditor, comboBox_amountType, Misc, PropertyNames::Ingredient::totalInventory, lineEdit_inventory); - - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - SMART_FIELD_INIT(MiscEditor, label_producer , lineEdit_producer , Misc, PropertyNames::Misc::producer ); - SMART_FIELD_INIT(MiscEditor, label_productId, lineEdit_productId, Misc, PropertyNames::Misc::productId ); - - this->connectSignalsAndSlots(); + this->postSetupUiInit({ + // + // Write inventory late to make sure we've the row in the inventory table (because total inventory amount isn't + // really an attribute of the Fermentable). + // + // Note that we do not need to store the value of comboBox_amountType. It merely controls the available unit for + // lineEdit_inventory + // + EDITOR_FIELD_NORM(Misc, label_name , lineEdit_name , NamedEntity::name ), + EDITOR_FIELD_NORM(Misc, label_inventory , lineEdit_inventory , Ingredient::totalInventory, 1, WhenToWriteField::Late), + EDITOR_FIELD_COPQ(Misc, label_amountType, comboBox_amountType, Ingredient::totalInventory, lineEdit_inventory, WhenToWriteField::Never), + EDITOR_FIELD_ENUM(Misc, label_type , comboBox_type , Misc::type ), + EDITOR_FIELD_NORM(Misc, tab_useFor , textEdit_useFor , Misc::useFor ), + EDITOR_FIELD_NORM(Misc, tab_notes , textEdit_notes , Misc::notes ), + // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ + EDITOR_FIELD_NORM(Misc, label_producer , lineEdit_producer , Misc::producer ), + EDITOR_FIELD_NORM(Misc, label_productId , lineEdit_productId , Misc::productId ), + }); return; } MiscEditor::~MiscEditor() = default; -void MiscEditor::writeFieldsToEditItem() { - this->m_editItem->setType(this->comboBox_type->getNonOptValue()); - this->m_editItem->setName (this->lineEdit_name ->text ()); - this->m_editItem->setUseFor (this->textEdit_useFor ->toPlainText ()); - this->m_editItem->setNotes (this->textEdit_notes ->toPlainText ()); - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - this->m_editItem->setProducer (this->lineEdit_producer ->text ()); - this->m_editItem->setProductId (this->lineEdit_productId ->text ()); - return; -} - -void MiscEditor::writeLateFieldsToEditItem() { - // - // Do this late to make sure we've the row in the inventory table (because total inventory amount isn't really an - // attribute of the Misc). - // - // Note that we do not need to store the value of comboBox_amountType. It merely controls the available unit for - // lineEdit_inventory - // - // Note that, if the inventory field is blank, we'll treat that as meaning "don't change the inventory" - // - if (!this->lineEdit_inventory->isEmptyOrBlank()) { - this->m_editItem->setTotalInventory(lineEdit_inventory->getNonOptCanonicalAmt()); - } - return; -} - -void MiscEditor::readFieldsFromEditItem(std::optional propName) { - if (!propName || *propName == PropertyNames::NamedEntity::name) { this->lineEdit_name ->setTextCursor(m_editItem->name ()); // Continues to next line - this->tabWidget_editor->setTabText(0, m_editItem->name()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Misc::type ) { this->comboBox_type ->setValue (m_editItem->type ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Ingredient::totalInventory) { this->lineEdit_inventory->setAmount(m_editItem->totalInventory()); - this->comboBox_amountType->autoSetFromControlledField(); - if (propName) { return; } } - if (!propName || *propName == PropertyNames::Misc::useFor ) { this->textEdit_useFor ->setPlainText (m_editItem->useFor ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Misc::notes ) { this->textEdit_notes ->setPlainText (m_editItem->notes ()); if (propName) { return; } } - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - if (!propName || *propName == PropertyNames::Misc::producer ) { this->lineEdit_producer ->setTextCursor(m_editItem->producer ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Misc::productId ) { this->lineEdit_productId->setTextCursor(m_editItem->productId()); if (propName) { return; } } - - this->label_id_value->setText(QString::number(m_editItem->key())); - return; -} - // Insert the boiler-plate stuff that we cannot do in EditorBase -EDITOR_COMMON_CODE(MiscEditor) +EDITOR_COMMON_CODE(Misc) diff --git a/src/editors/MiscEditor.h b/src/editors/MiscEditor.h index 9adea862..05f687ec 100644 --- a/src/editors/MiscEditor.h +++ b/src/editors/MiscEditor.h @@ -29,6 +29,7 @@ #include "editors/EditorBase.h" #include "model/Misc.h" +#define MiscEditorOptions EditorBaseOptions{ .nameTab = true, .idDisplay = true } /*! * \class MiscEditor * @@ -37,10 +38,12 @@ * See comment on EditorBase::connectSignalsAndSlots for why we need to have \c public, not \c private * inheritance from the Ui base. */ -class MiscEditor : public QDialog, public Ui::miscEditor, public EditorBase { +class MiscEditor : public QDialog, + public Ui::miscEditor, + public EditorBase { Q_OBJECT - EDITOR_COMMON_DECL(Misc) + EDITOR_COMMON_DECL(Misc, MiscEditorOptions) }; #endif diff --git a/src/editors/NamedMashEditor.h b/src/editors/NamedMashEditor.h index fa74871e..e2b4a6eb 100644 --- a/src/editors/NamedMashEditor.h +++ b/src/editors/NamedMashEditor.h @@ -43,7 +43,7 @@ class Equipment; * * \brief View/controller dialog for editing a mash. * - * See also \c MashEditor + * See also \c MashEditor (into which I think this should be subsumed!) */ class NamedMashEditor : public QDialog, public Ui::namedMashEditor { Q_OBJECT diff --git a/src/editors/StyleEditor.cpp b/src/editors/StyleEditor.cpp index c145e023..3eb735e5 100644 --- a/src/editors/StyleEditor.cpp +++ b/src/editors/StyleEditor.cpp @@ -25,115 +25,48 @@ #include "measurement/Unit.h" #include "sortFilterProxyModels/StyleSortFilterProxyModel.h" -StyleEditor::StyleEditor(QWidget* parent) : +StyleEditor::StyleEditor(QWidget* parent, QString const editorName) : QDialog{parent}, - EditorBase() { + EditorBase(editorName) { setupUi(this); - + this->postSetupUiInit({ + // Note that the Min / Max pairs of entry fields each share a label (which is shown to the left of both fields) + EDITOR_FIELD_NORM(Style, label_name , lineEdit_name , NamedEntity::name ), + EDITOR_FIELD_NORM(Style, label_category , lineEdit_category , Style::category ), + EDITOR_FIELD_NORM(Style, label_categoryNumber , lineEdit_categoryNumber , Style::categoryNumber ), + EDITOR_FIELD_NORM(Style, label_styleLetter , lineEdit_styleLetter , Style::styleLetter ), + EDITOR_FIELD_NORM(Style, label_styleGuide , lineEdit_styleGuide , Style::styleGuide ), + EDITOR_FIELD_NORM(Style, label_og , lineEdit_ogMin , Style::ogMin ), + EDITOR_FIELD_NORM(Style, label_og , lineEdit_ogMax , Style::ogMax ), + EDITOR_FIELD_NORM(Style, label_fg , lineEdit_fgMin , Style::fgMin ), + EDITOR_FIELD_NORM(Style, label_fg , lineEdit_fgMax , Style::fgMax ), + EDITOR_FIELD_NORM(Style, label_ibu , lineEdit_ibuMin , Style::ibuMin , 0), + EDITOR_FIELD_NORM(Style, label_ibu , lineEdit_ibuMax , Style::ibuMax , 0), + EDITOR_FIELD_NORM(Style, label_color , lineEdit_colorMin , Style::colorMin_srm ), + EDITOR_FIELD_NORM(Style, label_color , lineEdit_colorMax , Style::colorMax_srm ), + EDITOR_FIELD_NORM(Style, label_carb , lineEdit_carbMin , Style::carbMin_vol , 0), + EDITOR_FIELD_NORM(Style, label_carb , lineEdit_carbMax , Style::carbMax_vol , 0), + EDITOR_FIELD_NORM(Style, label_abv , lineEdit_abvMin , Style::abvMin_pct , 1), + EDITOR_FIELD_NORM(Style, label_abv , lineEdit_abvMax , Style::abvMax_pct , 1), + EDITOR_FIELD_ENUM(Style, label_type , comboBox_type , Style::type ), + EDITOR_FIELD_NORM(Style, tab_ingredients , textEdit_ingredients , Style::ingredients ), + EDITOR_FIELD_NORM(Style, tab_examples , textEdit_examples , Style::examples ), + EDITOR_FIELD_NORM(Style, tab_notes , textEdit_notes , Style::notes ), + // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ + EDITOR_FIELD_NORM(Style, tab_aroma , textEdit_aroma , Style::aroma ), + EDITOR_FIELD_NORM(Style, tab_appearance , textEdit_appearance , Style::appearance ), + EDITOR_FIELD_NORM(Style, tab_flavor , textEdit_flavor , Style::flavor ), + EDITOR_FIELD_NORM(Style, tab_mouthfeel , textEdit_mouthfeel , Style::mouthfeel ), + EDITOR_FIELD_NORM(Style, tab_overallImpression , textEdit_overallImpression, Style::overallImpression), + }); + + // EditorBase doesn't do this for us because we don't put the style name in the tab (possibly because it can be quite + // long). this->tabWidget_editor->tabBar()->setStyle(new BtHorizontalTabs); - - // Note that the Min / Max pairs of entry fields each share a label (which is shown to the left of both fields) - SMART_FIELD_INIT(StyleEditor, label_name , lineEdit_name , Style, PropertyNames::NamedEntity::name ); - SMART_FIELD_INIT(StyleEditor, label_category , lineEdit_category , Style, PropertyNames::Style::category ); - SMART_FIELD_INIT(StyleEditor, label_categoryNumber, lineEdit_categoryNumber, Style, PropertyNames::Style::categoryNumber ); - SMART_FIELD_INIT(StyleEditor, label_styleLetter , lineEdit_styleLetter , Style, PropertyNames::Style::styleLetter ); - SMART_FIELD_INIT(StyleEditor, label_styleGuide , lineEdit_styleGuide , Style, PropertyNames::Style::styleGuide ); - SMART_FIELD_INIT(StyleEditor, label_og , lineEdit_ogMin , Style, PropertyNames::Style::ogMin ); - SMART_FIELD_INIT(StyleEditor, label_og , lineEdit_ogMax , Style, PropertyNames::Style::ogMax ); - SMART_FIELD_INIT(StyleEditor, label_fg , lineEdit_fgMin , Style, PropertyNames::Style::fgMin ); - SMART_FIELD_INIT(StyleEditor, label_fg , lineEdit_fgMax , Style, PropertyNames::Style::fgMax ); - SMART_FIELD_INIT(StyleEditor, label_ibu , lineEdit_ibuMin , Style, PropertyNames::Style::ibuMin , 0); - SMART_FIELD_INIT(StyleEditor, label_ibu , lineEdit_ibuMax , Style, PropertyNames::Style::ibuMax , 0); - SMART_FIELD_INIT(StyleEditor, label_color , lineEdit_colorMin , Style, PropertyNames::Style::colorMin_srm ); - SMART_FIELD_INIT(StyleEditor, label_color , lineEdit_colorMax , Style, PropertyNames::Style::colorMax_srm ); - SMART_FIELD_INIT(StyleEditor, label_carb , lineEdit_carbMin , Style, PropertyNames::Style::carbMin_vol , 0); - SMART_FIELD_INIT(StyleEditor, label_carb , lineEdit_carbMax , Style, PropertyNames::Style::carbMax_vol , 0); - SMART_FIELD_INIT(StyleEditor, label_abv , lineEdit_abvMin , Style, PropertyNames::Style::abvMin_pct , 1); - SMART_FIELD_INIT(StyleEditor, label_abv , lineEdit_abvMax , Style, PropertyNames::Style::abvMax_pct , 1); - - BT_COMBO_BOX_INIT(StyleEditor, comboBox_type, Style, type); - - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - // [Nothing here as all new fields are text-only] - - this->connectSignalsAndSlots(); return; } StyleEditor::~StyleEditor() = default; -void StyleEditor::writeFieldsToEditItem() { - - m_editItem->setName (this->lineEdit_name ->text ()); - m_editItem->setCategory (this->lineEdit_category ->text ()); - m_editItem->setCategoryNumber(this->lineEdit_categoryNumber->text ()); - m_editItem->setStyleLetter (this->lineEdit_styleLetter ->text ()); - m_editItem->setStyleGuide (this->lineEdit_styleGuide ->text ()); - m_editItem->setType (this->comboBox_type ->getNonOptValue()); - m_editItem->setOgMin (this->lineEdit_ogMin ->getNonOptCanonicalQty ()); - m_editItem->setOgMax (this->lineEdit_ogMax ->getNonOptCanonicalQty ()); - m_editItem->setFgMin (this->lineEdit_fgMin ->getNonOptCanonicalQty ()); - m_editItem->setFgMax (this->lineEdit_fgMax ->getNonOptCanonicalQty ()); - m_editItem->setIbuMin (this->lineEdit_ibuMin ->getNonOptValue ()); - m_editItem->setIbuMax (this->lineEdit_ibuMax ->getNonOptValue ()); - m_editItem->setColorMin_srm (this->lineEdit_colorMin ->getNonOptCanonicalQty ()); - m_editItem->setColorMax_srm (this->lineEdit_colorMax ->getNonOptCanonicalQty ()); - m_editItem->setCarbMin_vol (this->lineEdit_carbMin ->getNonOptCanonicalQty ()); - m_editItem->setCarbMax_vol (this->lineEdit_carbMax ->getNonOptCanonicalQty ()); - m_editItem->setAbvMin_pct (this->lineEdit_abvMin ->getNonOptValue ()); - m_editItem->setAbvMax_pct (this->lineEdit_abvMax ->getNonOptValue ()); -/// this->m_editItem->setProfile (textEdit_profile ->toPlainText ()); - m_editItem->setIngredients (this->textEdit_ingredients ->toPlainText ()); - m_editItem->setExamples (this->textEdit_examples ->toPlainText ()); - m_editItem->setNotes (this->textEdit_notes ->toPlainText ()); - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - m_editItem->setAroma (this->textEdit_aroma ->toPlainText ()); - m_editItem->setAppearance (this->textEdit_appearance ->toPlainText ()); - m_editItem->setFlavor (this->textEdit_flavor ->toPlainText ()); - m_editItem->setMouthfeel (this->textEdit_mouthfeel ->toPlainText ()); - m_editItem->setOverallImpression(this->textEdit_overallImpression->toPlainText ()); - - return; -} - -void StyleEditor::writeLateFieldsToEditItem() { - // Nothing to do here for Style - return; -} - -void StyleEditor::readFieldsFromEditItem(std::optional propName) { - if (!propName || *propName == PropertyNames::NamedEntity::name ) { this->lineEdit_name ->setTextCursor(m_editItem->name ()); // Continues to next line - /* this->tabWidget_editor->setTabText(0, m_editItem->name()); */ if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::category ) { lineEdit_category ->setText (m_editItem->category ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::categoryNumber) { lineEdit_categoryNumber->setText (m_editItem->categoryNumber()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::styleLetter ) { lineEdit_styleLetter ->setText (m_editItem->styleLetter ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::styleGuide ) { lineEdit_styleGuide ->setText (m_editItem->styleGuide ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::type ) { comboBox_type ->setValue (m_editItem->type ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::ogMin ) { lineEdit_ogMin ->setQuantity (m_editItem->ogMin ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::ogMax ) { lineEdit_ogMax ->setQuantity (m_editItem->ogMax ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::fgMin ) { lineEdit_fgMin ->setQuantity (m_editItem->fgMin ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::fgMax ) { lineEdit_fgMax ->setQuantity (m_editItem->fgMax ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::ibuMin ) { lineEdit_ibuMin ->setQuantity (m_editItem->ibuMin ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::ibuMax ) { lineEdit_ibuMax ->setQuantity (m_editItem->ibuMax ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::colorMin_srm ) { lineEdit_colorMin ->setQuantity (m_editItem->colorMin_srm ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::colorMax_srm ) { lineEdit_colorMax ->setQuantity (m_editItem->colorMax_srm ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::carbMin_vol ) { lineEdit_carbMin ->setQuantity (m_editItem->carbMin_vol ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::carbMax_vol ) { lineEdit_carbMax ->setQuantity (m_editItem->carbMax_vol ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::abvMin_pct ) { lineEdit_abvMin ->setQuantity (m_editItem->abvMin_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::abvMax_pct ) { lineEdit_abvMax ->setQuantity (m_editItem->abvMax_pct ()); if (propName) { return; } } -/// if (!propName || *propName == PropertyNames::Style::profile ) { textEdit_profile ->setText (m_editItem->profile ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::ingredients ) { textEdit_ingredients ->setText (m_editItem->ingredients ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::examples ) { textEdit_examples ->setText (m_editItem->examples ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::notes ) { textEdit_notes ->setText (m_editItem->notes ()); if (propName) { return; } } - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - if (!propName || *propName == PropertyNames::Style::aroma ) { textEdit_aroma ->setText (m_editItem->aroma ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::appearance ) { textEdit_appearance ->setText (m_editItem->appearance ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::flavor ) { textEdit_flavor ->setText (m_editItem->flavor ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::mouthfeel ) { textEdit_mouthfeel ->setText (m_editItem->mouthfeel ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Style::overallImpression) { textEdit_overallImpression->setText (m_editItem->overallImpression()); if (propName) { return; } } - - return; -} - // Insert the boiler-plate stuff that we cannot do in EditorBase -EDITOR_COMMON_CODE(StyleEditor) +EDITOR_COMMON_CODE(Style) diff --git a/src/editors/StyleEditor.h b/src/editors/StyleEditor.h index 3a5d9d77..7450d7ed 100644 --- a/src/editors/StyleEditor.h +++ b/src/editors/StyleEditor.h @@ -27,6 +27,7 @@ #include "editors/EditorBase.h" #include "model/Style.h" +#define StyleEditorOptions EditorBaseOptions{ } /*! * \class StyleEditor * @@ -35,10 +36,12 @@ * See comment on EditorBase::connectSignalsAndSlots for why we need to have \c public, not \c private * inheritance from the Ui base. */ -class StyleEditor : public QDialog, public Ui::styleEditor, public EditorBase { +class StyleEditor : public QDialog, + public Ui::styleEditor, + public EditorBase { Q_OBJECT - EDITOR_COMMON_DECL(Style) + EDITOR_COMMON_DECL(Style, StyleEditorOptions) }; #endif diff --git a/src/editors/WaterEditor.cpp b/src/editors/WaterEditor.cpp index dc81dcd3..db1722a6 100644 --- a/src/editors/WaterEditor.cpp +++ b/src/editors/WaterEditor.cpp @@ -25,69 +25,31 @@ #include "database/ObjectStoreWrapper.h" #include "model/Water.h" -// This private implementation class holds all private non-virtual members of WaterEditor -class WaterEditor::impl { -public: - /** - * Constructor - */ - impl(QString const editorName) : editorName{editorName}, - observedWater{}, - editedWater{} { - return; - } - - /** - * Destructor - */ - ~impl() = default; - - QString const editorName; - - // This is the Water object we are "observing" and to which our edits will be committed if and when the user clicks - // OK - std::shared_ptr observedWater; - // This is a temporary copy of the "observed" Water that holds the live edits (which will be saved if the user clicks - // OK and lost if the user clicks Cancel) - std::unique_ptr editedWater; -}; - -WaterEditor::WaterEditor(QWidget *parent, - QString const editorName) : QDialog(parent), - pimpl{std::make_unique(editorName)} { - setupUi(this); - - SMART_FIELD_INIT(WaterEditor, label_ca , lineEdit_ca , Water, PropertyNames::Water::calcium_ppm , 2); - SMART_FIELD_INIT(WaterEditor, label_cl , lineEdit_cl , Water, PropertyNames::Water::chloride_ppm , 2); - SMART_FIELD_INIT(WaterEditor, label_mg , lineEdit_mg , Water, PropertyNames::Water::magnesium_ppm , 2); - SMART_FIELD_INIT(WaterEditor, label_so4 , lineEdit_so4 , Water, PropertyNames::Water::sulfate_ppm , 2); - SMART_FIELD_INIT(WaterEditor, label_na , lineEdit_na , Water, PropertyNames::Water::sodium_ppm , 2); - SMART_FIELD_INIT(WaterEditor, label_alk , lineEdit_alk , Water, PropertyNames::Water::alkalinity_ppm, 2); - SMART_FIELD_INIT(WaterEditor, label_pH , lineEdit_ph , Water, PropertyNames::Water::ph , 2); - // TODO: Finish adding extra fields below! - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ -// SMART_FIELD_INIT(WaterEditor, label_carbonate, lineEdit_carbonate, Water, PropertyNames::Water::carbonate_ppm , 2); -// SMART_FIELD_INIT(WaterEditor, label_potassium, lineEdit_potassium, Water, PropertyNames::Water::potassium_ppm , 2); -// SMART_FIELD_INIT(WaterEditor, label_iron , lineEdit_iron , Water, PropertyNames::Water::iron_ppm , 2); -// SMART_FIELD_INIT(WaterEditor, label_nitrate , lineEdit_nitrate , Water, PropertyNames::Water::nitrate_ppm , 2); -// SMART_FIELD_INIT(WaterEditor, label_nitrite , lineEdit_nitrite , Water, PropertyNames::Water::nitrite_ppm , 2); -// SMART_FIELD_INIT(WaterEditor, label_flouride , lineEdit_flouride , Water, PropertyNames::Water::flouride_ppm , 2); - - // .:TBD:. The QLineEdit::textEdited and QPlainTextEdit::textChanged signals below are sent somewhat more frequently - // than we really need - ie every time you type a character in the name or notes field. We should perhaps look at - // changing the corresponding field types... - connect(this->buttonBox, &QDialogButtonBox::accepted, this, &WaterEditor::saveAndClose ); - connect(this->buttonBox, &QDialogButtonBox::rejected, this, &WaterEditor::clearAndClose ); - connect(this->comboBox_alk, &QComboBox::currentTextChanged, this, &WaterEditor::inputFieldModified); - connect(this->lineEdit_alk, &SmartLineEdit::textModified, this, &WaterEditor::inputFieldModified); - connect(this->lineEdit_ca, &SmartLineEdit::textModified, this, &WaterEditor::inputFieldModified); - connect(this->lineEdit_cl, &SmartLineEdit::textModified, this, &WaterEditor::inputFieldModified); - connect(this->lineEdit_mg, &SmartLineEdit::textModified, this, &WaterEditor::inputFieldModified); - connect(this->lineEdit_na, &SmartLineEdit::textModified, this, &WaterEditor::inputFieldModified); - connect(this->lineEdit_name, &QLineEdit::textEdited, this, &WaterEditor::inputFieldModified); - connect(this->lineEdit_ph, &SmartLineEdit::textModified, this, &WaterEditor::inputFieldModified); - connect(this->lineEdit_so4, &SmartLineEdit::textModified, this, &WaterEditor::inputFieldModified); - connect(this->plainTextEdit_notes, &QPlainTextEdit::textChanged, this, &WaterEditor::inputFieldModified); +WaterEditor::WaterEditor(QWidget *parent, QString const editorName) : + QDialog(parent), + EditorBase(editorName) { + this->setupUi(this); + this->postSetupUiInit( + { + EDITOR_FIELD_NORM(Water, label_name , lineEdit_name , NamedEntity::name ), + EDITOR_FIELD_NORM(Water, label_notes , textEdit_notes , Water::notes ), + EDITOR_FIELD_NORM(Water, label_ca , lineEdit_ca , Water::calcium_ppm , 2), + EDITOR_FIELD_NORM(Water, label_cl , lineEdit_cl , Water::chloride_ppm , 2), + EDITOR_FIELD_NORM(Water, label_mg , lineEdit_mg , Water::magnesium_ppm , 2), + EDITOR_FIELD_NORM(Water, label_so4 , lineEdit_so4 , Water::sulfate_ppm , 2), + EDITOR_FIELD_NORM(Water, label_na , lineEdit_na , Water::sodium_ppm , 2), + EDITOR_FIELD_NORM(Water, label_alk , lineEdit_alk , Water::alkalinity_ppm , 2), + EDITOR_FIELD_NORM(Water, label_pH , lineEdit_ph , Water::ph , 2), + EDITOR_FIELD_NORM(Water, label_alkalinityAsHCO3, boolCombo_alkalinityAsHCO3, Water::alkalinityAsHCO3, tr("CaCO3"), tr("HCO3")), + // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ + EDITOR_FIELD_NORM(Water, label_carbonate , lineEdit_carbonate , Water::carbonate_ppm , 2), + EDITOR_FIELD_NORM(Water, label_potassium , lineEdit_potassium , Water::potassium_ppm , 2), + EDITOR_FIELD_NORM(Water, label_iron , lineEdit_iron , Water::iron_ppm , 2), + EDITOR_FIELD_NORM(Water, label_nitrate , lineEdit_nitrate , Water::nitrate_ppm , 2), + EDITOR_FIELD_NORM(Water, label_nitrite , lineEdit_nitrite , Water::nitrite_ppm , 2), + EDITOR_FIELD_NORM(Water, label_fluoride , lineEdit_fluoride , Water::fluoride_ppm , 2), + } + ); this->waterEditRadarChart->init( tr("PPM"), @@ -108,216 +70,39 @@ WaterEditor::WaterEditor(QWidget *parent, //WaterEditor::~WaterEditor() = default; WaterEditor::~WaterEditor() { qDebug() << Q_FUNC_INFO << "Cleaning up"; - if (this->pimpl->observedWater) { + if (this->m_editItem) { qDebug() << - Q_FUNC_INFO << this->pimpl->editorName << ": Was observing" << this->pimpl->observedWater->name() << - "#" << this->pimpl->observedWater->key() << " @" << static_cast(this->pimpl->observedWater.get()) << - " (use count" << this->pimpl->observedWater.use_count() << ")"; + Q_FUNC_INFO << this->m_editorName << ": Was observing" << this->m_editItem->name() << + "#" << this->m_editItem->key() << " @" << static_cast(this->m_editItem.get()) << + " (use count" << this->m_editItem.use_count() << ")"; } - if (this->pimpl->editedWater) { + if (this->m_liveEditItem) { qDebug() << - Q_FUNC_INFO << this->pimpl->editorName << ": Was editing" << this->pimpl->editedWater->name() << - "#" << this->pimpl->editedWater->key() << " @" << static_cast(this->pimpl->editedWater.get()); + Q_FUNC_INFO << this->m_editorName << ": Was editing" << this->m_liveEditItem->name() << + "#" << this->m_liveEditItem->key() << " @" << static_cast(this->m_liveEditItem.get()); } return; } -void WaterEditor::setWater(std::shared_ptr water) { - - if (this->pimpl->observedWater) { - qDebug() << - Q_FUNC_INFO << this->pimpl->editorName << ": Stop observing" << this->pimpl->observedWater->name() << - "#" << this->pimpl->observedWater->key() << " @" << static_cast(this->pimpl->observedWater.get()) << - " (use count" << this->pimpl->observedWater.use_count() << ")"; - disconnect(this->pimpl->observedWater.get(), nullptr, this, nullptr); - this->pimpl->observedWater.reset(); - } - - if (water) { - this->pimpl->observedWater = water; - qDebug() << - Q_FUNC_INFO << this->pimpl->editorName << ": Now observing" << this->pimpl->observedWater->name() << - "#" << this->pimpl->observedWater->key() << " @" << static_cast(this->pimpl->observedWater.get()) << - " (use count" << this->pimpl->observedWater.use_count() << ")"; - this->waterEditRadarChart->addSeries(tr("Current"), Qt::darkGreen, *this->pimpl->observedWater); - connect(this->pimpl->observedWater.get(), &NamedEntity::changed, this, &WaterEditor::changed); - - // Make a copy of the Water object we are observing - this->pimpl->editedWater = std::make_unique(*this->pimpl->observedWater); -/// this->pimpl->editedWater->setAmount(0.0); - this->waterEditRadarChart->addSeries(tr("Modified"), Qt::green, *this->pimpl->editedWater); +void WaterEditor::postSetEditItem() { + if (this->m_editItem) { + // Note that we don't need to remove the old series from any previous Water objects as the call to addSeries will + // replace them. + this->waterEditRadarChart->addSeries(tr("Current"), Qt::darkGreen, *this->m_editItem); - this->showChanges(); - } else { - qDebug() << Q_FUNC_INFO << this->pimpl->editorName << ": Observing Nothing"; + this->waterEditRadarChart->addSeries(tr("Modified"), Qt::green, *this->m_liveEditItem); } - return; } -void WaterEditor::newWater(QString folder) { - QString name = QInputDialog::getText(this, tr("Water name"), - tr("Water name:")); - if (name.isEmpty()) { - return; - } - - qDebug() << Q_FUNC_INFO << this->pimpl->editorName << ": Creating new Water, " << name; - - this->setWater(std::make_shared(name)); - if (!folder.isEmpty()) { - this->pimpl->observedWater->setFolder(folder); - } - - setVisible(true); - - return; -} - -void WaterEditor::showChanges(QMetaProperty const * prop) { - - if (!this->pimpl->observedWater) { - return; - } - - QString propName; - - bool updateAll = false; - - if (prop == nullptr) { - qDebug() << Q_FUNC_INFO << this->pimpl->editorName << ": Update all"; - updateAll = true; - } else { - propName = prop->name(); - qDebug() << Q_FUNC_INFO << this->pimpl->editorName << ": Changed" << propName; - } - - if (updateAll || propName == PropertyNames::NamedEntity::name ) { this->lineEdit_name->setText (this->pimpl->observedWater->name ()); if (!updateAll) { return; } } - if (updateAll || propName == PropertyNames::Water::calcium_ppm ) { this->lineEdit_ca ->setQuantity(this->pimpl->observedWater->calcium_ppm ()); if (!updateAll) { return; } } - if (updateAll || propName == PropertyNames::Water::magnesium_ppm ) { this->lineEdit_mg ->setQuantity(this->pimpl->observedWater->magnesium_ppm ()); if (!updateAll) { return; } } - if (updateAll || propName == PropertyNames::Water::sulfate_ppm ) { this->lineEdit_so4 ->setQuantity(this->pimpl->observedWater->sulfate_ppm ()); if (!updateAll) { return; } } - if (updateAll || propName == PropertyNames::Water::sodium_ppm ) { this->lineEdit_na ->setQuantity(this->pimpl->observedWater->sodium_ppm ()); if (!updateAll) { return; } } - if (updateAll || propName == PropertyNames::Water::chloride_ppm ) { this->lineEdit_cl ->setQuantity(this->pimpl->observedWater->chloride_ppm ()); if (!updateAll) { return; } } - if (updateAll || propName == PropertyNames::Water::bicarbonate_ppm ) { this->lineEdit_alk ->setQuantity(this->pimpl->observedWater->bicarbonate_ppm()); if (!updateAll) { return; } } - if (updateAll || propName == PropertyNames::Water::ph ) { this->lineEdit_ph ->setQuantity(this->pimpl->observedWater->ph ()); if (!updateAll) { return; } } - if (updateAll || propName == PropertyNames::Water::alkalinityAsHCO3) { - bool typeless = this->pimpl->observedWater->alkalinityAsHCO3(); - this->comboBox_alk->setCurrentIndex(comboBox_alk->findText(typeless ? "HCO3" : "CaCO3")); - if (!updateAll) { return; } - } - if (updateAll || propName == PropertyNames::Water::notes ) { this->plainTextEdit_notes->setPlainText(this->pimpl->observedWater->notes() ); if (!updateAll) { return; } } - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - if (updateAll || propName == PropertyNames::Water::carbonate_ppm ) { this->lineEdit_alk ->setQuantity(this->pimpl->observedWater->carbonate_ppm()); if (!updateAll) { return; } } - if (updateAll || propName == PropertyNames::Water::potassium_ppm ) { this->lineEdit_alk ->setQuantity(this->pimpl->observedWater->potassium_ppm()); if (!updateAll) { return; } } - if (updateAll || propName == PropertyNames::Water::iron_ppm ) { this->lineEdit_alk ->setQuantity(this->pimpl->observedWater->iron_ppm ()); if (!updateAll) { return; } } - if (updateAll || propName == PropertyNames::Water::nitrate_ppm ) { this->lineEdit_alk ->setQuantity(this->pimpl->observedWater->nitrate_ppm ()); if (!updateAll) { return; } } - if (updateAll || propName == PropertyNames::Water::nitrite_ppm ) { this->lineEdit_alk ->setQuantity(this->pimpl->observedWater->nitrite_ppm ()); if (!updateAll) { return; } } - if (updateAll || propName == PropertyNames::Water::flouride_ppm ) { this->lineEdit_alk ->setQuantity(this->pimpl->observedWater->flouride_ppm ()); if (!updateAll) { return; } } - return; -} - -void WaterEditor::inputFieldModified() { - // - // What we're doing here is, if one of the input fields on the dialog is modified, we update the corresponding - // field(s) on this->pimpl->editedWater and replot the radar chart. That way the user can see the "shape" of their - // changes in real time. +void WaterEditor::postInputFieldModified() { // - // When we come to close the window, depending on whether the user clicked "OK" or "Cancel" we then either copy the - // changes to the "observed" water (this->pimpl->observedWater) or discard them (resetting this->pimpl->editedWater - // to be the same as this->pimpl->observedWater). + // Strictly speaking we don't always need to replot the radar chart - eg if a text field changed it doesn't affect + // the chart - but, for the moment, we just keep things simple and always replot. // - QObject const * const signalSender = this->sender(); - // Usually leave the next line commented as otherwise get too much logging when user is typing in notes or name - // fields. -// qDebug() << Q_FUNC_INFO << this->pimpl->editorName << ": signal from" << signalSender; - if (signalSender && signalSender->parent() == this) { - // .:TBD:. Need to get to the bottom of the relationship between Water::alkalinity and Water::bicarbonate_ppm. It - // feels wrong that we just set both from the same input, but probably needs some more profound thought - // about what exactly correct behaviour should be. - if (signalSender == this->comboBox_alk) {this->pimpl->editedWater->setAlkalinityAsHCO3(this->comboBox_alk ->currentText() == QString("HCO3"));} - else if (signalSender == this->lineEdit_alk) {this->pimpl->editedWater->setBicarbonate_ppm (this->lineEdit_alk ->getNonOptCanonicalQty()); // NB continues on next line! - this->pimpl->editedWater->setAlkalinity_ppm (this->lineEdit_alk ->getNonOptCanonicalQty()); } - else if (signalSender == this->lineEdit_ca) {this->pimpl->editedWater->setCalcium_ppm (this->lineEdit_ca ->getNonOptCanonicalQty()); } - else if (signalSender == this->lineEdit_cl) {this->pimpl->editedWater->setChloride_ppm (this->lineEdit_cl ->getNonOptCanonicalQty()); } - else if (signalSender == this->lineEdit_mg) {this->pimpl->editedWater->setMagnesium_ppm (this->lineEdit_mg ->getNonOptCanonicalQty()); } - else if (signalSender == this->lineEdit_na) {this->pimpl->editedWater->setSodium_ppm (this->lineEdit_na ->getNonOptCanonicalQty()); } - else if (signalSender == this->lineEdit_name) {this->pimpl->editedWater->setName (this->lineEdit_name->text()); } - else if (signalSender == this->lineEdit_ph) {this->pimpl->editedWater->setPh (this->lineEdit_ph ->getNonOptCanonicalQty()); } - else if (signalSender == this->lineEdit_so4) {this->pimpl->editedWater->setSulfate_ppm (this->lineEdit_so4 ->getNonOptCanonicalQty()); } - else if (signalSender == this->plainTextEdit_notes) {this->pimpl->editedWater->setNotes (this->plainTextEdit_notes->toPlainText()); } - else { - // If we get here, it's probably a coding error - qWarning() << Q_FUNC_INFO << "Unrecognised child"; - } - - // - // Strictly speaking we don't always need to replot the radar chart - eg if a text field changed it doesn't affect - // the chart - but, for the moment, we just keep things simple and always replot. - // - this->waterEditRadarChart->replot(); - } - return; -} - -void WaterEditor::changed(QMetaProperty prop, QVariant /*val*/) { - if (sender() == this->pimpl->observedWater.get()) { - this->showChanges(&prop); - } - this->waterEditRadarChart->replot(); return; } -void WaterEditor::saveAndClose() { - qDebug() << Q_FUNC_INFO << this->pimpl->editorName; - if (!this->pimpl->observedWater) { - // For the moment, if we weren't given a Water object (via setWater) then we don't try to save any changes when - // the editor is closed. Arguably, if the user has actually filled in a bunch of data, then we should use that - // to create and save a new Water object. - qDebug() << Q_FUNC_INFO << "Save and close with no Water specified, so discarding any inputs"; - return; - } - - // Apply all the edits - if (this->pimpl->editedWater) { - *this->pimpl->observedWater = *this->pimpl->editedWater; - qDebug() << - Q_FUNC_INFO << this->pimpl->editorName << ": Applied edits to Water #" << this->pimpl->observedWater->key() << - ":" << this->pimpl->observedWater->name(); - } - - // - // TBD: When we're called from WaterDialog, it is that window that is responsible for adding new Water objects to the - // Recipe (which results in the Water object being saved in the DB). Saving the Water object means the current - // logic in WaterDialog won't pick up that it needs to be added to the Recipe. - // - if (this->pimpl->observedWater->key() < 0) { - qDebug() << Q_FUNC_INFO << "Writing new Water:" << this->pimpl->observedWater->name(); - ObjectStoreWrapper::insert(this->pimpl->observedWater); - } - - setVisible(false); - return; -} - -void WaterEditor::clearAndClose() { - qDebug() << Q_FUNC_INFO << this->pimpl->editorName; - - // At this point, we want to clear edits, but we _don't_ want to stop observing the Water that's been given to us as - // our creator (eg WaterDialog) may redisplay us without a repeat call to setWater. - - // This reverts all the input fields - this->showChanges(); - - // Revert all the edits in our temporary copy of the "observed" Water - if (this->pimpl->observedWater && this->pimpl->editedWater) { - *this->pimpl->editedWater = *this->pimpl->observedWater; - qDebug() << - Q_FUNC_INFO << this->pimpl->editorName << ": Discarded edits to Water #" << - this->pimpl->observedWater->key() << ":" << this->pimpl->observedWater->name(); - } - - setVisible(false); // Hide the window. - return; -} +EDITOR_COMMON_CODE(Water) diff --git a/src/editors/WaterEditor.h b/src/editors/WaterEditor.h index 917c2463..b9c98f24 100644 --- a/src/editors/WaterEditor.h +++ b/src/editors/WaterEditor.h @@ -29,40 +29,25 @@ #include "ui_waterEditor.h" -// Forward declarations. -class Water; +#include "editors/EditorBase.h" +#include "model/Water.h" +#define WaterEditorOptions EditorBaseOptions{ .liveEditItem = true } /*! * \class WaterEditor * * \brief View/controller class for creating and modifying water records. */ -class WaterEditor : public QDialog, public Ui::waterEditor { +class WaterEditor : public QDialog, + public Ui::waterEditor, + public EditorBase { Q_OBJECT public: - WaterEditor(QWidget *parent = nullptr, QString const editorName = "Unnamed"); - virtual ~WaterEditor(); - - /*! - * Sets the water we want to observe. - * - * \param water If \c nullptr then stop observing - */ - void setWater(std::shared_ptr water); - - void newWater(QString folder); - -public slots: - void showChanges(QMetaProperty const * prop = nullptr); - void inputFieldModified(); - void changed(QMetaProperty, QVariant); - void saveAndClose(); - void clearAndClose(); + EDITOR_COMMON_DECL(Water, WaterEditorOptions) private: - // Private implementation details - see https://herbsutter.com/gotw/_100/ - class impl; - std::unique_ptr pimpl; + void postSetEditItem(); + void postInputFieldModified(); }; #endif diff --git a/src/editors/YeastEditor.cpp b/src/editors/YeastEditor.cpp index ab46e5aa..377c8089 100644 --- a/src/editors/YeastEditor.cpp +++ b/src/editors/YeastEditor.cpp @@ -29,124 +29,52 @@ #include "database/ObjectStoreWrapper.h" #include "measurement/Unit.h" -YeastEditor::YeastEditor(QWidget * parent) : +YeastEditor::YeastEditor(QWidget * parent, QString const editorName) : QDialog(parent), - EditorBase() { + EditorBase(editorName) { setupUi(this); + this->postSetupUiInit( + { + // + // Write inventory late to make sure we've the row in the inventory table (because total inventory amount isn't + // really an attribute of the Yeast). + // + // Note that we do not need to store the value of comboBox_amountType. It merely controls the available unit + // for lineEdit_inventory + // + EDITOR_FIELD_NORM(Yeast, label_name , lineEdit_name , NamedEntity::name ), + EDITOR_FIELD_NORM(Yeast, label_laboratory , lineEdit_laboratory , Yeast::laboratory ), + EDITOR_FIELD_NORM(Yeast, label_inventory , lineEdit_inventory , Ingredient::totalInventory, 1, WhenToWriteField::Late), + EDITOR_FIELD_COPQ(Yeast, label_amountType , comboBox_amountType , Ingredient::totalInventory, lineEdit_inventory, WhenToWriteField::Never), + EDITOR_FIELD_NORM(Yeast, label_productId , lineEdit_productId , Yeast::productId ), + EDITOR_FIELD_NORM(Yeast, label_minTemperature, lineEdit_minTemperature , Yeast::minTemperature_c, 1 ), + EDITOR_FIELD_NORM(Yeast, label_maxTemperature, lineEdit_maxTemperature , Yeast::maxTemperature_c, 1 ), + EDITOR_FIELD_NORM(Yeast, label_maxReuse , lineEdit_maxReuse , Yeast::maxReuse ), + EDITOR_FIELD_ENUM(Yeast, label_type , comboBox_yeastType , Yeast::type ), + EDITOR_FIELD_ENUM(Yeast, label_form , comboBox_yeastForm , Yeast::form ), + EDITOR_FIELD_ENUM(Yeast, label_flocculation , comboBox_yeastFlocculation, Yeast::flocculation ), + EDITOR_FIELD_NORM(Yeast, tab_bestFor , textEdit_bestFor , Yeast::bestFor ), + EDITOR_FIELD_NORM(Yeast, tab_notes , textEdit_notes , Yeast::notes ), + // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ + EDITOR_FIELD_NORM(Yeast, label_alcoholTolerance , lineEdit_alcoholTolerance , Yeast::alcoholTolerance_pct , 1), + EDITOR_FIELD_NORM(Yeast, label_attenuationMin , lineEdit_attenuationMin , Yeast::attenuationMin_pct , 1), + EDITOR_FIELD_NORM(Yeast, label_attenuationMax , lineEdit_attenuationMax , Yeast::attenuationMax_pct , 1), + EDITOR_FIELD_NORM(Yeast, label_phenolicOffFlavorPositive, boolCombo_phenolicOffFlavorPositive, Yeast::phenolicOffFlavorPositive), + EDITOR_FIELD_NORM(Yeast, label_glucoamylasePositive , boolCombo_glucoamylasePositive , Yeast::glucoamylasePositive ), + EDITOR_FIELD_NORM(Yeast, label_killerProducingK1Toxin , boolCombo_killerProducingK1Toxin , Yeast::killerProducingK1Toxin ), + EDITOR_FIELD_NORM(Yeast, label_killerProducingK2Toxin , boolCombo_killerProducingK2Toxin , Yeast::killerProducingK2Toxin ), + EDITOR_FIELD_NORM(Yeast, label_killerProducingK28Toxin , boolCombo_killerProducingK28Toxin , Yeast::killerProducingK28Toxin ), + EDITOR_FIELD_NORM(Yeast, label_killerProducingKlusToxin , boolCombo_killerProducingKlusToxin , Yeast::killerProducingKlusToxin ), + EDITOR_FIELD_NORM(Yeast, label_killerNeutral , boolCombo_killerNeutral , Yeast::killerNeutral ) + + } + ); - this->tabWidget_editor->tabBar()->setStyle(new BtHorizontalTabs); - - SMART_FIELD_INIT(YeastEditor, label_name , lineEdit_name , Yeast, PropertyNames::NamedEntity::name ); - SMART_FIELD_INIT(YeastEditor, label_laboratory , lineEdit_laboratory , Yeast, PropertyNames::Yeast::laboratory ); - SMART_FIELD_INIT(YeastEditor, label_inventory , lineEdit_inventory , Yeast, PropertyNames::Ingredient::totalInventory, 1); - SMART_FIELD_INIT(YeastEditor, label_productId , lineEdit_productId , Yeast, PropertyNames::Yeast::productId ); - SMART_FIELD_INIT(YeastEditor, label_minTemperature, lineEdit_minTemperature, Yeast, PropertyNames::Yeast::minTemperature_c, 1); - SMART_FIELD_INIT(YeastEditor, label_maxTemperature, lineEdit_maxTemperature, Yeast, PropertyNames::Yeast::maxTemperature_c, 1); - SMART_FIELD_INIT(YeastEditor, label_maxReuse , lineEdit_maxReuse , Yeast, PropertyNames::Yeast::maxReuse ); - - BT_COMBO_BOX_INIT(HopEditor, comboBox_yeastType , Yeast, type ); - BT_COMBO_BOX_INIT(HopEditor, comboBox_yeastForm , Yeast, form ); - BT_COMBO_BOX_INIT(HopEditor, comboBox_yeastFlocculation, Yeast, flocculation); - - BT_COMBO_BOX_INIT_COPQ(YeastEditor, comboBox_amountType, Yeast, PropertyNames::Ingredient::totalInventory, lineEdit_inventory); - - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - - SMART_FIELD_INIT(YeastEditor, label_alcoholTolerance, lineEdit_alcoholTolerance, Yeast, PropertyNames::Yeast::alcoholTolerance_pct, 1); - SMART_FIELD_INIT(YeastEditor, label_attenuationMin , lineEdit_attenuationMin , Yeast, PropertyNames::Yeast::attenuationMin_pct , 1); - SMART_FIELD_INIT(YeastEditor, label_attenuationMax , lineEdit_attenuationMax , Yeast, PropertyNames::Yeast::attenuationMax_pct , 1); - - BT_BOOL_COMBO_BOX_INIT(YeastEditor, boolCombo_phenolicOffFlavorPositive, Yeast, phenolicOffFlavorPositive); - BT_BOOL_COMBO_BOX_INIT(YeastEditor, boolCombo_glucoamylasePositive , Yeast, glucoamylasePositive ); - BT_BOOL_COMBO_BOX_INIT(YeastEditor, boolCombo_killerProducingK1Toxin , Yeast, killerProducingK1Toxin ); - BT_BOOL_COMBO_BOX_INIT(YeastEditor, boolCombo_killerProducingK2Toxin , Yeast, killerProducingK2Toxin ); - BT_BOOL_COMBO_BOX_INIT(YeastEditor, boolCombo_killerProducingK28Toxin , Yeast, killerProducingK28Toxin ); - BT_BOOL_COMBO_BOX_INIT(YeastEditor, boolCombo_killerProducingKlusToxin , Yeast, killerProducingKlusToxin ); - BT_BOOL_COMBO_BOX_INIT(YeastEditor, boolCombo_killerNeutral , Yeast, killerNeutral ); - - this->connectSignalsAndSlots(); return; } YeastEditor::~YeastEditor() = default; -void YeastEditor::writeFieldsToEditItem() { - - this->m_editItem->setName (lineEdit_name ->text() ); - this->m_editItem->setType (comboBox_yeastType ->getNonOptValue() ); - this->m_editItem->setForm (comboBox_yeastForm ->getNonOptValue() ); - this->m_editItem->setLaboratory (lineEdit_laboratory ->text() ); - this->m_editItem->setProductId (lineEdit_productId ->text() ); - this->m_editItem->setMinTemperature_c(lineEdit_minTemperature ->getOptCanonicalQty() ); // ⮜⮜⮜ Optional in BeerXML ⮞⮞⮞ - this->m_editItem->setMaxTemperature_c(lineEdit_maxTemperature ->getOptCanonicalQty() ); // ⮜⮜⮜ Optional in BeerXML ⮞⮞⮞ - this->m_editItem->setFlocculation (comboBox_yeastFlocculation->getOptValue()); - this->m_editItem->setMaxReuse (lineEdit_maxReuse ->getOptValue() ); // ⮜⮜⮜ Optional in BeerXML ⮞⮞⮞ - this->m_editItem->setBestFor (textEdit_bestFor ->toPlainText() ); - this->m_editItem->setNotes (textEdit_notes ->toPlainText() ); - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - this->m_editItem->setAlcoholTolerance_pct (this->lineEdit_alcoholTolerance ->getOptValue()); - this->m_editItem->setAttenuationMin_pct (this->lineEdit_attenuationMin ->getOptValue()); - this->m_editItem->setAttenuationMax_pct (this->lineEdit_attenuationMax ->getOptValue()); - this->m_editItem->setPhenolicOffFlavorPositive(this->boolCombo_phenolicOffFlavorPositive->getOptBoolValue ()); - this->m_editItem->setGlucoamylasePositive (this->boolCombo_glucoamylasePositive ->getOptBoolValue ()); - this->m_editItem->setKillerProducingK1Toxin (this->boolCombo_killerProducingK1Toxin ->getOptBoolValue ()); - this->m_editItem->setKillerProducingK2Toxin (this->boolCombo_killerProducingK2Toxin ->getOptBoolValue ()); - this->m_editItem->setKillerProducingK28Toxin (this->boolCombo_killerProducingK28Toxin ->getOptBoolValue ()); - this->m_editItem->setKillerProducingKlusToxin (this->boolCombo_killerProducingKlusToxin ->getOptBoolValue ()); - this->m_editItem->setKillerNeutral (this->boolCombo_killerNeutral ->getOptBoolValue ()); - - return; -} - -void YeastEditor::writeLateFieldsToEditItem() { - // - // Do this late to make sure we've the row in the inventory table (because total inventory amount isn't really an - // attribute of the Misc). - // - // Note that we do not need to store the value of comboBox_amountType. It merely controls the available unit for - // lineEdit_inventory - // - // Note that, if the inventory field is blank, we'll treat that as meaning "don't change the inventory" - // - if (!this->lineEdit_inventory->isEmptyOrBlank()) { - this->m_editItem->setTotalInventory(lineEdit_inventory->getNonOptCanonicalAmt()); - } - return; -} - -void YeastEditor::readFieldsFromEditItem(std::optional propName) { - if (!propName || *propName == PropertyNames::NamedEntity::name ) { this->lineEdit_name ->setTextCursor(m_editItem->name ()); // Continues to next line - this->tabWidget_editor ->setTabText(0, m_editItem->name ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Yeast::type ) { this->comboBox_yeastType ->setValue (m_editItem->type ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Yeast::form ) { this->comboBox_yeastForm ->setValue (m_editItem->form ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Ingredient::totalInventory) { this->lineEdit_inventory ->setAmount (m_editItem->totalInventory ()); - this->comboBox_amountType ->autoSetFromControlledField(); - if (propName) { return; } } - if (!propName || *propName == PropertyNames::Yeast::laboratory ) { this->lineEdit_laboratory ->setText (m_editItem->laboratory ()); // Continues to next line - this->lineEdit_laboratory ->setCursorPosition(0) ; if (propName) { return; } } - if (!propName || *propName == PropertyNames::Yeast::productId ) { this->lineEdit_productId ->setText (m_editItem->productId ()); // Continues to next line - this->lineEdit_productId ->setCursorPosition(0) ; if (propName) { return; } } - if (!propName || *propName == PropertyNames::Yeast::minTemperature_c ) { this->lineEdit_minTemperature ->setQuantity (m_editItem->minTemperature_c()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Yeast::maxTemperature_c ) { this->lineEdit_maxTemperature ->setQuantity (m_editItem->maxTemperature_c()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Yeast::flocculation ) { this->comboBox_yeastFlocculation->setValue (m_editItem->flocculation ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Yeast::maxReuse ) { this->lineEdit_maxReuse ->setQuantity (m_editItem->maxReuse ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Yeast::bestFor ) { this->textEdit_bestFor ->setPlainText(m_editItem->bestFor ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Yeast::notes ) { this->textEdit_notes ->setPlainText(m_editItem->notes ()); if (propName) { return; } } - // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - if (!propName || *propName == PropertyNames::Yeast::alcoholTolerance_pct) { this->lineEdit_alcoholTolerance->setQuantity(m_editItem->alcoholTolerance_pct()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Yeast::attenuationMin_pct ) { this->lineEdit_attenuationMin ->setQuantity(m_editItem->attenuationMin_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Yeast::attenuationMax_pct ) { this->lineEdit_attenuationMax ->setQuantity(m_editItem->attenuationMax_pct ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Yeast::phenolicOffFlavorPositive) { this->boolCombo_phenolicOffFlavorPositive->setValue(m_editItem->phenolicOffFlavorPositive()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Yeast::glucoamylasePositive ) { this->boolCombo_glucoamylasePositive ->setValue(m_editItem->glucoamylasePositive ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Yeast::killerProducingK1Toxin ) { this->boolCombo_killerProducingK1Toxin ->setValue(m_editItem->killerProducingK1Toxin ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Yeast::killerProducingK2Toxin ) { this->boolCombo_killerProducingK2Toxin ->setValue(m_editItem->killerProducingK2Toxin ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Yeast::killerProducingK28Toxin ) { this->boolCombo_killerProducingK28Toxin ->setValue(m_editItem->killerProducingK28Toxin ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Yeast::killerProducingKlusToxin ) { this->boolCombo_killerProducingKlusToxin ->setValue(m_editItem->killerProducingKlusToxin ()); if (propName) { return; } } - if (!propName || *propName == PropertyNames::Yeast::killerNeutral ) { this->boolCombo_killerNeutral ->setValue(m_editItem->killerNeutral ()); if (propName) { return; } } - - this->label_id_value->setText(QString::number(m_editItem->key())); - return; -} // Insert the boiler-plate stuff that we cannot do in EditorBase -EDITOR_COMMON_CODE(YeastEditor) +EDITOR_COMMON_CODE(Yeast) diff --git a/src/editors/YeastEditor.h b/src/editors/YeastEditor.h index 6874468d..57c23105 100644 --- a/src/editors/YeastEditor.h +++ b/src/editors/YeastEditor.h @@ -29,6 +29,7 @@ #include "editors/EditorBase.h" #include "model/Yeast.h" +#define YeastEditorOptions EditorBaseOptions{ .nameTab = true, .idDisplay = true } /*! * \class YeastEditor * @@ -37,10 +38,12 @@ * See comment on EditorBase::connectSignalsAndSlots for why we need to have \c public, not \c private * inheritance from the Ui base. */ -class YeastEditor : public QDialog, public Ui::yeastEditor, public EditorBase { +class YeastEditor : public QDialog, + public Ui::yeastEditor, + public EditorBase { Q_OBJECT - EDITOR_COMMON_DECL(Yeast) + EDITOR_COMMON_DECL(Yeast, YeastEditorOptions) }; #endif diff --git a/src/model/Boil.cpp b/src/model/Boil.cpp index 9ce886ee..4ee143d3 100644 --- a/src/model/Boil.cpp +++ b/src/model/Boil.cpp @@ -67,16 +67,23 @@ Boil::Boil(QString name) : m_description {"" }, m_notes {"" }, m_preBoilSize_l{std::nullopt} { + + CONSTRUCTOR_END return; } Boil::Boil(NamedParameterBundle const & namedParameterBundle) : - NamedEntity {namedParameterBundle}, + NamedEntity {namedParameterBundle}, FolderBase{namedParameterBundle}, StepOwnerBase{}, SET_REGULAR_FROM_NPB (m_description , namedParameterBundle, PropertyNames::Boil::description ), SET_REGULAR_FROM_NPB (m_notes , namedParameterBundle, PropertyNames::Boil::notes ), SET_REGULAR_FROM_NPB (m_preBoilSize_l, namedParameterBundle, PropertyNames::Boil::preBoilSize_l) { + + // If we're being constructed from a BeerXML file, we use the property boilTime_mins for RECIPE > BOIL_TIME + SET_IF_PRESENT_FROM_NPB_NO_MV(Boil::setBoilTime_mins, namedParameterBundle, PropertyNames::Boil::boilTime_mins); + + CONSTRUCTOR_END return; } @@ -87,6 +94,8 @@ Boil::Boil(Boil const & other) : m_description {other.m_description }, m_notes {other.m_notes }, m_preBoilSize_l{other.m_preBoilSize_l} { + + CONSTRUCTOR_END return; } @@ -170,6 +179,7 @@ void Boil::ensureStandardProfile() { if (boilSteps.size() == 0 || boilSteps.at(0)->startTemp_c().value_or(100.0) > Boil::minimumBoilTemperature_c) { // We need to add a ramp-up (aka pre-boil) step auto preBoil = std::make_shared(tr("Pre-boil for %1").arg(recipe->name())); + preBoil->setDescription(tr("Automatically-generated pre-boil step for %1").arg(recipe->name())); // Get the starting temperature for the ramp-up from the end temperature of the mash double startingTemp = Boil::minimumBoilTemperature_c - 1.0; if (recipe->mash()) { @@ -188,6 +198,7 @@ void Boil::ensureStandardProfile() { if (boilSteps.size() < 2 || boilSteps.at(1)->startTemp_c().value_or(0.0) < Boil::minimumBoilTemperature_c) { // We need to add a main (aka boil proper) step auto mainBoil = std::make_shared(tr("Main boil for %1").arg(recipe->name())); + mainBoil->setDescription(tr("Automatically-generated boil proper step for %1").arg(recipe->name())); mainBoil->setStartTemp_c(100.0); mainBoil->setEndTemp_c(100.0); this->insertStep(mainBoil, 2); @@ -196,6 +207,7 @@ void Boil::ensureStandardProfile() { if (boilSteps.size() < 3 || boilSteps.at(2)->endTemp_c().value_or(100.0) > Boil::minimumBoilTemperature_c) { // We need to add a post-boil step auto postBoil = std::make_shared(tr("Post-boil for %1").arg(recipe->name())); + postBoil->setDescription(tr("Automatically-generated post-boil step for %1").arg(recipe->name())); double endingTemp = 30.0; if (recipe->fermentation()) { auto fs = recipe->fermentation()->fermentationSteps(); diff --git a/src/model/Boil.h b/src/model/Boil.h index 5313b9b0..735f92e4 100644 --- a/src/model/Boil.h +++ b/src/model/Boil.h @@ -35,7 +35,7 @@ AddPropertyName(description ) AddPropertyName(notes ) AddPropertyName(preBoilSize_l) -AddPropertyName(boilTime_mins) +AddPropertyName(boilTime_mins) // Only used for BeerXML AddPropertyName(boilSteps ) #undef AddPropertyName //=========================================== End of property name constants =========================================== diff --git a/src/model/BoilStep.cpp b/src/model/BoilStep.cpp index 72b06dbd..482e25c7 100644 --- a/src/model/BoilStep.cpp +++ b/src/model/BoilStep.cpp @@ -57,6 +57,8 @@ static_assert(std::is_base_of::value); BoilStep::BoilStep(QString name) : StepExtended {name }, m_chillingType{std::nullopt} { + + CONSTRUCTOR_END return; } @@ -67,12 +69,16 @@ BoilStep::BoilStep(NamedParameterBundle const & namedParameterBundle) : // always be because it's optional) then it is supported by this class. In other words, either it's not there, or // (if it is then) it's supported. Q_ASSERT(!namedParameterBundle.contains(PropertyNames::Step::rampTime_mins) || this->rampTimeIsSupported()); + + CONSTRUCTOR_END return; } BoilStep::BoilStep(BoilStep const & other) : StepExtended {other }, m_chillingType{other.m_chillingType} { + + CONSTRUCTOR_END return; } diff --git a/src/model/BrewNote.cpp b/src/model/BrewNote.cpp index 7978156f..c4682afd 100644 --- a/src/model/BrewNote.cpp +++ b/src/model/BrewNote.cpp @@ -100,12 +100,16 @@ TypeLookup const BrewNote::typeLookup { // Initializers BrewNote::BrewNote(QString name) : BrewNote(QDate(), name) { + + CONSTRUCTOR_END return; } BrewNote::BrewNote(Recipe const & recipe) : BrewNote(QDate(), "") { this->m_recipeId = recipe.key(); + + CONSTRUCTOR_END return; } @@ -142,6 +146,8 @@ BrewNote::BrewNote(QDate dateNow, QString const & name) : m_projPoints {0.0 }, m_projFermPoints {0.0 }, m_projAtten {0.0 } { + + CONSTRUCTOR_END return; } @@ -178,6 +184,8 @@ BrewNote::BrewNote(NamedParameterBundle const & namedParameterBundle) : SET_REGULAR_FROM_NPB (m_projPoints , namedParameterBundle, PropertyNames::BrewNote::projPoints ), SET_REGULAR_FROM_NPB (m_projFermPoints , namedParameterBundle, PropertyNames::BrewNote::projFermPoints ), SET_REGULAR_FROM_NPB (m_projAtten , namedParameterBundle, PropertyNames::BrewNote::projAtten ) { + + CONSTRUCTOR_END return; } @@ -213,6 +221,8 @@ BrewNote::BrewNote(BrewNote const & other) : m_projPoints {other.m_projPoints }, m_projFermPoints {other.m_projFermPoints }, m_projAtten {other.m_projAtten } { + + CONSTRUCTOR_END return; } diff --git a/src/model/Equipment.cpp b/src/model/Equipment.cpp index b732c001..8372539d 100644 --- a/src/model/Equipment.cpp +++ b/src/model/Equipment.cpp @@ -135,7 +135,7 @@ Equipment::Equipment(QString name) : m_kettleEvaporationPerHour_l {std::nullopt}, // Previously 4.0 m_boilTime_min {std::nullopt}, // Previously 60.0 m_calcBoilVolume {true }, - m_lauterTunDeadspaceLoss_l {0.0 }, + m_lauterTunDeadspaceLoss_l {0.0 }, m_topUpKettle_l {std::nullopt}, m_hopUtilization_pct {std::nullopt}, // Previously 100.0 m_kettleNotes {"" }, @@ -173,6 +173,8 @@ Equipment::Equipment(QString name) : m_fermenterNotes {"" }, m_agingVesselNotes {"" }, m_packagingVesselNotes {"" } { + + CONSTRUCTOR_END return; } @@ -190,52 +192,54 @@ Equipment::Equipment(NamedParameterBundle const & namedParameterBundle) : SET_REGULAR_FROM_NPB (m_kettleBoilSize_l , namedParameterBundle, PropertyNames::Equipment::kettleBoilSize_l ), SET_REGULAR_FROM_NPB (m_fermenterBatchSize_l , namedParameterBundle, PropertyNames::Equipment::fermenterBatchSize_l ), SET_REGULAR_FROM_NPB (m_mashTunVolume_l , namedParameterBundle, PropertyNames::Equipment::mashTunVolume_l ), - SET_REGULAR_FROM_NPB (m_mashTunWeight_kg , namedParameterBundle, PropertyNames::Equipment::mashTunWeight_kg ), - SET_REGULAR_FROM_NPB (m_mashTunSpecificHeat_calGC , namedParameterBundle, PropertyNames::Equipment::mashTunSpecificHeat_calGC ), - SET_REGULAR_FROM_NPB (m_topUpWater_l , namedParameterBundle, PropertyNames::Equipment::topUpWater_l ), + SET_REGULAR_FROM_NPB (m_mashTunWeight_kg , namedParameterBundle, PropertyNames::Equipment::mashTunWeight_kg , std::nullopt), + SET_REGULAR_FROM_NPB (m_mashTunSpecificHeat_calGC , namedParameterBundle, PropertyNames::Equipment::mashTunSpecificHeat_calGC , std::nullopt), + SET_REGULAR_FROM_NPB (m_topUpWater_l , namedParameterBundle, PropertyNames::Equipment::topUpWater_l , std::nullopt), SET_REGULAR_FROM_NPB (m_kettleTrubChillerLoss_l , namedParameterBundle, PropertyNames::Equipment::kettleTrubChillerLoss_l ), SET_REGULAR_FROM_NPB (m_evapRate_pctHr , namedParameterBundle, PropertyNames::Equipment::evapRate_pctHr ), - SET_REGULAR_FROM_NPB (m_kettleEvaporationPerHour_l , namedParameterBundle, PropertyNames::Equipment::kettleEvaporationPerHour_l , 4.0 ), - SET_REGULAR_FROM_NPB (m_boilTime_min , namedParameterBundle, PropertyNames::Equipment::boilTime_min ), + SET_REGULAR_FROM_NPB (m_kettleEvaporationPerHour_l , namedParameterBundle, PropertyNames::Equipment::kettleEvaporationPerHour_l , std::nullopt), + SET_REGULAR_FROM_NPB (m_boilTime_min , namedParameterBundle, PropertyNames::Equipment::boilTime_min , std::nullopt), SET_REGULAR_FROM_NPB (m_calcBoilVolume , namedParameterBundle, PropertyNames::Equipment::calcBoilVolume ), SET_REGULAR_FROM_NPB (m_lauterTunDeadspaceLoss_l , namedParameterBundle, PropertyNames::Equipment::lauterTunDeadspaceLoss_l ), - SET_REGULAR_FROM_NPB (m_topUpKettle_l , namedParameterBundle, PropertyNames::Equipment::topUpKettle_l ), - SET_REGULAR_FROM_NPB (m_hopUtilization_pct , namedParameterBundle, PropertyNames::Equipment::hopUtilization_pct ), - SET_REGULAR_FROM_NPB (m_kettleNotes , namedParameterBundle, PropertyNames::Equipment::kettleNotes ), - SET_REGULAR_FROM_NPB (m_mashTunGrainAbsorption_LKg , namedParameterBundle, PropertyNames::Equipment::mashTunGrainAbsorption_LKg , 1.086), + SET_REGULAR_FROM_NPB (m_topUpKettle_l , namedParameterBundle, PropertyNames::Equipment::topUpKettle_l , std::nullopt), + SET_REGULAR_FROM_NPB (m_hopUtilization_pct , namedParameterBundle, PropertyNames::Equipment::hopUtilization_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_kettleNotes , namedParameterBundle, PropertyNames::Equipment::kettleNotes , ""), + SET_REGULAR_FROM_NPB (m_mashTunGrainAbsorption_LKg , namedParameterBundle, PropertyNames::Equipment::mashTunGrainAbsorption_LKg , std::nullopt), SET_REGULAR_FROM_NPB (m_boilingPoint_c , namedParameterBundle, PropertyNames::Equipment::boilingPoint_c , 100.0), SET_REGULAR_FROM_NPB (m_kettleInternalDiameter_cm , namedParameterBundle, PropertyNames::Equipment::kettleInternalDiameter_cm , std::nullopt), SET_REGULAR_FROM_NPB (m_kettleOpeningDiameter_cm , namedParameterBundle, PropertyNames::Equipment::kettleOpeningDiameter_cm , std::nullopt), // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - SET_REGULAR_FROM_NPB (m_hltType , namedParameterBundle, PropertyNames::Equipment::hltType ), - SET_REGULAR_FROM_NPB (m_mashTunType , namedParameterBundle, PropertyNames::Equipment::mashTunType ), - SET_REGULAR_FROM_NPB (m_lauterTunType , namedParameterBundle, PropertyNames::Equipment::lauterTunType ), - SET_REGULAR_FROM_NPB (m_kettleType , namedParameterBundle, PropertyNames::Equipment::kettleType ), - SET_REGULAR_FROM_NPB (m_fermenterType , namedParameterBundle, PropertyNames::Equipment::fermenterType ), - SET_REGULAR_FROM_NPB (m_agingVesselType , namedParameterBundle, PropertyNames::Equipment::agingVesselType ), - SET_REGULAR_FROM_NPB (m_packagingVesselType , namedParameterBundle, PropertyNames::Equipment::packagingVesselType ), - SET_REGULAR_FROM_NPB (m_hltVolume_l , namedParameterBundle, PropertyNames::Equipment::hltVolume_l ), - SET_REGULAR_FROM_NPB (m_lauterTunVolume_l , namedParameterBundle, PropertyNames::Equipment::lauterTunVolume_l ), - SET_REGULAR_FROM_NPB (m_agingVesselVolume_l , namedParameterBundle, PropertyNames::Equipment::agingVesselVolume_l ), - SET_REGULAR_FROM_NPB (m_packagingVesselVolume_l , namedParameterBundle, PropertyNames::Equipment::packagingVesselVolume_l ), - SET_REGULAR_FROM_NPB (m_hltLoss_l , namedParameterBundle, PropertyNames::Equipment::hltLoss_l ), - SET_REGULAR_FROM_NPB (m_mashTunLoss_l , namedParameterBundle, PropertyNames::Equipment::mashTunLoss_l ), - SET_REGULAR_FROM_NPB (m_fermenterLoss_l , namedParameterBundle, PropertyNames::Equipment::fermenterLoss_l ), - SET_REGULAR_FROM_NPB (m_agingVesselLoss_l , namedParameterBundle, PropertyNames::Equipment::agingVesselLoss_l ), - SET_REGULAR_FROM_NPB (m_packagingVesselLoss_l , namedParameterBundle, PropertyNames::Equipment::packagingVesselLoss_l ), - SET_REGULAR_FROM_NPB (m_kettleOutflowPerMinute_l , namedParameterBundle, PropertyNames::Equipment::kettleOutflowPerMinute_l ), - SET_REGULAR_FROM_NPB (m_hltWeight_kg , namedParameterBundle, PropertyNames::Equipment::hltWeight_kg ), - SET_REGULAR_FROM_NPB (m_lauterTunWeight_kg , namedParameterBundle, PropertyNames::Equipment::lauterTunWeight_kg ), - SET_REGULAR_FROM_NPB (m_kettleWeight_kg , namedParameterBundle, PropertyNames::Equipment::kettleWeight_kg ), - SET_REGULAR_FROM_NPB (m_hltSpecificHeat_calGC , namedParameterBundle, PropertyNames::Equipment::hltSpecificHeat_calGC ), - SET_REGULAR_FROM_NPB (m_lauterTunSpecificHeat_calGC, namedParameterBundle, PropertyNames::Equipment::lauterTunSpecificHeat_calGC), - SET_REGULAR_FROM_NPB (m_kettleSpecificHeat_calGC , namedParameterBundle, PropertyNames::Equipment::kettleSpecificHeat_calGC ), - SET_REGULAR_FROM_NPB (m_hltNotes , namedParameterBundle, PropertyNames::Equipment::hltNotes ), - SET_REGULAR_FROM_NPB (m_mashTunNotes , namedParameterBundle, PropertyNames::Equipment::mashTunNotes ), - SET_REGULAR_FROM_NPB (m_lauterTunNotes , namedParameterBundle, PropertyNames::Equipment::lauterTunNotes ), - SET_REGULAR_FROM_NPB (m_fermenterNotes , namedParameterBundle, PropertyNames::Equipment::fermenterNotes ), - SET_REGULAR_FROM_NPB (m_agingVesselNotes , namedParameterBundle, PropertyNames::Equipment::agingVesselNotes ), - SET_REGULAR_FROM_NPB (m_packagingVesselNotes , namedParameterBundle, PropertyNames::Equipment::packagingVesselNotes ) { + SET_REGULAR_FROM_NPB (m_hltType , namedParameterBundle, PropertyNames::Equipment::hltType , "" ), + SET_REGULAR_FROM_NPB (m_mashTunType , namedParameterBundle, PropertyNames::Equipment::mashTunType , "" ), + SET_REGULAR_FROM_NPB (m_lauterTunType , namedParameterBundle, PropertyNames::Equipment::lauterTunType , "" ), + SET_REGULAR_FROM_NPB (m_kettleType , namedParameterBundle, PropertyNames::Equipment::kettleType , "" ), + SET_REGULAR_FROM_NPB (m_fermenterType , namedParameterBundle, PropertyNames::Equipment::fermenterType , "" ), + SET_REGULAR_FROM_NPB (m_agingVesselType , namedParameterBundle, PropertyNames::Equipment::agingVesselType , "" ), + SET_REGULAR_FROM_NPB (m_packagingVesselType , namedParameterBundle, PropertyNames::Equipment::packagingVesselType , "" ), + SET_REGULAR_FROM_NPB (m_hltVolume_l , namedParameterBundle, PropertyNames::Equipment::hltVolume_l , 0.0 ), + SET_REGULAR_FROM_NPB (m_lauterTunVolume_l , namedParameterBundle, PropertyNames::Equipment::lauterTunVolume_l , 0.0 ), + SET_REGULAR_FROM_NPB (m_agingVesselVolume_l , namedParameterBundle, PropertyNames::Equipment::agingVesselVolume_l , 0.0 ), + SET_REGULAR_FROM_NPB (m_packagingVesselVolume_l , namedParameterBundle, PropertyNames::Equipment::packagingVesselVolume_l , 0.0 ), + SET_REGULAR_FROM_NPB (m_hltLoss_l , namedParameterBundle, PropertyNames::Equipment::hltLoss_l , 0.0 ), + SET_REGULAR_FROM_NPB (m_mashTunLoss_l , namedParameterBundle, PropertyNames::Equipment::mashTunLoss_l , 0.0 ), + SET_REGULAR_FROM_NPB (m_fermenterLoss_l , namedParameterBundle, PropertyNames::Equipment::fermenterLoss_l , 0.0 ), + SET_REGULAR_FROM_NPB (m_agingVesselLoss_l , namedParameterBundle, PropertyNames::Equipment::agingVesselLoss_l , 0.0 ), + SET_REGULAR_FROM_NPB (m_packagingVesselLoss_l , namedParameterBundle, PropertyNames::Equipment::packagingVesselLoss_l , 0.0 ), + SET_REGULAR_FROM_NPB (m_kettleOutflowPerMinute_l , namedParameterBundle, PropertyNames::Equipment::kettleOutflowPerMinute_l , std::nullopt), + SET_REGULAR_FROM_NPB (m_hltWeight_kg , namedParameterBundle, PropertyNames::Equipment::hltWeight_kg , std::nullopt), + SET_REGULAR_FROM_NPB (m_lauterTunWeight_kg , namedParameterBundle, PropertyNames::Equipment::lauterTunWeight_kg , std::nullopt), + SET_REGULAR_FROM_NPB (m_kettleWeight_kg , namedParameterBundle, PropertyNames::Equipment::kettleWeight_kg , std::nullopt), + SET_REGULAR_FROM_NPB (m_hltSpecificHeat_calGC , namedParameterBundle, PropertyNames::Equipment::hltSpecificHeat_calGC , std::nullopt), + SET_REGULAR_FROM_NPB (m_lauterTunSpecificHeat_calGC, namedParameterBundle, PropertyNames::Equipment::lauterTunSpecificHeat_calGC, std::nullopt), + SET_REGULAR_FROM_NPB (m_kettleSpecificHeat_calGC , namedParameterBundle, PropertyNames::Equipment::kettleSpecificHeat_calGC , std::nullopt), + SET_REGULAR_FROM_NPB (m_hltNotes , namedParameterBundle, PropertyNames::Equipment::hltNotes , "" ), + SET_REGULAR_FROM_NPB (m_mashTunNotes , namedParameterBundle, PropertyNames::Equipment::mashTunNotes , "" ), + SET_REGULAR_FROM_NPB (m_lauterTunNotes , namedParameterBundle, PropertyNames::Equipment::lauterTunNotes , "" ), + SET_REGULAR_FROM_NPB (m_fermenterNotes , namedParameterBundle, PropertyNames::Equipment::fermenterNotes , "" ), + SET_REGULAR_FROM_NPB (m_agingVesselNotes , namedParameterBundle, PropertyNames::Equipment::agingVesselNotes , "" ), + SET_REGULAR_FROM_NPB (m_packagingVesselNotes , namedParameterBundle, PropertyNames::Equipment::packagingVesselNotes , "" ) { + + CONSTRUCTOR_END return; } @@ -291,6 +295,8 @@ Equipment::Equipment(Equipment const & other) : m_fermenterNotes {other.m_fermenterNotes }, m_agingVesselNotes {other.m_agingVesselNotes }, m_packagingVesselNotes {other.m_packagingVesselNotes } { + + CONSTRUCTOR_END return; } diff --git a/src/model/Fermentable.cpp b/src/model/Fermentable.cpp index 4f7fc65e..88db512c 100644 --- a/src/model/Fermentable.cpp +++ b/src/model/Fermentable.cpp @@ -224,6 +224,8 @@ Fermentable::Fermentable(QString name) : m_fan_ppm {std::nullopt }, m_fermentability_pct {std::nullopt }, m_betaGlucan_ppm {std::nullopt } { + + CONSTRUCTOR_END return; } @@ -242,27 +244,28 @@ Fermentable::Fermentable(NamedParameterBundle const & namedParameterBundle) : SET_REGULAR_FROM_NPB (m_recommendMash , namedParameterBundle, PropertyNames::Fermentable::recommendMash ), SET_REGULAR_FROM_NPB (m_ibuGalPerLb , namedParameterBundle, PropertyNames::Fermentable::ibuGalPerLb ), // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - SET_OPT_ENUM_FROM_NPB(m_grainGroup, Fermentable::GrainGroup, namedParameterBundle, PropertyNames::Fermentable::grainGroup ), - SET_REGULAR_FROM_NPB (m_producer , namedParameterBundle, PropertyNames::Fermentable::producer ), - SET_REGULAR_FROM_NPB (m_productId , namedParameterBundle, PropertyNames::Fermentable::productId ), - SET_REGULAR_FROM_NPB (m_fineGrindYield_pct , namedParameterBundle, PropertyNames::Fermentable::fineGrindYield_pct ), - SET_REGULAR_FROM_NPB (m_coarseGrindYield_pct , namedParameterBundle, PropertyNames::Fermentable::coarseGrindYield_pct ), - SET_REGULAR_FROM_NPB (m_potentialYield_sg , namedParameterBundle, PropertyNames::Fermentable::potentialYield_sg ), - SET_REGULAR_FROM_NPB (m_alphaAmylase_dextUnits , namedParameterBundle, PropertyNames::Fermentable::alphaAmylase_dextUnits ), - SET_REGULAR_FROM_NPB (m_kolbachIndex_pct , namedParameterBundle, PropertyNames::Fermentable::kolbachIndex_pct ), - SET_REGULAR_FROM_NPB (m_hardnessPrpGlassy_pct , namedParameterBundle, PropertyNames::Fermentable::hardnessPrpGlassy_pct ), - SET_REGULAR_FROM_NPB (m_hardnessPrpHalf_pct , namedParameterBundle, PropertyNames::Fermentable::hardnessPrpHalf_pct ), - SET_REGULAR_FROM_NPB (m_hardnessPrpMealy_pct , namedParameterBundle, PropertyNames::Fermentable::hardnessPrpMealy_pct ), - SET_REGULAR_FROM_NPB (m_kernelSizePrpPlump_pct , namedParameterBundle, PropertyNames::Fermentable::kernelSizePrpPlump_pct ), - SET_REGULAR_FROM_NPB (m_kernelSizePrpThin_pct , namedParameterBundle, PropertyNames::Fermentable::kernelSizePrpThin_pct ), - SET_REGULAR_FROM_NPB (m_friability_pct , namedParameterBundle, PropertyNames::Fermentable::friability_pct ), - SET_REGULAR_FROM_NPB (m_di_ph , namedParameterBundle, PropertyNames::Fermentable::di_ph ), - SET_REGULAR_FROM_NPB (m_viscosity_cP , namedParameterBundle, PropertyNames::Fermentable::viscosity_cP ), - SET_REGULAR_FROM_NPB (m_dmsP_ppm , namedParameterBundle, PropertyNames::Fermentable::dmsP_ppm ), - SET_REGULAR_FROM_NPB (m_fan_ppm , namedParameterBundle, PropertyNames::Fermentable::fan_ppm ), - SET_REGULAR_FROM_NPB (m_fermentability_pct , namedParameterBundle, PropertyNames::Fermentable::fermentability_pct ), - SET_REGULAR_FROM_NPB (m_betaGlucan_ppm , namedParameterBundle, PropertyNames::Fermentable::betaGlucan_ppm ) { + SET_OPT_ENUM_FROM_NPB(m_grainGroup, Fermentable::GrainGroup, namedParameterBundle, PropertyNames::Fermentable::grainGroup ), + SET_REGULAR_FROM_NPB (m_producer , namedParameterBundle, PropertyNames::Fermentable::producer , QString() ), + SET_REGULAR_FROM_NPB (m_productId , namedParameterBundle, PropertyNames::Fermentable::productId , QString() ), + SET_REGULAR_FROM_NPB (m_fineGrindYield_pct , namedParameterBundle, PropertyNames::Fermentable::fineGrindYield_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_coarseGrindYield_pct , namedParameterBundle, PropertyNames::Fermentable::coarseGrindYield_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_potentialYield_sg , namedParameterBundle, PropertyNames::Fermentable::potentialYield_sg , std::nullopt), + SET_REGULAR_FROM_NPB (m_alphaAmylase_dextUnits , namedParameterBundle, PropertyNames::Fermentable::alphaAmylase_dextUnits, std::nullopt), + SET_REGULAR_FROM_NPB (m_kolbachIndex_pct , namedParameterBundle, PropertyNames::Fermentable::kolbachIndex_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_hardnessPrpGlassy_pct , namedParameterBundle, PropertyNames::Fermentable::hardnessPrpGlassy_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_hardnessPrpHalf_pct , namedParameterBundle, PropertyNames::Fermentable::hardnessPrpHalf_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_hardnessPrpMealy_pct , namedParameterBundle, PropertyNames::Fermentable::hardnessPrpMealy_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_kernelSizePrpPlump_pct , namedParameterBundle, PropertyNames::Fermentable::kernelSizePrpPlump_pct, std::nullopt), + SET_REGULAR_FROM_NPB (m_kernelSizePrpThin_pct , namedParameterBundle, PropertyNames::Fermentable::kernelSizePrpThin_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_friability_pct , namedParameterBundle, PropertyNames::Fermentable::friability_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_di_ph , namedParameterBundle, PropertyNames::Fermentable::di_ph , std::nullopt), + SET_REGULAR_FROM_NPB (m_viscosity_cP , namedParameterBundle, PropertyNames::Fermentable::viscosity_cP , std::nullopt), + SET_REGULAR_FROM_NPB (m_dmsP_ppm , namedParameterBundle, PropertyNames::Fermentable::dmsP_ppm , std::nullopt), + SET_REGULAR_FROM_NPB (m_fan_ppm , namedParameterBundle, PropertyNames::Fermentable::fan_ppm , std::nullopt), + SET_REGULAR_FROM_NPB (m_fermentability_pct , namedParameterBundle, PropertyNames::Fermentable::fermentability_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_betaGlucan_ppm , namedParameterBundle, PropertyNames::Fermentable::betaGlucan_ppm , std::nullopt) { + CONSTRUCTOR_END return; } @@ -301,6 +304,8 @@ Fermentable::Fermentable(Fermentable const & other) : m_fan_ppm {other.m_fan_ppm }, m_fermentability_pct {other.m_fermentability_pct }, m_betaGlucan_ppm {other.m_betaGlucan_ppm } { + + CONSTRUCTOR_END return; } diff --git a/src/model/Fermentation.cpp b/src/model/Fermentation.cpp index f97e7fd3..e63017ca 100644 --- a/src/model/Fermentation.cpp +++ b/src/model/Fermentation.cpp @@ -61,15 +61,19 @@ Fermentation::Fermentation(QString name) : StepOwnerBase{}, m_description {"" }, m_notes {"" } { + + CONSTRUCTOR_END return; } Fermentation::Fermentation(NamedParameterBundle const & namedParameterBundle) : - NamedEntity {namedParameterBundle}, + NamedEntity {namedParameterBundle}, FolderBase{namedParameterBundle}, StepOwnerBase{}, - SET_REGULAR_FROM_NPB (m_description , namedParameterBundle, PropertyNames::Fermentation::description ), - SET_REGULAR_FROM_NPB (m_notes , namedParameterBundle, PropertyNames::Fermentation::notes ) { + SET_REGULAR_FROM_NPB (m_description, namedParameterBundle, PropertyNames::Fermentation::description), + SET_REGULAR_FROM_NPB (m_notes , namedParameterBundle, PropertyNames::Fermentation::notes ) { + + CONSTRUCTOR_END return; } @@ -79,6 +83,8 @@ Fermentation::Fermentation(Fermentation const & other) : StepOwnerBase{other}, m_description {other.m_description }, m_notes {other.m_notes } { + + CONSTRUCTOR_END return; } diff --git a/src/model/Fermentation.h b/src/model/Fermentation.h index 8cf4cbf0..8a554916 100644 --- a/src/model/Fermentation.h +++ b/src/model/Fermentation.h @@ -76,9 +76,9 @@ class Fermentation : public NamedEntity, //=================================================== PROPERTIES ==================================================== //! \brief Folder. See model/FolderBase for implementation of the getter & setter. - Q_PROPERTY(QString folder READ folder WRITE setFolder) - Q_PROPERTY(QString description READ description WRITE setDescription) - Q_PROPERTY(QString notes READ notes WRITE setNotes ) + Q_PROPERTY(QString folder READ folder WRITE setFolder ) + Q_PROPERTY(QString description READ description WRITE setDescription) + Q_PROPERTY(QString notes READ notes WRITE setNotes ) //! \brief The individual fermentation steps. (See \c StepOwnerBase for getter/setter implementation.) Q_PROPERTY(QList> fermentationSteps READ fermentationSteps WRITE setFermentationSteps STORED false) diff --git a/src/model/FermentationStep.cpp b/src/model/FermentationStep.cpp index 971e2b8e..291a348c 100644 --- a/src/model/FermentationStep.cpp +++ b/src/model/FermentationStep.cpp @@ -49,6 +49,8 @@ FermentationStep::FermentationStep(QString name) : StepExtended{name}, m_freeRise {std::nullopt}, m_vessel {""} { + + CONSTRUCTOR_END return; } @@ -58,6 +60,8 @@ FermentationStep::FermentationStep(NamedParameterBundle const & namedParameterBu SET_REGULAR_FROM_NPB (m_vessel , namedParameterBundle, PropertyNames::FermentationStep::vessel , QString() ) { // See comment in Step constructor Q_ASSERT(this->rampTimeIsSupported() == namedParameterBundle.contains(PropertyNames::Step::rampTime_mins)); + + CONSTRUCTOR_END return; } @@ -65,6 +69,8 @@ FermentationStep::FermentationStep(FermentationStep const & other) : StepExtended{other }, m_freeRise {other.m_freeRise}, m_vessel {other.m_vessel } { + + CONSTRUCTOR_END return; } diff --git a/src/model/Hop.cpp b/src/model/Hop.cpp index 05a0308e..0fd44e5c 100644 --- a/src/model/Hop.cpp +++ b/src/model/Hop.cpp @@ -128,32 +128,32 @@ ObjectStore & Hop::getObjectStoreTypedInstance() const { TypeLookup const Hop::typeLookup { "Hop", { - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::alpha_pct , Hop::m_alpha_pct , NonPhysicalQuantity::Percentage ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::form , Hop::m_form , NonPhysicalQuantity::Enum ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::beta_pct , Hop::m_beta_pct , NonPhysicalQuantity::Percentage ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::origin , Hop::m_origin , NonPhysicalQuantity::String ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::type , Hop::m_type , NonPhysicalQuantity::Enum ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::notes , Hop::m_notes , NonPhysicalQuantity::String ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::hsi_pct , Hop::m_hsi_pct , NonPhysicalQuantity::Percentage ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::substitutes , Hop::m_substitutes , NonPhysicalQuantity::String ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::humulene_pct , Hop::m_humulene_pct , NonPhysicalQuantity::Percentage ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::caryophyllene_pct , Hop::m_caryophyllene_pct , NonPhysicalQuantity::Percentage ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::cohumulone_pct , Hop::m_cohumulone_pct , NonPhysicalQuantity::Percentage ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::myrcene_pct , Hop::m_myrcene_pct , NonPhysicalQuantity::Percentage ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::alpha_pct , Hop::m_alpha_pct , NonPhysicalQuantity::Percentage ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::form , Hop::m_form , NonPhysicalQuantity::Enum ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::beta_pct , Hop::m_beta_pct , NonPhysicalQuantity::Percentage ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::origin , Hop::m_origin , NonPhysicalQuantity::String ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::type , Hop::m_type , NonPhysicalQuantity::Enum ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::notes , Hop::m_notes , NonPhysicalQuantity::String ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::hsi_pct , Hop::m_hsi_pct , NonPhysicalQuantity::Percentage ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::substitutes , Hop::m_substitutes , NonPhysicalQuantity::String ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::humulene_pct , Hop::m_humulene_pct , NonPhysicalQuantity::Percentage ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::caryophyllene_pct , Hop::m_caryophyllene_pct , NonPhysicalQuantity::Percentage ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::cohumulone_pct , Hop::m_cohumulone_pct , NonPhysicalQuantity::Percentage ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::myrcene_pct , Hop::m_myrcene_pct , NonPhysicalQuantity::Percentage ), // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::totalOil_mlPer100g, Hop::m_totalOil_mlPer100g, NonPhysicalQuantity::Dimensionless), // Not really dimensionless... - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::farnesene_pct , Hop::m_farnesene_pct , NonPhysicalQuantity::Percentage ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::geraniol_pct , Hop::m_geraniol_pct , NonPhysicalQuantity::Percentage ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::bPinene_pct , Hop::m_bPinene_pct , NonPhysicalQuantity::Percentage ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::linalool_pct , Hop::m_linalool_pct , NonPhysicalQuantity::Percentage ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::limonene_pct , Hop::m_limonene_pct , NonPhysicalQuantity::Percentage ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::nerol_pct , Hop::m_nerol_pct , NonPhysicalQuantity::Percentage ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::pinene_pct , Hop::m_pinene_pct , NonPhysicalQuantity::Percentage ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::polyphenols_pct , Hop::m_polyphenols_pct , NonPhysicalQuantity::Percentage ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::xanthohumol_pct , Hop::m_xanthohumol_pct , NonPhysicalQuantity::Percentage ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::producer , Hop::m_producer , NonPhysicalQuantity::String ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::productId , Hop::m_productId , NonPhysicalQuantity::String ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::year , Hop::m_year , NonPhysicalQuantity::String ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::totalOil_mlPer100g, Hop::m_totalOil_mlPer100g, NonPhysicalQuantity::Dimensionless), // Not really dimensionless... + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::farnesene_pct , Hop::m_farnesene_pct , NonPhysicalQuantity::Percentage ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::geraniol_pct , Hop::m_geraniol_pct , NonPhysicalQuantity::Percentage ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::bPinene_pct , Hop::m_bPinene_pct , NonPhysicalQuantity::Percentage ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::linalool_pct , Hop::m_linalool_pct , NonPhysicalQuantity::Percentage ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::limonene_pct , Hop::m_limonene_pct , NonPhysicalQuantity::Percentage ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::nerol_pct , Hop::m_nerol_pct , NonPhysicalQuantity::Percentage ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::pinene_pct , Hop::m_pinene_pct , NonPhysicalQuantity::Percentage ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::polyphenols_pct , Hop::m_polyphenols_pct , NonPhysicalQuantity::Percentage ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::xanthohumol_pct , Hop::m_xanthohumol_pct , NonPhysicalQuantity::Percentage ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::producer , Hop::m_producer , NonPhysicalQuantity::String ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::productId , Hop::m_productId , NonPhysicalQuantity::String ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Hop::year , Hop::m_year , NonPhysicalQuantity::String ), }, // Parent classes lookup {&Ingredient::typeLookup, @@ -163,63 +163,67 @@ static_assert(std::is_base_of::value); Hop::Hop(QString name) : Ingredient{name}, - m_alpha_pct {0.0 }, - m_form {std::nullopt}, - m_beta_pct {std::nullopt}, - m_origin {"" }, - m_type {std::nullopt}, - m_notes {"" }, - m_hsi_pct {0.0 }, - m_substitutes {"" }, - m_humulene_pct {0.0 }, - m_caryophyllene_pct {0.0 }, - m_cohumulone_pct {0.0 }, - m_myrcene_pct {0.0 }, + m_alpha_pct {0.0 }, + m_form {std::nullopt}, + m_beta_pct {std::nullopt}, + m_origin {"" }, + m_type {std::nullopt}, + m_notes {"" }, + m_hsi_pct {0.0 }, + m_substitutes {"" }, + m_humulene_pct {0.0 }, + m_caryophyllene_pct {0.0 }, + m_cohumulone_pct {0.0 }, + m_myrcene_pct {0.0 }, // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ m_totalOil_mlPer100g{std::nullopt}, - m_farnesene_pct {std::nullopt}, - m_geraniol_pct {std::nullopt}, - m_bPinene_pct {std::nullopt}, - m_linalool_pct {std::nullopt}, - m_limonene_pct {std::nullopt}, - m_nerol_pct {std::nullopt}, - m_pinene_pct {std::nullopt}, - m_polyphenols_pct {std::nullopt}, - m_xanthohumol_pct {std::nullopt}, - m_producer {"" }, - m_productId {"" }, - m_year {"" } { + m_farnesene_pct {std::nullopt}, + m_geraniol_pct {std::nullopt}, + m_bPinene_pct {std::nullopt}, + m_linalool_pct {std::nullopt}, + m_limonene_pct {std::nullopt}, + m_nerol_pct {std::nullopt}, + m_pinene_pct {std::nullopt}, + m_polyphenols_pct {std::nullopt}, + m_xanthohumol_pct {std::nullopt}, + m_producer {"" }, + m_productId {"" }, + m_year {"" } { + + CONSTRUCTOR_END return; } Hop::Hop(NamedParameterBundle const & namedParameterBundle) : Ingredient{namedParameterBundle}, - SET_REGULAR_FROM_NPB (m_alpha_pct , namedParameterBundle, PropertyNames::Hop::alpha_pct ), - SET_OPT_ENUM_FROM_NPB(m_form , Hop::Form, namedParameterBundle, PropertyNames::Hop::form ), - SET_REGULAR_FROM_NPB (m_beta_pct , namedParameterBundle, PropertyNames::Hop::beta_pct ), - SET_REGULAR_FROM_NPB (m_origin , namedParameterBundle, PropertyNames::Hop::origin ), - SET_OPT_ENUM_FROM_NPB(m_type , Hop::Type, namedParameterBundle, PropertyNames::Hop::type ), - SET_REGULAR_FROM_NPB (m_notes , namedParameterBundle, PropertyNames::Hop::notes ), - SET_REGULAR_FROM_NPB (m_hsi_pct , namedParameterBundle, PropertyNames::Hop::hsi_pct ), - SET_REGULAR_FROM_NPB (m_substitutes , namedParameterBundle, PropertyNames::Hop::substitutes ), - SET_REGULAR_FROM_NPB (m_humulene_pct , namedParameterBundle, PropertyNames::Hop::humulene_pct ), - SET_REGULAR_FROM_NPB (m_caryophyllene_pct , namedParameterBundle, PropertyNames::Hop::caryophyllene_pct ), - SET_REGULAR_FROM_NPB (m_cohumulone_pct , namedParameterBundle, PropertyNames::Hop::cohumulone_pct ), - SET_REGULAR_FROM_NPB (m_myrcene_pct , namedParameterBundle, PropertyNames::Hop::myrcene_pct ), + SET_REGULAR_FROM_NPB (m_alpha_pct , namedParameterBundle, PropertyNames::Hop::alpha_pct ), + SET_OPT_ENUM_FROM_NPB(m_form , Hop::Form, namedParameterBundle, PropertyNames::Hop::form ), + SET_REGULAR_FROM_NPB (m_beta_pct , namedParameterBundle, PropertyNames::Hop::beta_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_origin , namedParameterBundle, PropertyNames::Hop::origin ), + SET_OPT_ENUM_FROM_NPB(m_type , Hop::Type, namedParameterBundle, PropertyNames::Hop::type ), + SET_REGULAR_FROM_NPB (m_notes , namedParameterBundle, PropertyNames::Hop::notes ), + SET_REGULAR_FROM_NPB (m_hsi_pct , namedParameterBundle, PropertyNames::Hop::hsi_pct ), + SET_REGULAR_FROM_NPB (m_substitutes , namedParameterBundle, PropertyNames::Hop::substitutes ), + SET_REGULAR_FROM_NPB (m_humulene_pct , namedParameterBundle, PropertyNames::Hop::humulene_pct ), + SET_REGULAR_FROM_NPB (m_caryophyllene_pct , namedParameterBundle, PropertyNames::Hop::caryophyllene_pct ), + SET_REGULAR_FROM_NPB (m_cohumulone_pct , namedParameterBundle, PropertyNames::Hop::cohumulone_pct ), + SET_REGULAR_FROM_NPB (m_myrcene_pct , namedParameterBundle, PropertyNames::Hop::myrcene_pct ), // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - SET_REGULAR_FROM_NPB (m_totalOil_mlPer100g, namedParameterBundle, PropertyNames::Hop::totalOil_mlPer100g), - SET_REGULAR_FROM_NPB (m_farnesene_pct , namedParameterBundle, PropertyNames::Hop::farnesene_pct ), - SET_REGULAR_FROM_NPB (m_geraniol_pct , namedParameterBundle, PropertyNames::Hop::geraniol_pct ), - SET_REGULAR_FROM_NPB (m_bPinene_pct , namedParameterBundle, PropertyNames::Hop::bPinene_pct ), - SET_REGULAR_FROM_NPB (m_linalool_pct , namedParameterBundle, PropertyNames::Hop::linalool_pct ), - SET_REGULAR_FROM_NPB (m_limonene_pct , namedParameterBundle, PropertyNames::Hop::limonene_pct ), - SET_REGULAR_FROM_NPB (m_nerol_pct , namedParameterBundle, PropertyNames::Hop::nerol_pct ), - SET_REGULAR_FROM_NPB (m_pinene_pct , namedParameterBundle, PropertyNames::Hop::pinene_pct ), - SET_REGULAR_FROM_NPB (m_polyphenols_pct , namedParameterBundle, PropertyNames::Hop::polyphenols_pct ), - SET_REGULAR_FROM_NPB (m_xanthohumol_pct , namedParameterBundle, PropertyNames::Hop::xanthohumol_pct ), - SET_REGULAR_FROM_NPB (m_producer , namedParameterBundle, PropertyNames::Hop::producer ), - SET_REGULAR_FROM_NPB (m_productId , namedParameterBundle, PropertyNames::Hop::productId ), - SET_REGULAR_FROM_NPB (m_year , namedParameterBundle, PropertyNames::Hop::year ) { + SET_REGULAR_FROM_NPB (m_totalOil_mlPer100g, namedParameterBundle, PropertyNames::Hop::totalOil_mlPer100g, std::nullopt), + SET_REGULAR_FROM_NPB (m_farnesene_pct , namedParameterBundle, PropertyNames::Hop::farnesene_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_geraniol_pct , namedParameterBundle, PropertyNames::Hop::geraniol_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_bPinene_pct , namedParameterBundle, PropertyNames::Hop::bPinene_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_linalool_pct , namedParameterBundle, PropertyNames::Hop::linalool_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_limonene_pct , namedParameterBundle, PropertyNames::Hop::limonene_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_nerol_pct , namedParameterBundle, PropertyNames::Hop::nerol_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_pinene_pct , namedParameterBundle, PropertyNames::Hop::pinene_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_polyphenols_pct , namedParameterBundle, PropertyNames::Hop::polyphenols_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_xanthohumol_pct , namedParameterBundle, PropertyNames::Hop::xanthohumol_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_producer , namedParameterBundle, PropertyNames::Hop::producer , "" ), + SET_REGULAR_FROM_NPB (m_productId , namedParameterBundle, PropertyNames::Hop::productId , "" ), + SET_REGULAR_FROM_NPB (m_year , namedParameterBundle, PropertyNames::Hop::year , "" ) { + + CONSTRUCTOR_END return; } @@ -251,6 +255,8 @@ Hop::Hop(Hop const & other) : m_producer {other.m_producer }, m_productId {other.m_productId }, m_year {other.m_year } { + + CONSTRUCTOR_END return; } diff --git a/src/model/Ingredient.cpp b/src/model/Ingredient.cpp index 425c4893..f78d65c4 100644 --- a/src/model/Ingredient.cpp +++ b/src/model/Ingredient.cpp @@ -35,18 +35,24 @@ static_assert(std::is_base_of, Ingredient>::value); Ingredient::Ingredient(QString name) : OutlineableNamedEntity{name}, FolderBase{} { + + CONSTRUCTOR_END return; } Ingredient::Ingredient(NamedParameterBundle const & namedParameterBundle) : OutlineableNamedEntity{namedParameterBundle}, FolderBase{namedParameterBundle} { + + CONSTRUCTOR_END return; } Ingredient::Ingredient(Ingredient const & other) : OutlineableNamedEntity{other}, FolderBase{other} { + + CONSTRUCTOR_END return; } diff --git a/src/model/IngredientAmount.h b/src/model/IngredientAmount.h index 2c061eee..8875494b 100644 --- a/src/model/IngredientAmount.h +++ b/src/model/IngredientAmount.h @@ -198,8 +198,8 @@ class IngredientAmount : public CuriouslyRecurringTemplateBasedoSetQuantity(val.quantity); this->doSetUnit (val.unit ); diff --git a/src/model/IngredientBase.h b/src/model/IngredientBase.h index 16c59015..c1a885d9 100644 --- a/src/model/IngredientBase.h +++ b/src/model/IngredientBase.h @@ -35,14 +35,25 @@ class IngredientBase : public CuriouslyRecurringTemplateBase(this->derived())->amount(); + auto inventory = InventoryTools::getInventory(this->derived()); + // Normally leave this log statement commented out as it generates too many lines in the log file +// qDebug() << +// Q_FUNC_INFO << "Inventory object:" << *inventory << "; ingredient ID:" << inventory->ingredientId() << +// "; amount:" << inventory->amount(); + return inventory->amount(); } /** * \brief Used to implement Ingredient::setTotalInventory() for Ingredient subclass (ie Derived) */ void doSetTotalInventory(Measurement::Amount const val) { + // InventoryTools::getInventory will have ensured the object returned here is in the database, even if it was + // newly-created. auto inventory = InventoryTools::getInventory(this->derived()); + // Normally leave this log statement commented out as it generates too many lines in the log file +// qDebug() << +// Q_FUNC_INFO << "Inventory object:" << *inventory << "; change amount from" << inventory->amount() << "to" << +// val; inventory->setAmount(val); return; } diff --git a/src/model/IngredientInRecipe.cpp b/src/model/IngredientInRecipe.cpp index 3c1584ec..cc56e749 100644 --- a/src/model/IngredientInRecipe.cpp +++ b/src/model/IngredientInRecipe.cpp @@ -40,18 +40,25 @@ TypeLookup const IngredientInRecipe::typeLookup { IngredientInRecipe::IngredientInRecipe(QString name, int const recipeId, int const ingredientId) : OwnedByRecipe{name, recipeId}, m_ingredientId{ingredientId} { + + CONSTRUCTOR_END return; } IngredientInRecipe::IngredientInRecipe(NamedParameterBundle const & namedParameterBundle) : OwnedByRecipe{namedParameterBundle}, - SET_REGULAR_FROM_NPB(m_ingredientId, namedParameterBundle, PropertyNames::IngredientInRecipe::ingredientId) { + // Although ingredientId is required, we have to supply a default value for when we are reading from BeerXML or BeerJSON + SET_REGULAR_FROM_NPB(m_ingredientId, namedParameterBundle, PropertyNames::IngredientInRecipe::ingredientId, -1) { + + CONSTRUCTOR_END return; } IngredientInRecipe::IngredientInRecipe(IngredientInRecipe const & other) : OwnedByRecipe{other}, m_ingredientId{other.m_ingredientId} { + + CONSTRUCTOR_END return; } diff --git a/src/model/Instruction.cpp b/src/model/Instruction.cpp index 2ade4609..84b9f741 100644 --- a/src/model/Instruction.cpp +++ b/src/model/Instruction.cpp @@ -104,6 +104,8 @@ Instruction::Instruction(QString name) : m_timerValue(""), m_completed (false), m_interval (0.0) { + + CONSTRUCTOR_END return; } @@ -115,6 +117,8 @@ Instruction::Instruction(NamedParameterBundle const & namedParameterBundle) : SET_REGULAR_FROM_NPB (m_timerValue, namedParameterBundle, PropertyNames::Instruction::timerValue), SET_REGULAR_FROM_NPB (m_completed , namedParameterBundle, PropertyNames::Instruction::completed ), SET_REGULAR_FROM_NPB (m_interval , namedParameterBundle, PropertyNames::Instruction::interval ) { + + CONSTRUCTOR_END return; } @@ -126,6 +130,8 @@ Instruction::Instruction(Instruction const & other) : m_timerValue{other.m_timerValue}, m_completed {other.m_completed }, m_interval {other.m_interval } { + + CONSTRUCTOR_END return; } diff --git a/src/model/Inventory.cpp b/src/model/Inventory.cpp index 21107028..71c10ba7 100644 --- a/src/model/Inventory.cpp +++ b/src/model/Inventory.cpp @@ -56,18 +56,24 @@ TypeLookup const Inventory::typeLookup { Inventory::Inventory() : NamedEntity{""}, m_ingredientId{-1} { + + CONSTRUCTOR_END return; } Inventory::Inventory(NamedParameterBundle const & namedParameterBundle) : NamedEntity{namedParameterBundle}, SET_REGULAR_FROM_NPB (m_ingredientId, namedParameterBundle, PropertyNames::Inventory::ingredientId) { + + CONSTRUCTOR_END return; } Inventory::Inventory(Inventory const & other) : NamedEntity {other }, m_ingredientId{other.m_ingredientId} { + + CONSTRUCTOR_END return; } diff --git a/src/model/Inventory.h b/src/model/Inventory.h index aff53816..045f1f5f 100644 --- a/src/model/Inventory.h +++ b/src/model/Inventory.h @@ -261,18 +261,21 @@ TypeLookup const Inventory##IngredientName::typeLookup { }; \ static_assert(std::is_base_of::value); \ Inventory##IngredientName::Inventory##IngredientName() : \ - Inventory(), \ - IngredientAmount() { \ - return; \ + Inventory{}, \ + IngredientAmount{} { \ + CONSTRUCTOR_END \ + return; \ } \ Inventory##IngredientName::Inventory##IngredientName(NamedParameterBundle const & npb) : \ Inventory {npb}, \ IngredientAmount{npb} { \ + CONSTRUCTOR_END \ return; \ } \ Inventory##IngredientName::Inventory##IngredientName(Inventory##IngredientName const & other) : \ Inventory {other}, \ IngredientAmount{other} { \ + CONSTRUCTOR_END \ return; \ } \ Inventory##IngredientName::~Inventory##IngredientName() = default; \ diff --git a/src/model/Mash.cpp b/src/model/Mash.cpp index 8b1ea58a..f3ec65d2 100644 --- a/src/model/Mash.cpp +++ b/src/model/Mash.cpp @@ -87,6 +87,8 @@ Mash::Mash(QString name) : m_mashTunWeight_kg {0.0 }, m_mashTunSpecificHeat_calGC{0.0 }, m_equipAdjust {true} { + + CONSTRUCTOR_END return; } @@ -95,13 +97,15 @@ Mash::Mash(NamedParameterBundle const & namedParameterBundle) : FolderBase{namedParameterBundle}, StepOwnerBase{}, SET_REGULAR_FROM_NPB (m_grainTemp_c , namedParameterBundle, PropertyNames::Mash::grainTemp_c ), - SET_REGULAR_FROM_NPB (m_notes , namedParameterBundle, PropertyNames::Mash::notes ), + SET_REGULAR_FROM_NPB (m_notes , namedParameterBundle, PropertyNames::Mash::notes , ""), SET_REGULAR_FROM_NPB (m_tunTemp_c , namedParameterBundle, PropertyNames::Mash::tunTemp_c ), SET_REGULAR_FROM_NPB (m_spargeTemp_c , namedParameterBundle, PropertyNames::Mash::spargeTemp_c ), SET_REGULAR_FROM_NPB (m_ph , namedParameterBundle, PropertyNames::Mash::ph ), SET_REGULAR_FROM_NPB (m_mashTunWeight_kg , namedParameterBundle, PropertyNames::Mash::mashTunWeight_kg ), SET_REGULAR_FROM_NPB (m_mashTunSpecificHeat_calGC, namedParameterBundle, PropertyNames::Mash::mashTunSpecificHeat_calGC), SET_REGULAR_FROM_NPB (m_equipAdjust , namedParameterBundle, PropertyNames::Mash::equipAdjust ) { + + CONSTRUCTOR_END return; } @@ -117,6 +121,8 @@ Mash::Mash(Mash const & other) : m_mashTunWeight_kg {other.m_mashTunWeight_kg }, m_mashTunSpecificHeat_calGC{other.m_mashTunSpecificHeat_calGC}, m_equipAdjust {other.m_equipAdjust } { + + CONSTRUCTOR_END return; } diff --git a/src/model/MashStep.cpp b/src/model/MashStep.cpp index 165a3dbe..009493ae 100644 --- a/src/model/MashStep.cpp +++ b/src/model/MashStep.cpp @@ -87,6 +87,8 @@ MashStep::MashStep(QString name) : m_infuseTemp_c {std::nullopt }, // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ m_liquorToGristRatio_lKg{std::nullopt } { + + CONSTRUCTOR_END return; } @@ -106,11 +108,13 @@ MashStep::MashStep(NamedParameterBundle const & namedParameterBundle) : // if (0.0 == m_amount_l) { if (m_type == MashStep::Type::Decoction) { - m_amount_l = namedParameterBundle.val(PropertyNames::MashStep::decoctionAmount_l, 0.0); + ASSIGN_REGULAR_FROM_NPB(m_amount_l, namedParameterBundle, PropertyNames::MashStep::decoctionAmount_l, 0.0); } else { - m_amount_l = namedParameterBundle.val(PropertyNames::MashStep::infuseAmount_l , 0.0); + ASSIGN_REGULAR_FROM_NPB(m_amount_l, namedParameterBundle, PropertyNames::MashStep::infuseAmount_l , 0.0); } } + + CONSTRUCTOR_END return; } @@ -121,6 +125,8 @@ MashStep::MashStep(MashStep const & other) : m_infuseTemp_c {other.m_infuseTemp_c }, // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ m_liquorToGristRatio_lKg{other.m_liquorToGristRatio_lKg} { + + CONSTRUCTOR_END return; } diff --git a/src/model/MashStep.h b/src/model/MashStep.h index 7ed22b51..35b6dd11 100644 --- a/src/model/MashStep.h +++ b/src/model/MashStep.h @@ -136,7 +136,9 @@ class MashStep : public Step, public StepBase { * \brief The infusion temp in C. ⮜⮜⮜ Not part of BeerXML; optional in BeerJSON ⮞⮞⮞ * * An infusion step is where you're adding hot water to the mash, so this is the temperature of the water - * being added. + * being added. This is not part of BeerJSON, so I guess the thinking there is that the temperature of the + * water being added is either not important or can be calculated from other data (including the difference + * between the start and end temperatures of the step). */ Q_PROPERTY(std::optional infuseTemp_c READ infuseTemp_c WRITE setInfuseTemp_c ) // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ diff --git a/src/model/Misc.cpp b/src/model/Misc.cpp index 5b9f0581..d4fb2687 100644 --- a/src/model/Misc.cpp +++ b/src/model/Misc.cpp @@ -111,6 +111,8 @@ Misc::Misc(QString name) : // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ m_producer {"" }, m_productId{"" } { + + CONSTRUCTOR_END return; } @@ -123,6 +125,7 @@ Misc::Misc(NamedParameterBundle const & namedParameterBundle) : SET_REGULAR_FROM_NPB (m_producer , namedParameterBundle, PropertyNames::Misc::producer ), SET_REGULAR_FROM_NPB (m_productId, namedParameterBundle, PropertyNames::Misc::productId) { + CONSTRUCTOR_END return; } @@ -134,6 +137,8 @@ Misc::Misc(Misc const & other) : // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ m_producer {other.m_producer }, m_productId{other.m_productId} { + + CONSTRUCTOR_END return; } diff --git a/src/model/NamedEntity.cpp b/src/model/NamedEntity.cpp index 6e7d12ea..01ab8a7e 100644 --- a/src/model/NamedEntity.cpp +++ b/src/model/NamedEntity.cpp @@ -69,6 +69,8 @@ NamedEntity::NamedEntity(QString t_name, bool t_display) : m_display {t_display}, m_deleted {false }, m_beingModified{false } { + + CONSTRUCTOR_END return; } @@ -94,6 +96,8 @@ NamedEntity::NamedEntity(NamedParameterBundle const & namedParameterBundle) : SET_REGULAR_FROM_NPB (m_display, namedParameterBundle, PropertyNames::NamedEntity::display, true ), SET_REGULAR_FROM_NPB (m_deleted, namedParameterBundle, PropertyNames::NamedEntity::deleted, false ), m_beingModified{false} { + + CONSTRUCTOR_END return; } @@ -108,6 +112,8 @@ NamedEntity::NamedEntity(NamedEntity const & other) : m_display {other.m_display}, m_deleted {other.m_deleted}, m_beingModified{false} { + + CONSTRUCTOR_END return; } @@ -454,6 +460,11 @@ void NamedEntity::prepareForPropertyChange(BtStringConst const & propertyName) { } void NamedEntity::propagatePropertyChange(BtStringConst const & propertyName, bool notify) const { + if (!this->m_propagationAndSignalsEnabled) { + qDebug() << Q_FUNC_INFO << "m_propagationAndSignalsEnabled unset on" << this->metaObject()->className(); + return; + } + // If we're already stored in the object store, tell it about the property change so that it can write it to the // database. (We don't pass the new value as it will get read out of the object via propertyName.) if (this->m_key > 0) { diff --git a/src/model/NamedEntity.h b/src/model/NamedEntity.h index f52c8571..59524689 100644 --- a/src/model/NamedEntity.h +++ b/src/model/NamedEntity.h @@ -409,6 +409,23 @@ class NamedEntity : public QObject { void changedName(QString); protected: + /** + * \brief If a derived class calls a setter from its constructor (eg as we do in \c Step to handle setting + * \c m_stepTime_mins from either \c PropertyNames::Step::stepTime_mins or + * \c PropertyNames::Step::stepTime_days) we don't want to be sending signals or trying to write to the + * database. Besides being somewhat circular to write to the DB whilst we are perhaps reading the object from + * the DB, it's not always possible. Eg, \c Step is a pure virtual class, and, at the point its constructor + * is running, it is not valid to call \c this->getObjectStoreTypedInstance() (because the vtable for the + * derived class such as \c BoilStep or \c MashStep has not yet been created). + * + * So, this flag is set to \c false in the \c NamedEntity constructor (courtesy of the default here) to + * disable signalling and propagation down to the DB when properties change. Concrete subclasses should turn + * this flag on as the last action of their constructor. HOWEVER to make life simple, we just include the + * CONSTRUCTOR_END macro (see below) at the end of every constructor, which turns the flag on if the class is + * not abstract. + */ + bool m_propagationAndSignalsEnabled = false; + //! The key of this entity in its table. int m_key; // This is <=0 if there is no parent (or parent is not yet known) @@ -584,7 +601,7 @@ class NamedEntity : public QObject { if (newValue == memberVariable) { qDebug() << Q_FUNC_INFO << this->metaObject()->className() << "#" << this->key() << ": ignoring call to setter for" << - propertyName << "as value not changing"; + propertyName << "as value (" << newValue << ") not changing"; return true; } return false; @@ -601,6 +618,8 @@ class NamedEntity : public QObject { bool setAndNotify(BtStringConst const & propertyName, T & memberVariable, T const newValue) { + // Normally leave this log statement commented out as it generates too many lines in the log file +// qDebug() << Q_FUNC_INFO << propertyName << ": change from" << memberVariable << "to" << newValue; if (this->newValueMatchesExisting(propertyName, memberVariable, newValue)) { return false; } @@ -698,6 +717,25 @@ Q_DECLARE_METATYPE(std::shared_ptr) */ #define SET_AND_NOTIFY(...) this->setAndNotify(__VA_ARGS__) +template constexpr bool IsAbstract(T const *) { return std::is_abstract::value; } + +/** + * \brief Subclasses should include this macro at the end of \b all of their constructors. See comment on + * \c m_propagationAndSignalsEnabled for more info. + * + * There is probably a way to make this if statement constexpr, but I haven't figured it out yet! + * + * TODO: We could probably remove a \b lot of boilerplate from the three main constructors (create empty, + * copy, create from NamedParameterBundle) by having generic code that uses the static typeLookup member + * variable to loop through and initialise all of the instance member variables. We'd need to move default + * values to the header (which is best practice now anyway) and add something to \c TypeInfo to say when + * default values are OK for constructing from NamedParameterBundle, but that should be doable). + */ +#define CONSTRUCTOR_END \ + if (!IsAbstract(this)) { \ + this->NamedEntity::m_propagationAndSignalsEnabled = true; \ + } + /** * \brief For some templated functions, it's useful at compile time to have one version for NE classes with folders and * one for those without. We need to put the concepts here in the base class for them to be accessible. diff --git a/src/model/NamedParameterBundle.cpp b/src/model/NamedParameterBundle.cpp index 38cb78ec..e39c60b5 100644 --- a/src/model/NamedParameterBundle.cpp +++ b/src/model/NamedParameterBundle.cpp @@ -1,5 +1,5 @@ /*====================================================================================================================== - * model/NamedParameterBundle.cpp is part of Brewken, and is copyright the following authors 2021-2023: + * model/NamedParameterBundle.cpp is part of Brewken, and is copyright the following authors 2021-2024: * • Matt Young * * Brewken is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -47,14 +47,20 @@ void NamedParameterBundle::insert(PropertyPath const & propertyPath, QVariant co for (auto const property : properties) { if (property == properties.last()) { bundle->insert(*property, value); + } else { + bool const newBundle { !bundle->m_containedBundles.contains(**property) }; + // Here, operator[]() silently inserts an item into bundle->m_containedBundles if no item exists with the same + // key, which is exactly the behaviour we want, and hence why we don't explicitly need to call insert elsewhere + // on this member variable. + // + // Note that property is pointer to BtStringConst, so we need one '*' to dereference it and another to extract the + // wrapped string. + bundle = &bundle->m_containedBundles[**property]; + if (newBundle) { + // If we just created a new bundle, ensure it inherits the top level mode. + bundle->m_mode = this->m_mode; + } } - // Here, operator[]() silently inserts an item into bundle->m_containedBundles if no item exists with the same - // key, which is exactly the behaviour we want, and hence why we don't explicitly need to call insert elsewhere on - // this member variable. - // - // Note that property is pointer to BtStringConst, so we need one '*' to dereference it and another to extract the - // wrapped string. - bundle = &bundle->m_containedBundles[**property]; } return; } @@ -79,6 +85,21 @@ bool NamedParameterBundle::contains(PropertyPath const & propertyPath) const { return false; } +template +void NamedParameterBundle::insertIfNotPresent(P const & propertyNameOrPath, QVariant const & value) { + if (!this->contains(propertyNameOrPath)) { + this->insert(propertyNameOrPath, value); + } + return; +} + +// +// Instantiate the above template functions for the types that are going to use them +// (This is all just a trick to allow the template definition to be here in the .cpp file and not in the header.) +// +template void NamedParameterBundle::insertIfNotPresent(BtStringConst const & propertyName, QVariant const & value); +template void NamedParameterBundle::insertIfNotPresent(PropertyPath const & propertyPath, QVariant const & value); + std::size_t NamedParameterBundle::size() const noexcept { // // This function is only used for logging, so, for simplicitly, we'll count each contained bundle as 1, rather than @@ -106,7 +127,7 @@ QVariant NamedParameterBundle::get(BtStringConst const & propertyName) const { errorMessageAsStream << key; } errorMessageAsStream << ")"; - if (this->m_mode == NamedParameterBundle::Strict) { + if (this->m_mode == NamedParameterBundle::OperationMode::Strict) { // // We want to throw an exception here because it's a lot less code than checking a return value on every call // and, usually, missing required parameter is a coding error. @@ -147,26 +168,29 @@ NamedParameterBundle const & NamedParameterBundle::getBundle(BtStringConst const return this->m_containedBundles.at(*propertyName); } - -template -S & operator<<(S & stream, NamedParameterBundle const & namedParameterBundle); - template -S & operator<<(S & stream, NamedParameterBundle const * namedParameterBundle); - -template -S & operator<<(S & stream, NamedParameterBundle const & namedParameterBundle) { - stream << namedParameterBundle.size() << "element NamedParameterBundle @" << - static_cast(&namedParameterBundle) << " {"; +S & NamedParameterBundle::writeToStream(S & stream, QString const indent) const { + stream << indent << this->size() << "element NamedParameterBundle @" << + static_cast(this) << " {\n"; + QString const newIndent{QString(" %1").arg(indent)}; + for (auto const & [key, value] : this->m_parameters) { + stream << newIndent << key << "->" << value.typeName() << ":" << value.toString() << "\n"; + } - for (auto const & [key, value] : namedParameterBundle.m_parameters) { - stream << key << "->" << value.typeName() << ":" << value.toString() << " "; + for (auto const & [bundleName, bundle] : this->m_containedBundles) { + stream << newIndent << bundleName << "->\n"; + bundle.writeToStream(stream, newIndent); } - stream << "}"; + stream << indent << "}\n"; return stream; } +template +S & operator<<(S & stream, NamedParameterBundle const & namedParameterBundle) { + return namedParameterBundle.writeToStream(stream, ""); +} + template S & operator<<(S & stream, NamedParameterBundle const * namedParameterBundle) { if (namedParameterBundle) { diff --git a/src/model/NamedParameterBundle.h b/src/model/NamedParameterBundle.h index ed5e6402..10aa8993 100644 --- a/src/model/NamedParameterBundle.h +++ b/src/model/NamedParameterBundle.h @@ -30,6 +30,7 @@ #include "utils/BtStringConst.h" #include "utils/MetaTypes.h" #include "utils/PropertyPath.h" +#include "utils/TypeLookup.h" class NamedParameterBundle; @@ -57,14 +58,14 @@ S & operator<<(S & stream, NamedParameterBundle const * namedParameterBundle); */ class NamedParameterBundle { public: - enum OperationMode { + enum class OperationMode { Strict, NotStrict }; - template friend S & operator<<(S & stream, NamedParameterBundle const & namedParameterBundle); + template S & writeToStream(S & stream, QString const indent) const; - NamedParameterBundle(OperationMode mode = Strict); + NamedParameterBundle(OperationMode mode = OperationMode::Strict); ~NamedParameterBundle(); void insert(BtStringConst const & propertyName, QVariant const & value); @@ -75,6 +76,11 @@ class NamedParameterBundle { bool contains(PropertyPath const & propertyPath) const; + //! \brief Convenience function combining \c contains and \c insert + template + void insertIfNotPresent(P const & propertyNameOrPath, QVariant const & value); + + std::size_t size() const noexcept; bool isEmpty() const; @@ -154,7 +160,7 @@ class NamedParameterBundle { }; -// +//====================================================================================================================== // In the past, in a constructor's member initializer list, we would put things along the lines of: // // m_foobar{namedParameterBundle.val(PropertyNames::Hop::foobar)} @@ -167,7 +173,7 @@ class NamedParameterBundle { // But this is a bit clunky, and we have to refer to the member variable twice. // // So, now we use the macros below. -// +//====================================================================================================================== /** * \brief In a constructor's member initializer list, instead of writing: @@ -192,8 +198,40 @@ class NamedParameterBundle { * std::nullopt. The lack of any value in the bundle means no DB column was mapped to this property name.) * */ -#define SET_REGULAR_FROM_NPB(MemberVariable, NamedParameterBundle, PropertyName, ...) \ - MemberVariable{NamedParameterBundle.val(PropertyName __VA_OPT__(, __VA_ARGS__))} +#define SET_REGULAR_FROM_NPB(memberVariable, namedParameterBundle, propertyName, ...) \ + memberVariable{namedParameterBundle.val(propertyName __VA_OPT__(, __VA_ARGS__))} + +/** + * \brief Same as SET_REGULAR_FROM_NPB but for use in the body of a constructor (rather than the initialisation list) + */ +#define ASSIGN_REGULAR_FROM_NPB(memberVariable, namedParameterBundle, propertyName, ...) \ + this->memberVariable = namedParameterBundle.val(propertyName __VA_OPT__(, __VA_ARGS__)) + +/** + * \brief Sometimes we want to call a setter (in the body of the constructor) rather than write directly to a member + * variable in the initialiser list. Eg in Step where we want to be able to store \c stepTime_days in + * \c m_stepTime_mins by calling \c setStepTime_days. + * + * This should work even for "setters" with multiple parameters, provided the first one is the one coming out of + * the \c NamedParameterBundle + */ +#define SET_REGULAR_FROM_NPB_NO_MV(setterMemberFunction, namedParameterBundle, propertyName, ...) \ + this->setterMemberFunction(namedParameterBundle.val>(propertyName __VA_OPT__(, __VA_ARGS__))) + +/** + * \brief Similar to SET_REGULAR_FROM_NPB_NO_MV, but only calls the setter if the property is in the bundle + * + * Evaluates to \c true if property was in the bundle, \c false otherwise + * + * The logic below is (a && (function-returning-void, true)). If a is false, result is false with no further + * evaluation because of short-circuit evaluation. If a is true, then the function-returning-void is called and, + * because of the comma operator, its (non-)result is ignored and replaced by 'true' leading to an overall true + * result (true && true). + */ +#define SET_IF_PRESENT_FROM_NPB_NO_MV(setterMemberFunction, namedParameterBundle, propertyName, ...) \ + (namedParameterBundle.contains(propertyName) && \ + ((SET_REGULAR_FROM_NPB_NO_MV(setterMemberFunction, namedParameterBundle, propertyName __VA_OPT__(, __VA_ARGS__))), \ + true)) /** * \brief In a constructor's member initializer list, instead of writing: diff --git a/src/model/OutlineableNamedEntity.cpp b/src/model/OutlineableNamedEntity.cpp index 0f29568b..2b0c3631 100644 --- a/src/model/OutlineableNamedEntity.cpp +++ b/src/model/OutlineableNamedEntity.cpp @@ -32,18 +32,24 @@ TypeLookup const OutlineableNamedEntity::typeLookup { OutlineableNamedEntity::OutlineableNamedEntity(QString name) : NamedEntity{name, true}, m_outline{false} { + + CONSTRUCTOR_END return; } OutlineableNamedEntity::OutlineableNamedEntity(NamedParameterBundle const & namedParameterBundle) : NamedEntity{namedParameterBundle}, SET_REGULAR_FROM_NPB(m_outline, namedParameterBundle, PropertyNames::OutlineableNamedEntity::outline, false) { + + CONSTRUCTOR_END return; } OutlineableNamedEntity::OutlineableNamedEntity(OutlineableNamedEntity const & other) : NamedEntity{other}, m_outline{other.m_outline} { + + CONSTRUCTOR_END return; } diff --git a/src/model/OutlineableNamedEntity.h b/src/model/OutlineableNamedEntity.h index 56d000d3..8af2d286 100644 --- a/src/model/OutlineableNamedEntity.h +++ b/src/model/OutlineableNamedEntity.h @@ -51,7 +51,7 @@ AddPropertyName(outline) * │ │ • yield † │ • alpha_acid │ │ │ • iron │ * │ │ • color │ • beta_acid │ │ │ • nitrate │ * │ │ │ │ │ │ • nitrite │ - * │ │ │ │ │ │ • flouride │ + * │ │ │ │ │ │ • flouride ⹋ │ * │ │ │ │ │ │ • sulfate │ * │ │ │ │ │ │ • chloride │ * │ │ │ │ │ │ • sodium │ @@ -79,7 +79,7 @@ AddPropertyName(outline) * │ │ • fermentability │ │ │ │ │ * │ │ • beta_glucan │ │ │ │ │ * └────────────┴───────────────────┴─────────────────┴──────────────┴───────────────────────┴───────────────┘ - * † = compound field; ‡ = field we don't support + * † = compound field; ‡ = field we don't support; ⹋ sic -- see https://github.com/beerjson/beerjson/issues/214 * * What this means is that, when we read in a Hop/Fermentable/Misc/Yeast/Water inside a RecipeAddition/ * RecipeUseOf record (an XxxxAdditionType record in BeerJSON) then we need different logic when we "check for diff --git a/src/model/OwnedByRecipe.cpp b/src/model/OwnedByRecipe.cpp index 4d5f61fa..f0427985 100644 --- a/src/model/OwnedByRecipe.cpp +++ b/src/model/OwnedByRecipe.cpp @@ -40,18 +40,25 @@ TypeLookup const OwnedByRecipe::typeLookup { OwnedByRecipe::OwnedByRecipe(QString name, int const recipeId) : NamedEntity{name, true}, m_recipeId{recipeId} { + + CONSTRUCTOR_END return; } OwnedByRecipe::OwnedByRecipe(NamedParameterBundle const & namedParameterBundle) : NamedEntity{namedParameterBundle}, - SET_REGULAR_FROM_NPB(m_recipeId, namedParameterBundle, PropertyNames::OwnedByRecipe::recipeId) { + // Although recipeId is required, we have to supply a default value for when we are reading from BeerXML or BeerJSON + SET_REGULAR_FROM_NPB(m_recipeId, namedParameterBundle, PropertyNames::OwnedByRecipe::recipeId, -1) { + + CONSTRUCTOR_END return; } OwnedByRecipe::OwnedByRecipe(OwnedByRecipe const & other) : NamedEntity{other}, m_recipeId{other.m_recipeId} { + + CONSTRUCTOR_END return; } diff --git a/src/model/Recipe.cpp b/src/model/Recipe.cpp index db5d0e30..71c36dd5 100644 --- a/src/model/Recipe.cpp +++ b/src/model/Recipe.cpp @@ -462,12 +462,14 @@ class Recipe::impl { // No change (from "not set" to "not set") return; } - if (val && val->key() == ourId) { - // No change (same object as we already have) - return; - } if (ourId > 0) { + // This comparison is only valid if we have a valid ID, because val might not yet be stored in the DB. + if (val && val->key() == ourId) { + // No change (same object as we already have) + return; + } + std::shared_ptr oldVal = ObjectStoreWrapper::getById(ourId); disconnect(oldVal.get(), nullptr, &this->m_self, nullptr); } @@ -477,15 +479,20 @@ class Recipe::impl { return; } - // TBD: Would be nice to get rid of this call to copyIfNeeded - std::shared_ptr valToAdd = copyIfNeeded(*val); - ourId = valToAdd->key(); + if (val->key() < 0) { + ourId = ObjectStoreWrapper::insert(val); + } else { + // TBD: Would be nice to get rid of this call to copyIfNeeded + val = copyIfNeeded(*val); + ourId = val->key(); + } + BtStringConst const & property = Recipe::propertyNameFor(); qDebug() << Q_FUNC_INFO << "Setting" << property << "to" << ourId; this->m_self.propagatePropertyChange(property); - connect(valToAdd.get(), &NamedEntity::changed, &this->m_self, &Recipe::acceptChangeToContainedObject); - emit this->m_self.changed(this->m_self.metaProperty(*property), QVariant::fromValue(valToAdd.get())); + connect(val.get(), &NamedEntity::changed, &this->m_self, &Recipe::acceptChangeToContainedObject); + emit this->m_self.changed(this->m_self.metaProperty(*property), QVariant::fromValue(val.get())); this->m_self.recalcAll(); return; @@ -1564,7 +1571,7 @@ bool Recipe::isEqualTo(NamedEntity const & other) const { Utils::AutoCompare(this->m_type , rhs.m_type ) && Utils::AutoCompare(this->m_batchSize_l , rhs.m_batchSize_l ) && Utils::AutoCompare(this->m_efficiency_pct, rhs.m_efficiency_pct) && - Utils::AutoCompare(this->m_age , rhs.m_age ) && + Utils::AutoCompare(this->m_age_days , rhs.m_age_days ) && Utils::AutoCompare(this->m_ageTemp_c , rhs.m_ageTemp_c ) && Utils::AutoCompare(this->m_og , rhs.m_og ) && Utils::AutoCompare(this->m_fg , rhs.m_fg ) && @@ -1610,7 +1617,7 @@ TypeLookup const Recipe::typeLookup { PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Recipe::asstBrewer , Recipe::m_asstBrewer , NonPhysicalQuantity::String ), PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Recipe::batchSize_l , Recipe::m_batchSize_l , Measurement::PhysicalQuantity::Volume ), PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Recipe::efficiency_pct , Recipe::m_efficiency_pct , NonPhysicalQuantity::Percentage ), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Recipe::age_days , Recipe::m_age , NonPhysicalQuantity::Dimensionless ), // See comment above for why Dimensionless, not Time + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Recipe::age_days , Recipe::m_age_days , NonPhysicalQuantity::Dimensionless ), // See comment above for why Dimensionless, not Time PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Recipe::ageTemp_c , Recipe::m_ageTemp_c , Measurement::PhysicalQuantity::Temperature ), PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Recipe::date , Recipe::m_date , NonPhysicalQuantity::Date ), PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Recipe::carbonation_vols , Recipe::m_carbonation_vols , Measurement::PhysicalQuantity::Carbonation ), @@ -1630,6 +1637,7 @@ TypeLookup const Recipe::typeLookup { PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Recipe::og , Recipe::m_og , Measurement::PhysicalQuantity::Density ), PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Recipe::fg , Recipe::m_fg , Measurement::PhysicalQuantity::Density ), PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Recipe::locked , Recipe::m_locked ), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Recipe::calcsEnabled , Recipe::m_calcsEnabled ), PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Recipe::ancestorId , Recipe::m_ancestor_id ), PROPERTY_TYPE_LOOKUP_ENTRY_NO_MV(PropertyNames::Recipe::ABV_pct , Recipe::ABV_pct , NonPhysicalQuantity::Percentage ), // Calculated, not in DB @@ -1688,8 +1696,8 @@ Recipe::Recipe(QString name) : m_asstBrewer {QString{"%1: free beer software"}.arg(CONFIG_APPLICATION_NAME_UC)}, m_batchSize_l {0.0 }, m_efficiency_pct {0.0 }, - m_age {0.0 }, - m_ageTemp_c {0.0 }, + m_age_days {std::nullopt }, + m_ageTemp_c {std::nullopt }, m_date {QDate::currentDate()}, // Date is allowed to be blank, but we default it to today m_carbonation_vols {std::nullopt }, m_forcedCarbonation {false }, @@ -1710,12 +1718,15 @@ Recipe::Recipe(QString name) : m_og {1.0 }, m_fg {1.0 }, m_locked {false }, + m_calcsEnabled {true }, m_uninitializedCalcs {true }, m_uninitializedCalcsMutex{}, m_recalcMutex {}, m_ancestor_id {-1 }, m_ancestors {}, m_hasDescendants {false } { + + CONSTRUCTOR_END return; } @@ -1724,12 +1735,12 @@ Recipe::Recipe(NamedParameterBundle const & namedParameterBundle) : FolderBase {namedParameterBundle}, pimpl {std::make_unique(*this)}, SET_REGULAR_FROM_NPB (m_type , namedParameterBundle, PropertyNames::Recipe::type ), - SET_REGULAR_FROM_NPB (m_brewer , namedParameterBundle, PropertyNames::Recipe::brewer ), - SET_REGULAR_FROM_NPB (m_asstBrewer , namedParameterBundle, PropertyNames::Recipe::asstBrewer ), + SET_REGULAR_FROM_NPB (m_brewer , namedParameterBundle, PropertyNames::Recipe::brewer , ""), + SET_REGULAR_FROM_NPB (m_asstBrewer , namedParameterBundle, PropertyNames::Recipe::asstBrewer , ""), SET_REGULAR_FROM_NPB (m_batchSize_l , namedParameterBundle, PropertyNames::Recipe::batchSize_l ), SET_REGULAR_FROM_NPB (m_efficiency_pct , namedParameterBundle, PropertyNames::Recipe::efficiency_pct ), - SET_REGULAR_FROM_NPB (m_age , namedParameterBundle, PropertyNames::Recipe::age_days ), - SET_REGULAR_FROM_NPB (m_ageTemp_c , namedParameterBundle, PropertyNames::Recipe::ageTemp_c ), + SET_REGULAR_FROM_NPB (m_age_days , namedParameterBundle, PropertyNames::Recipe::age_days , std::nullopt), + SET_REGULAR_FROM_NPB (m_ageTemp_c , namedParameterBundle, PropertyNames::Recipe::ageTemp_c , std::nullopt), SET_REGULAR_FROM_NPB (m_date , namedParameterBundle, PropertyNames::Recipe::date ), SET_REGULAR_FROM_NPB (m_carbonation_vols , namedParameterBundle, PropertyNames::Recipe::carbonation_vols ), SET_REGULAR_FROM_NPB (m_forcedCarbonation , namedParameterBundle, PropertyNames::Recipe::forcedCarbonation ), @@ -1737,31 +1748,34 @@ Recipe::Recipe(NamedParameterBundle const & namedParameterBundle) : SET_REGULAR_FROM_NPB (m_carbonationTemp_c , namedParameterBundle, PropertyNames::Recipe::carbonationTemp_c ), SET_REGULAR_FROM_NPB (m_primingSugarEquiv , namedParameterBundle, PropertyNames::Recipe::primingSugarEquiv ), SET_REGULAR_FROM_NPB (m_kegPrimingFactor , namedParameterBundle, PropertyNames::Recipe::kegPrimingFactor ), - SET_REGULAR_FROM_NPB (m_notes , namedParameterBundle, PropertyNames::Recipe::notes ), - SET_REGULAR_FROM_NPB (m_tasteNotes , namedParameterBundle, PropertyNames::Recipe::tasteNotes ), + SET_REGULAR_FROM_NPB (m_notes , namedParameterBundle, PropertyNames::Recipe::notes , ""), + SET_REGULAR_FROM_NPB (m_tasteNotes , namedParameterBundle, PropertyNames::Recipe::tasteNotes , ""), SET_REGULAR_FROM_NPB (m_tasteRating , namedParameterBundle, PropertyNames::Recipe::tasteRating ), - SET_REGULAR_FROM_NPB (m_styleId , namedParameterBundle, PropertyNames::Recipe::styleId ), - SET_REGULAR_FROM_NPB (m_equipmentId , namedParameterBundle, PropertyNames::Recipe::equipmentId ), - SET_REGULAR_FROM_NPB (m_mashId , namedParameterBundle, PropertyNames::Recipe::mashId ), + // Although some of these IDs are not really optional, we need default values for them for when reading from BeerXML or BeerJSON + SET_REGULAR_FROM_NPB (m_styleId , namedParameterBundle, PropertyNames::Recipe::styleId , -1), + SET_REGULAR_FROM_NPB (m_equipmentId , namedParameterBundle, PropertyNames::Recipe::equipmentId , -1), + SET_REGULAR_FROM_NPB (m_mashId , namedParameterBundle, PropertyNames::Recipe::mashId , -1), SET_REGULAR_FROM_NPB (m_boilId , namedParameterBundle, PropertyNames::Recipe::boilId , -1), SET_REGULAR_FROM_NPB (m_fermentationId , namedParameterBundle, PropertyNames::Recipe::fermentationId , -1), - SET_REGULAR_FROM_NPB (m_beerAcidity_pH , namedParameterBundle, PropertyNames::Recipe::beerAcidity_pH ), - SET_REGULAR_FROM_NPB (m_apparentAttenuation_pct, namedParameterBundle, PropertyNames::Recipe::apparentAttenuation_pct), + SET_REGULAR_FROM_NPB (m_beerAcidity_pH , namedParameterBundle, PropertyNames::Recipe::beerAcidity_pH , std::nullopt), + SET_REGULAR_FROM_NPB (m_apparentAttenuation_pct, namedParameterBundle, PropertyNames::Recipe::apparentAttenuation_pct, std::nullopt), // Note that, although we read them in here, the OG and FG are going to get recalculated when someone first tries to // access them. SET_REGULAR_FROM_NPB (m_og , namedParameterBundle, PropertyNames::Recipe::og ), SET_REGULAR_FROM_NPB (m_fg , namedParameterBundle, PropertyNames::Recipe::fg ), - SET_REGULAR_FROM_NPB (m_locked , namedParameterBundle, PropertyNames::Recipe::locked ), + SET_REGULAR_FROM_NPB (m_locked , namedParameterBundle, PropertyNames::Recipe::locked , false), + m_calcsEnabled {true}, m_uninitializedCalcs {true}, m_uninitializedCalcsMutex{}, m_recalcMutex {}, - SET_REGULAR_FROM_NPB (m_ancestor_id , namedParameterBundle, PropertyNames::Recipe::ancestorId ), + SET_REGULAR_FROM_NPB (m_ancestor_id , namedParameterBundle, PropertyNames::Recipe::ancestorId , -1), m_ancestors {}, m_hasDescendants {false} { // At this stage, we haven't set any Hops, Fermentables, etc. This is deliberate because the caller typically needs // to access subsidiary records to obtain this info. Callers will usually use setters (setHopIds, etc but via // setProperty) to finish constructing the object. + CONSTRUCTOR_END return; } @@ -1774,7 +1788,7 @@ Recipe::Recipe(Recipe const & other) : m_asstBrewer {other.m_asstBrewer }, m_batchSize_l {other.m_batchSize_l }, m_efficiency_pct {other.m_efficiency_pct }, - m_age {other.m_age }, + m_age_days {other.m_age_days }, m_ageTemp_c {other.m_ageTemp_c }, m_date {other.m_date }, m_carbonation_vols {other.m_carbonation_vols }, @@ -1796,6 +1810,7 @@ Recipe::Recipe(Recipe const & other) : m_og {other.m_og }, m_fg {other.m_fg }, m_locked {other.m_locked }, + m_calcsEnabled {other.m_calcsEnabled }, m_uninitializedCalcs {true }, m_uninitializedCalcsMutex{}, m_recalcMutex {}, @@ -1843,6 +1858,10 @@ Recipe::Recipe(Recipe const & other) : this->recalcAll(); + // This turns on writing to the DB and sending signals when things change. However, we do this _after_ calling + // recalcAll() as the newly constructed object will not yet be stored in the DB and won't yet have anyone listening + // to its signals. + CONSTRUCTOR_END return; } @@ -2404,9 +2423,7 @@ void Recipe::setEfficiency_pct(double val) { } void Recipe::setAsstBrewer(const QString & val) { - SET_AND_NOTIFY(PropertyNames::Recipe::asstBrewer, - this->m_asstBrewer, - val); + SET_AND_NOTIFY(PropertyNames::Recipe::asstBrewer, this->m_asstBrewer, val); return; } @@ -2435,12 +2452,12 @@ void Recipe::setFg(double val) { return; } -void Recipe::setAge_days(double val) { - SET_AND_NOTIFY(PropertyNames::Recipe::age_days, this->m_age, this->enforceMin(val, "age")); +void Recipe::setAge_days(std::optional val) { + SET_AND_NOTIFY(PropertyNames::Recipe::age_days, this->m_age_days, this->enforceMin(val, "age_days")); return; } -void Recipe::setAgeTemp_c(double val) { +void Recipe::setAgeTemp_c(std::optional val) { SET_AND_NOTIFY(PropertyNames::Recipe::ageTemp_c, this->m_ageTemp_c, val); return; } @@ -2483,7 +2500,7 @@ void Recipe::setKegPrimingFactor(double val) { void Recipe::setBeerAcidity_pH (std::optional const val) { SET_AND_NOTIFY(PropertyNames::Recipe::beerAcidity_pH , this->m_beerAcidity_pH , val); return; } void Recipe::setApparentAttenuation_pct(std::optional const val) { SET_AND_NOTIFY(PropertyNames::Recipe::apparentAttenuation_pct, this->m_apparentAttenuation_pct, val); return; } -void Recipe::setLocked(bool isLocked) { +void Recipe::setLocked(bool const isLocked) { // Locking a Recipe doesn't count as changing it for the purposes of versioning or the UI, so no call to setAndNotify // here. if (this->newValueMatchesExisting(PropertyNames::Recipe::locked, this->m_locked, isLocked)) { @@ -2494,6 +2511,11 @@ void Recipe::setLocked(bool isLocked) { return; } +void Recipe::setCalcsEnabled(bool const val) { + this->m_calcsEnabled = val; + return; +} + QList Recipe::ancestors() const { // If we know we have some ancestors, and we didn't yet load them, do so now if (this->m_ancestor_id > 0 && this->m_ancestor_id != this->key() && this->m_ancestors.size() == 0) { @@ -2726,14 +2748,16 @@ bool Recipe::forcedCarbonation() const { return m_forcedCarbonation; } double Recipe::batchSize_l() const { return m_batchSize_l; } double Recipe::efficiency_pct() const { return m_efficiency_pct; } double Recipe::tasteRating() const { return m_tasteRating; } -double Recipe::age_days() const { return m_age; } -double Recipe::ageTemp_c() const { return m_ageTemp_c; } +std::optional Recipe::age_days() const { return m_age_days; } +std::optional Recipe::ageTemp_c() const { return m_ageTemp_c; } double Recipe::carbonationTemp_c() const { return m_carbonationTemp_c; } double Recipe::primingSugarEquiv() const { return m_primingSugarEquiv; } double Recipe::kegPrimingFactor() const { return m_kegPrimingFactor; } std::optional Recipe::date() const { return m_date; } std::optional Recipe::carbonation_vols() const { return m_carbonation_vols; } -bool Recipe::locked() const { return m_locked; } +bool Recipe::locked() const { return m_locked ; } +bool Recipe::calcsEnabled() const { return m_calcsEnabled; } + // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ std::optional Recipe::beerAcidity_pH () const { return m_beerAcidity_pH ; } std::optional Recipe::apparentAttenuation_pct() const { return m_apparentAttenuation_pct; } @@ -2770,6 +2794,11 @@ void Recipe::recalcIfNeeded(QString classNameOfWhatWasAddedOrChanged) { } void Recipe::recalcAll() { + if (!this->m_calcsEnabled) { + qDebug() << Q_FUNC_INFO << "Calculations disabled"; + return; + } + // WARNING // Infinite recursion possible, since these methods will emit changed(), // causing other objects to call finalVolume_l() for example, which may diff --git a/src/model/Recipe.h b/src/model/Recipe.h index b9bee3de..11bd0eb3 100644 --- a/src/model/Recipe.h +++ b/src/model/Recipe.h @@ -59,6 +59,7 @@ AddPropertyName(boilId ) AddPropertyName(boilVolume_l ) AddPropertyName(brewer ) AddPropertyName(brewNotes ) +AddPropertyName(calcsEnabled ) AddPropertyName(caloriesPer33cl ) AddPropertyName(caloriesPerLiter ) AddPropertyName(caloriesPerUs12oz ) @@ -242,10 +243,14 @@ class Recipe : public NamedEntity, * .:TBD:. This is stored as a double but the UI constrains it to an unsigned int. */ Q_PROPERTY(double tasteRating READ tasteRating WRITE setTasteRating ) - //! \brief The number of days to age the beer after bottling. - Q_PROPERTY(double age_days READ age_days WRITE setAge_days ) - //! \brief The temp in C as beer is aging after bottling. - Q_PROPERTY(double ageTemp_c READ ageTemp_c WRITE setAgeTemp_c ) + /** + * \brief The number of days to age the beer after bottling. This is an optional field in BeerXML, but is not + * directly part of BeerJSON. .:TBD:. We should probably map this and ageTemp_c to the BeerJSON equivalent -- + * just as soon as we work out what that is! + */ + Q_PROPERTY(std::optional age_days READ age_days WRITE setAge_days ) + //! \brief The temp in C as beer is ageing after bottling. + Q_PROPERTY(std::optional ageTemp_c READ ageTemp_c WRITE setAgeTemp_c ) /** * \brief In BeerXML, a recipe has a date which is supposed to be when it was brewed. This is slightly meaningless * unless you take it to mean "first brewed". We then take that to be the "created" date in BeerJSON and our @@ -277,6 +282,16 @@ class Recipe : public NamedEntity, Q_PROPERTY(double kegPrimingFactor READ kegPrimingFactor WRITE setKegPrimingFactor ) //! \brief Whether the recipe is locked against changes Q_PROPERTY(bool locked READ locked WRITE setLocked ) + + /** + * \brief Whether calculations are enabled. By default, all the automatic calculations are enabled. However, it is + * helpful to be able to disable them when we are reading a recipe in from serialisation, otherwise, we can + * end up trying to calculate things before we've finished reading in data (eg for fermentable additions). + * + * TBD: Should perhaps move this up to \c NamedEntity. + */ + Q_PROPERTY(bool calcsEnabled READ calcsEnabled WRITE setCalcsEnabled STORED false) + // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ //! \brief The final beer pH at the end of fermentation. Q_PROPERTY(std::optional beerAcidity_pH READ beerAcidity_pH WRITE setBeerAcidity_pH) @@ -504,8 +519,8 @@ class Recipe : public NamedEntity, double secondaryTemp_c () const; double tertiaryAge_days () const; double tertiaryTemp_c () const; - double age_days () const; - double ageTemp_c () const; + std::optional age_days () const; + std::optional ageTemp_c () const; std::optional date () const; std::optional carbonation_vols () const; bool forcedCarbonation () const; @@ -514,6 +529,7 @@ class Recipe : public NamedEntity, double primingSugarEquiv () const; double kegPrimingFactor () const; bool locked () const; + bool calcsEnabled () const; // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ std::optional beerAcidity_pH() const; std::optional apparentAttenuation_pct() const; @@ -661,8 +677,8 @@ class Recipe : public NamedEntity, void setOg (double const val); void setFg (double const val); void setFermentationStages(int const val); - void setAge_days (double const val); - void setAgeTemp_c (double const val); + void setAge_days (std::optional const val); + void setAgeTemp_c (std::optional const val); void setDate (std::optional const val); void setCarbonation_vols (std::optional const val); void setForcedCarbonation (bool const val); @@ -671,6 +687,7 @@ class Recipe : public NamedEntity, void setPrimingSugarEquiv (double const val); void setKegPrimingFactor (double const val); void setLocked (bool const val); + void setCalcsEnabled (bool const val); void setHasDescendants (bool const val); // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ void setBeerAcidity_pH (std::optional const val); @@ -709,8 +726,8 @@ public slots: double m_batchSize_l ; double m_efficiency_pct ; int m_fermentationStages ; - double m_age ; - double m_ageTemp_c ; + std::optional m_age_days ; + std::optional m_ageTemp_c ; std::optional m_date ; std::optional m_carbonation_vols ; bool m_forcedCarbonation ; @@ -735,7 +752,8 @@ public slots: double m_og ; double m_fg ; - bool m_locked; + bool m_locked ; + bool m_calcsEnabled; // True when constructed, indicates whether recalcAll has been called. bool m_uninitializedCalcs ; diff --git a/src/model/RecipeAddition.cpp b/src/model/RecipeAddition.cpp index 78677e4b..997646ef 100644 --- a/src/model/RecipeAddition.cpp +++ b/src/model/RecipeAddition.cpp @@ -81,6 +81,8 @@ RecipeAddition::RecipeAddition(QString name, int const recipeId, int const ingre m_addAtGravity_sg{std::nullopt}, m_addAtAcidity_pH{std::nullopt}, m_duration_mins {std::nullopt} { + + CONSTRUCTOR_END return; } @@ -88,22 +90,26 @@ RecipeAddition::RecipeAddition(NamedParameterBundle const & namedParameterBundle IngredientInRecipe{namedParameterBundle}, // Note that we do not set m_stage here as it is for subclasses to determine how that should be defaulted if it is // not present. - SET_REGULAR_FROM_NPB (m_step , namedParameterBundle, PropertyNames::RecipeAddition::step ), - SET_REGULAR_FROM_NPB (m_addAtTime_mins , namedParameterBundle, PropertyNames::RecipeAddition::addAtTime_mins ), - SET_REGULAR_FROM_NPB (m_addAtGravity_sg, namedParameterBundle, PropertyNames::RecipeAddition::addAtGravity_sg), - SET_REGULAR_FROM_NPB (m_addAtAcidity_pH, namedParameterBundle, PropertyNames::RecipeAddition::addAtAcidity_pH), - SET_REGULAR_FROM_NPB (m_duration_mins , namedParameterBundle, PropertyNames::RecipeAddition::duration_mins ) { + SET_REGULAR_FROM_NPB (m_step , namedParameterBundle, PropertyNames::RecipeAddition::step , std::nullopt), + SET_REGULAR_FROM_NPB (m_addAtTime_mins , namedParameterBundle, PropertyNames::RecipeAddition::addAtTime_mins , std::nullopt), + SET_REGULAR_FROM_NPB (m_addAtGravity_sg, namedParameterBundle, PropertyNames::RecipeAddition::addAtGravity_sg, std::nullopt), + SET_REGULAR_FROM_NPB (m_addAtAcidity_pH, namedParameterBundle, PropertyNames::RecipeAddition::addAtAcidity_pH, std::nullopt), + SET_REGULAR_FROM_NPB (m_duration_mins , namedParameterBundle, PropertyNames::RecipeAddition::duration_mins , std::nullopt) { + + CONSTRUCTOR_END return; } RecipeAddition::RecipeAddition(RecipeAddition const & other) : - IngredientInRecipe{other }, + IngredientInRecipe{other }, m_stage {other.m_stage }, m_step {other.m_step }, m_addAtTime_mins {other.m_addAtTime_mins }, m_addAtGravity_sg{other.m_addAtGravity_sg}, m_addAtAcidity_pH{other.m_addAtAcidity_pH}, m_duration_mins {other.m_duration_mins } { + + CONSTRUCTOR_END return; } diff --git a/src/model/RecipeAdditionFermentable.cpp b/src/model/RecipeAdditionFermentable.cpp index e1a5c492..841ac189 100644 --- a/src/model/RecipeAdditionFermentable.cpp +++ b/src/model/RecipeAdditionFermentable.cpp @@ -50,6 +50,8 @@ RecipeAdditionFermentable::RecipeAdditionFermentable(QString name, int const rec RecipeAddition{name, recipeId, ingredientId}, RecipeAdditionBase{}, IngredientAmount{} { + + CONSTRUCTOR_END return; } @@ -64,6 +66,8 @@ RecipeAdditionFermentable::RecipeAdditionFermentable(NamedParameterBundle const m_stage = namedParameterBundle.val(PropertyNames::RecipeAddition::stage, RecipeAddition::Stage::Boil); /// qDebug() << Q_FUNC_INFO << "RecipeAdditionFermentable #" << this->key() << ": Recipe #" << this->m_recipeId << ", Fermentable #" << this->m_ingredientId; + + CONSTRUCTOR_END return; } @@ -71,6 +75,8 @@ RecipeAdditionFermentable::RecipeAdditionFermentable(RecipeAdditionFermentable c RecipeAddition{other}, RecipeAdditionBase{}, IngredientAmount{other} { + + CONSTRUCTOR_END return; } diff --git a/src/model/RecipeAdditionHop.cpp b/src/model/RecipeAdditionHop.cpp index d77c11ed..56f06abd 100644 --- a/src/model/RecipeAdditionHop.cpp +++ b/src/model/RecipeAdditionHop.cpp @@ -68,6 +68,8 @@ RecipeAdditionHop::RecipeAdditionHop(QString name, int const recipeId, int const RecipeAddition{name, recipeId, ingredientId}, RecipeAdditionBase{}, IngredientAmount{} { + + CONSTRUCTOR_END return; } @@ -82,6 +84,8 @@ RecipeAdditionHop::RecipeAdditionHop(NamedParameterBundle const & namedParameter m_stage = namedParameterBundle.val(PropertyNames::RecipeAddition::stage, RecipeAddition::Stage::Boil); /// qDebug() << Q_FUNC_INFO << "RecipeAdditionHop #" << this->key() << ": Recipe #" << this->m_recipeId << ", Hop #" << this->m_ingredientId; + + CONSTRUCTOR_END return; } @@ -89,6 +93,8 @@ RecipeAdditionHop::RecipeAdditionHop(RecipeAdditionHop const & other) : RecipeAddition{other}, RecipeAdditionBase{}, IngredientAmount{other} { + + CONSTRUCTOR_END return; } diff --git a/src/model/RecipeAdditionMisc.cpp b/src/model/RecipeAdditionMisc.cpp index e81ff21b..ca4bf365 100644 --- a/src/model/RecipeAdditionMisc.cpp +++ b/src/model/RecipeAdditionMisc.cpp @@ -68,6 +68,8 @@ RecipeAdditionMisc::RecipeAdditionMisc(QString name, int const recipeId, int con RecipeAddition{name, recipeId, ingredientId}, RecipeAdditionBase{}, IngredientAmount{} { + + CONSTRUCTOR_END return; } @@ -81,6 +83,8 @@ RecipeAdditionMisc::RecipeAdditionMisc(NamedParameterBundle const & namedParamet // m_stage = namedParameterBundle.val(PropertyNames::RecipeAddition::stage, RecipeAddition::Stage::Boil); + + CONSTRUCTOR_END return; } @@ -88,6 +92,8 @@ RecipeAdditionMisc::RecipeAdditionMisc(RecipeAdditionMisc const & other) : RecipeAddition{other}, RecipeAdditionBase{}, IngredientAmount{other} { + + CONSTRUCTOR_END return; } diff --git a/src/model/RecipeAdditionYeast.cpp b/src/model/RecipeAdditionYeast.cpp index a974ae8b..949f86bf 100644 --- a/src/model/RecipeAdditionYeast.cpp +++ b/src/model/RecipeAdditionYeast.cpp @@ -56,6 +56,8 @@ RecipeAdditionYeast::RecipeAdditionYeast(QString name, int const recipeId, int c m_attenuation_pct {std::nullopt}, m_timesCultured {std::nullopt}, m_cellCountBillions{std::nullopt} { + + CONSTRUCTOR_END return; } @@ -73,6 +75,8 @@ RecipeAdditionYeast::RecipeAdditionYeast(NamedParameterBundle const & namedParam m_stage = namedParameterBundle.val(PropertyNames::RecipeAddition::stage, RecipeAddition::Stage::Fermentation); /// qDebug() << Q_FUNC_INFO << "RecipeAdditionYeast #" << this->key() << ": Recipe #" << this->m_recipeId << ", Yeast #" << this->m_ingredientId; + + CONSTRUCTOR_END return; } @@ -83,6 +87,8 @@ RecipeAdditionYeast::RecipeAdditionYeast(RecipeAdditionYeast const & other) : m_attenuation_pct {other.m_attenuation_pct }, m_timesCultured {other.m_timesCultured }, m_cellCountBillions{other.m_cellCountBillions} { + + CONSTRUCTOR_END return; } diff --git a/src/model/RecipeAdjustmentSalt.cpp b/src/model/RecipeAdjustmentSalt.cpp index 9b0fbbc0..89f40c9b 100644 --- a/src/model/RecipeAdjustmentSalt.cpp +++ b/src/model/RecipeAdjustmentSalt.cpp @@ -67,6 +67,8 @@ RecipeAdjustmentSalt::RecipeAdjustmentSalt(QString name, int const recipeId, int IngredientInRecipe{name, recipeId, saltId}, RecipeAdditionBase{}, IngredientAmount{} { + + CONSTRUCTOR_END return; } @@ -74,6 +76,8 @@ RecipeAdjustmentSalt::RecipeAdjustmentSalt(NamedParameterBundle const & namedPar IngredientInRecipe{namedParameterBundle}, RecipeAdditionBase{}, IngredientAmount{namedParameterBundle} { + + CONSTRUCTOR_END return; } @@ -81,6 +85,8 @@ RecipeAdjustmentSalt::RecipeAdjustmentSalt(RecipeAdjustmentSalt const & other) : IngredientInRecipe{other}, RecipeAdditionBase{}, IngredientAmount{other} { + + CONSTRUCTOR_END return; } diff --git a/src/model/RecipeUseOfWater.cpp b/src/model/RecipeUseOfWater.cpp index 86714c4b..3b7c1d6c 100644 --- a/src/model/RecipeUseOfWater.cpp +++ b/src/model/RecipeUseOfWater.cpp @@ -47,18 +47,24 @@ static_assert(std::is_base_of::value); RecipeUseOfWater::RecipeUseOfWater(QString name, int const recipeId, int const ingredientId) : IngredientInRecipe{name, recipeId, ingredientId}, m_volume_l {0.0} { + + CONSTRUCTOR_END return; } RecipeUseOfWater::RecipeUseOfWater(NamedParameterBundle const & namedParameterBundle) : IngredientInRecipe{namedParameterBundle}, SET_REGULAR_FROM_NPB (m_volume_l , namedParameterBundle, PropertyNames::RecipeUseOfWater::volume_l ) { + + CONSTRUCTOR_END return; } RecipeUseOfWater::RecipeUseOfWater(RecipeUseOfWater const & other) : IngredientInRecipe{other }, m_volume_l {other.m_volume_l } { + + CONSTRUCTOR_END return; } diff --git a/src/model/Salt.cpp b/src/model/Salt.cpp index 49d562ef..105d48b0 100644 --- a/src/model/Salt.cpp +++ b/src/model/Salt.cpp @@ -96,6 +96,8 @@ Salt::Salt(QString name) : Ingredient {name}, m_type {Salt::Type::CaCl2}, m_percent_acid{std::nullopt} { + + CONSTRUCTOR_END return; } @@ -103,13 +105,17 @@ Salt::Salt(NamedParameterBundle const & namedParameterBundle) : Ingredient {namedParameterBundle}, SET_REGULAR_FROM_NPB (m_type , namedParameterBundle, PropertyNames::Salt::type ), SET_REGULAR_FROM_NPB (m_percent_acid, namedParameterBundle, PropertyNames::Salt::percentAcid) { + + CONSTRUCTOR_END return; } Salt::Salt(Salt const & other) : - Ingredient {other }, + Ingredient {other }, m_type {other.m_type }, m_percent_acid{other.m_percent_acid} { + + CONSTRUCTOR_END return; } diff --git a/src/model/Step.cpp b/src/model/Step.cpp index 72099a1c..4524da07 100644 --- a/src/model/Step.cpp +++ b/src/model/Step.cpp @@ -90,25 +90,36 @@ Step::Step(QString name) : m_rampTime_mins {std::nullopt}, m_startAcidity_pH{std::nullopt}, m_endAcidity_pH {std::nullopt} { + + CONSTRUCTOR_END return; } Step::Step(NamedParameterBundle const & namedParameterBundle) : NamedEntity (namedParameterBundle ), - SET_REGULAR_FROM_NPB (m_stepTime_mins , namedParameterBundle, PropertyNames::Step::stepTime_mins ), - SET_REGULAR_FROM_NPB (m_startTemp_c , namedParameterBundle, PropertyNames::Step::startTemp_c ), - SET_REGULAR_FROM_NPB (m_endTemp_c , namedParameterBundle, PropertyNames::Step:: endTemp_c ), - SET_REGULAR_FROM_NPB (m_stepNumber , namedParameterBundle, PropertyNames::Step::stepNumber ), - SET_REGULAR_FROM_NPB (m_ownerId , namedParameterBundle, PropertyNames::Step::ownerId ), + // See below for m_stepTime_mins + SET_REGULAR_FROM_NPB (m_startTemp_c , namedParameterBundle, PropertyNames::Step::startTemp_c , std::nullopt), + SET_REGULAR_FROM_NPB (m_endTemp_c , namedParameterBundle, PropertyNames::Step:: endTemp_c , std::nullopt), + SET_REGULAR_FROM_NPB (m_stepNumber , namedParameterBundle, PropertyNames::Step::stepNumber , 0), + SET_REGULAR_FROM_NPB (m_ownerId , namedParameterBundle, PropertyNames::Step::ownerId , -1), // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - SET_REGULAR_FROM_NPB (m_description , namedParameterBundle, PropertyNames::Step::description ), - // rampTime_mins needs a default value because it might not be used -- see below + SET_REGULAR_FROM_NPB (m_description , namedParameterBundle, PropertyNames::Step::description , ""), SET_REGULAR_FROM_NPB (m_rampTime_mins , namedParameterBundle, PropertyNames::Step::rampTime_mins , std::nullopt), - SET_REGULAR_FROM_NPB (m_startAcidity_pH, namedParameterBundle, PropertyNames::Step::startAcidity_pH), - SET_REGULAR_FROM_NPB (m_endAcidity_pH , namedParameterBundle, PropertyNames::Step:: endAcidity_pH) { + SET_REGULAR_FROM_NPB (m_startAcidity_pH, namedParameterBundle, PropertyNames::Step::startAcidity_pH, std::nullopt), + SET_REGULAR_FROM_NPB (m_endAcidity_pH , namedParameterBundle, PropertyNames::Step:: endAcidity_pH, std::nullopt) { // It would be nice to be able to assert here that rampTime_mins is present in the bundle only if the subclass // supports it. However, we cannot safely call a virtual member function from a base class constructor, so we have // to do such asserts in the derived classes. + + // If we're being constructed from a BeerXML file, we use the property stepTime_days for RECIPE > PRIMARY_AGE etc + // Otherwise we use the stepTime_mins property + if (!SET_IF_PRESENT_FROM_NPB_NO_MV(Step::setStepTime_mins, namedParameterBundle, PropertyNames::Step::stepTime_mins) && + !SET_IF_PRESENT_FROM_NPB_NO_MV(Step::setStepTime_days, namedParameterBundle, PropertyNames::Step::stepTime_days)) { + qWarning() << Q_FUNC_INFO << "Neither stepTime_mins nor stepTime_days set in bundle, so step time will be 0."; + this->m_stepTime_mins = 0.0; + } + + CONSTRUCTOR_END return; } @@ -124,6 +135,8 @@ Step::Step(Step const & other) : m_rampTime_mins {other.m_rampTime_mins }, m_startAcidity_pH{other.m_startAcidity_pH}, m_endAcidity_pH {other.m_endAcidity_pH } { + + CONSTRUCTOR_END return; } diff --git a/src/model/Step.h b/src/model/Step.h index 9609decb..e6b47c65 100644 --- a/src/model/Step.h +++ b/src/model/Step.h @@ -37,7 +37,7 @@ AddPropertyName(rampTime_mins ) AddPropertyName(startAcidity_pH) AddPropertyName(startTemp_c ) AddPropertyName(stepNumber ) -AddPropertyName(stepTime_days ) +AddPropertyName(stepTime_days ) // Mostly needed for BeerXML AddPropertyName(stepTime_mins ) #undef AddPropertyName //=========================================== End of property name constants =========================================== diff --git a/src/model/StepExtended.cpp b/src/model/StepExtended.cpp index 4f1bceec..4863932d 100644 --- a/src/model/StepExtended.cpp +++ b/src/model/StepExtended.cpp @@ -48,6 +48,8 @@ StepExtended::StepExtended(QString name) : Step {name}, m_startGravity_sg{std::nullopt}, m_endGravity_sg {std::nullopt} { + + CONSTRUCTOR_END return; } @@ -55,6 +57,8 @@ StepExtended::StepExtended(NamedParameterBundle const & namedParameterBundle) : Step (namedParameterBundle ), SET_REGULAR_FROM_NPB (m_startGravity_sg, namedParameterBundle, PropertyNames::StepExtended::startGravity_sg), SET_REGULAR_FROM_NPB (m_endGravity_sg , namedParameterBundle, PropertyNames::StepExtended::endGravity_sg ) { + + CONSTRUCTOR_END return; } @@ -62,6 +66,8 @@ StepExtended::StepExtended(StepExtended const & other) : Step {other}, m_startGravity_sg{other.m_startGravity_sg}, m_endGravity_sg {other.m_endGravity_sg } { + + CONSTRUCTOR_END return; } diff --git a/src/model/Style.cpp b/src/model/Style.cpp index 78877084..96b4e9e6 100644 --- a/src/model/Style.cpp +++ b/src/model/Style.cpp @@ -131,6 +131,8 @@ Style::Style(QString name) : m_flavor {"" }, m_mouthfeel {"" }, m_overallImpression{"" } { + + CONSTRUCTOR_END return; } @@ -150,19 +152,21 @@ Style::Style(NamedParameterBundle const & namedParameterBundle) : SET_REGULAR_FROM_NPB (m_ibuMax , namedParameterBundle, PropertyNames::Style::ibuMax ), SET_REGULAR_FROM_NPB (m_colorMin_srm , namedParameterBundle, PropertyNames::Style::colorMin_srm ), SET_REGULAR_FROM_NPB (m_colorMax_srm , namedParameterBundle, PropertyNames::Style::colorMax_srm ), - SET_REGULAR_FROM_NPB (m_carbMin_vol , namedParameterBundle, PropertyNames::Style::carbMin_vol ), - SET_REGULAR_FROM_NPB (m_carbMax_vol , namedParameterBundle, PropertyNames::Style::carbMax_vol ), - SET_REGULAR_FROM_NPB (m_abvMin_pct , namedParameterBundle, PropertyNames::Style::abvMin_pct ), - SET_REGULAR_FROM_NPB (m_abvMax_pct , namedParameterBundle, PropertyNames::Style::abvMax_pct ), + SET_REGULAR_FROM_NPB (m_carbMin_vol , namedParameterBundle, PropertyNames::Style::carbMin_vol , std::nullopt), + SET_REGULAR_FROM_NPB (m_carbMax_vol , namedParameterBundle, PropertyNames::Style::carbMax_vol , std::nullopt), + SET_REGULAR_FROM_NPB (m_abvMin_pct , namedParameterBundle, PropertyNames::Style::abvMin_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_abvMax_pct , namedParameterBundle, PropertyNames::Style::abvMax_pct , std::nullopt), SET_REGULAR_FROM_NPB (m_notes , namedParameterBundle, PropertyNames::Style::notes ), SET_REGULAR_FROM_NPB (m_ingredients , namedParameterBundle, PropertyNames::Style::ingredients ), SET_REGULAR_FROM_NPB (m_examples , namedParameterBundle, PropertyNames::Style::examples ), // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - SET_REGULAR_FROM_NPB (m_aroma , namedParameterBundle, PropertyNames::Style::aroma ), - SET_REGULAR_FROM_NPB (m_appearance , namedParameterBundle, PropertyNames::Style::appearance ), - SET_REGULAR_FROM_NPB (m_flavor , namedParameterBundle, PropertyNames::Style::flavor ), - SET_REGULAR_FROM_NPB (m_mouthfeel , namedParameterBundle, PropertyNames::Style::mouthfeel ), - SET_REGULAR_FROM_NPB (m_overallImpression, namedParameterBundle, PropertyNames::Style::overallImpression) { + SET_REGULAR_FROM_NPB (m_aroma , namedParameterBundle, PropertyNames::Style::aroma , ""), + SET_REGULAR_FROM_NPB (m_appearance , namedParameterBundle, PropertyNames::Style::appearance , ""), + SET_REGULAR_FROM_NPB (m_flavor , namedParameterBundle, PropertyNames::Style::flavor , ""), + SET_REGULAR_FROM_NPB (m_mouthfeel , namedParameterBundle, PropertyNames::Style::mouthfeel , ""), + SET_REGULAR_FROM_NPB (m_overallImpression, namedParameterBundle, PropertyNames::Style::overallImpression, "") { + + CONSTRUCTOR_END return; } @@ -195,6 +199,8 @@ Style::Style(Style const & other) : m_flavor {other.m_flavor }, m_mouthfeel {other.m_mouthfeel }, m_overallImpression{other.m_overallImpression} { + + CONSTRUCTOR_END return; } diff --git a/src/model/Water.cpp b/src/model/Water.cpp index 8aef5e99..f5053681 100644 --- a/src/model/Water.cpp +++ b/src/model/Water.cpp @@ -69,7 +69,7 @@ bool Water::isEqualTo(NamedEntity const & other) const { Utils::AutoCompare(this->m_iron_ppm , rhs.m_iron_ppm ) && Utils::AutoCompare(this->m_nitrate_ppm , rhs.m_nitrate_ppm ) && Utils::AutoCompare(this->m_nitrite_ppm , rhs.m_nitrite_ppm ) && - Utils::AutoCompare(this->m_flouride_ppm , rhs.m_flouride_ppm ) && + Utils::AutoCompare(this->m_fluoride_ppm , rhs.m_fluoride_ppm ) && Utils::AutoCompare(this->m_sulfate_ppm , rhs.m_sulfate_ppm ) && Utils::AutoCompare(this->m_chloride_ppm , rhs.m_chloride_ppm ) && Utils::AutoCompare(this->m_sodium_ppm , rhs.m_sodium_ppm ) && @@ -117,7 +117,7 @@ TypeLookup const Water::typeLookup { PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Water::iron_ppm , Water::m_iron_ppm , Measurement::PhysicalQuantity::MassFractionOrConc), PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Water::nitrate_ppm , Water::m_nitrate_ppm , Measurement::PhysicalQuantity::MassFractionOrConc), PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Water::nitrite_ppm , Water::m_nitrite_ppm , Measurement::PhysicalQuantity::MassFractionOrConc), - PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Water::flouride_ppm , Water::m_flouride_ppm , Measurement::PhysicalQuantity::MassFractionOrConc), + PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Water::fluoride_ppm , Water::m_fluoride_ppm , Measurement::PhysicalQuantity::MassFractionOrConc), }, // Parent classes lookup {&OutlineableNamedEntity::typeLookup, @@ -148,7 +148,9 @@ Water::Water(QString name) : m_iron_ppm {std::nullopt}, m_nitrate_ppm {std::nullopt}, m_nitrite_ppm {std::nullopt}, - m_flouride_ppm {std::nullopt} { + m_fluoride_ppm {std::nullopt} { + + CONSTRUCTOR_END return; } @@ -162,20 +164,22 @@ Water::Water(NamedParameterBundle const & namedParameterBundle) : SET_REGULAR_FROM_NPB (m_chloride_ppm , namedParameterBundle, PropertyNames::Water::chloride_ppm ), SET_REGULAR_FROM_NPB (m_sodium_ppm , namedParameterBundle, PropertyNames::Water::sodium_ppm ), SET_REGULAR_FROM_NPB (m_magnesium_ppm , namedParameterBundle, PropertyNames::Water::magnesium_ppm ), - SET_REGULAR_FROM_NPB (m_ph , namedParameterBundle, PropertyNames::Water::ph ), - SET_REGULAR_FROM_NPB (m_alkalinity_ppm , namedParameterBundle, PropertyNames::Water::alkalinity_ppm ), + SET_REGULAR_FROM_NPB (m_ph , namedParameterBundle, PropertyNames::Water::ph , std::nullopt), + SET_REGULAR_FROM_NPB (m_alkalinity_ppm , namedParameterBundle, PropertyNames::Water::alkalinity_ppm , std::nullopt), SET_REGULAR_FROM_NPB (m_notes , namedParameterBundle, PropertyNames::Water::notes ), SET_OPT_ENUM_FROM_NPB(m_type , Water::Type, namedParameterBundle, PropertyNames::Water::type ), - SET_REGULAR_FROM_NPB (m_mashRo_pct , namedParameterBundle, PropertyNames::Water::mashRo_pct ), - SET_REGULAR_FROM_NPB (m_spargeRo_pct , namedParameterBundle, PropertyNames::Water::spargeRo_pct ), + SET_REGULAR_FROM_NPB (m_mashRo_pct , namedParameterBundle, PropertyNames::Water::mashRo_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_spargeRo_pct , namedParameterBundle, PropertyNames::Water::spargeRo_pct , std::nullopt), SET_REGULAR_FROM_NPB (m_alkalinity_as_hco3, namedParameterBundle, PropertyNames::Water::alkalinityAsHCO3), // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - SET_REGULAR_FROM_NPB (m_carbonate_ppm , namedParameterBundle, PropertyNames::Water::carbonate_ppm ), - SET_REGULAR_FROM_NPB (m_potassium_ppm , namedParameterBundle, PropertyNames::Water::potassium_ppm ), - SET_REGULAR_FROM_NPB (m_iron_ppm , namedParameterBundle, PropertyNames::Water::iron_ppm ), - SET_REGULAR_FROM_NPB (m_nitrate_ppm , namedParameterBundle, PropertyNames::Water::nitrate_ppm ), - SET_REGULAR_FROM_NPB (m_nitrite_ppm , namedParameterBundle, PropertyNames::Water::nitrite_ppm ), - SET_REGULAR_FROM_NPB (m_flouride_ppm , namedParameterBundle, PropertyNames::Water::flouride_ppm ) { + SET_REGULAR_FROM_NPB (m_carbonate_ppm , namedParameterBundle, PropertyNames::Water::carbonate_ppm , std::nullopt), + SET_REGULAR_FROM_NPB (m_potassium_ppm , namedParameterBundle, PropertyNames::Water::potassium_ppm , std::nullopt), + SET_REGULAR_FROM_NPB (m_iron_ppm , namedParameterBundle, PropertyNames::Water::iron_ppm , std::nullopt), + SET_REGULAR_FROM_NPB (m_nitrate_ppm , namedParameterBundle, PropertyNames::Water::nitrate_ppm , std::nullopt), + SET_REGULAR_FROM_NPB (m_nitrite_ppm , namedParameterBundle, PropertyNames::Water::nitrite_ppm , std::nullopt), + SET_REGULAR_FROM_NPB (m_fluoride_ppm , namedParameterBundle, PropertyNames::Water::fluoride_ppm , std::nullopt) { + + CONSTRUCTOR_END return; } @@ -202,7 +206,9 @@ Water::Water(Water const& other) : m_iron_ppm {other.m_iron_ppm }, m_nitrate_ppm {other.m_nitrate_ppm }, m_nitrite_ppm {other.m_nitrite_ppm }, - m_flouride_ppm {other.m_flouride_ppm } { + m_fluoride_ppm {other.m_fluoride_ppm } { + + CONSTRUCTOR_END return; } @@ -231,7 +237,7 @@ void Water::swap(NamedEntity & other) noexcept { std::swap(this->m_iron_ppm , otherWater.m_iron_ppm ); std::swap(this->m_nitrate_ppm , otherWater.m_nitrate_ppm ); std::swap(this->m_nitrite_ppm , otherWater.m_nitrite_ppm ); - std::swap(this->m_flouride_ppm , otherWater.m_flouride_ppm ); + std::swap(this->m_fluoride_ppm , otherWater.m_fluoride_ppm ); return; } @@ -283,7 +289,7 @@ Water & Water::operator=(Water other) { if (this->m_iron_ppm != other.m_iron_ppm ) { this->propagatePropertyChange(PropertyNames::Water::iron_ppm ); } if (this->m_nitrate_ppm != other.m_nitrate_ppm ) { this->propagatePropertyChange(PropertyNames::Water::nitrate_ppm ); } if (this->m_nitrite_ppm != other.m_nitrite_ppm ) { this->propagatePropertyChange(PropertyNames::Water::nitrite_ppm ); } - if (this->m_flouride_ppm != other.m_flouride_ppm ) { this->propagatePropertyChange(PropertyNames::Water::flouride_ppm ); } + if (this->m_fluoride_ppm != other.m_fluoride_ppm ) { this->propagatePropertyChange(PropertyNames::Water::fluoride_ppm ); } return *this; } @@ -310,7 +316,7 @@ std::optional Water::potassium_ppm () const { return std::optional Water::iron_ppm () const { return m_iron_ppm ; } std::optional Water::nitrate_ppm () const { return m_nitrate_ppm ; } std::optional Water::nitrite_ppm () const { return m_nitrite_ppm ; } -std::optional Water::flouride_ppm () const { return m_flouride_ppm ; } +std::optional Water::fluoride_ppm () const { return m_fluoride_ppm ; } //============================================= "SETTER" MEMBER FUNCTIONS ============================================== ///void Water::setAmount (double const val) { SET_AND_NOTIFY(PropertyNames::Water::amount , m_amount , val ); return; } @@ -334,7 +340,7 @@ void Water::setPotassium_ppm (std::optional const val) { SET_AND_NOTIF void Water::setIron_ppm (std::optional const val) { SET_AND_NOTIFY(PropertyNames::Water::iron_ppm , m_iron_ppm , val); return; } void Water::setNitrate_ppm (std::optional const val) { SET_AND_NOTIFY(PropertyNames::Water::nitrate_ppm , m_nitrate_ppm , val); return; } void Water::setNitrite_ppm (std::optional const val) { SET_AND_NOTIFY(PropertyNames::Water::nitrite_ppm , m_nitrite_ppm , val); return; } -void Water::setFlouride_ppm (std::optional const val) { SET_AND_NOTIFY(PropertyNames::Water::flouride_ppm , m_flouride_ppm , val); return; } +void Water::setFluoride_ppm (std::optional const val) { SET_AND_NOTIFY(PropertyNames::Water::fluoride_ppm , m_fluoride_ppm , val); return; } double Water::ppm(Water::Ion const ion) const { switch (ion) { diff --git a/src/model/Water.h b/src/model/Water.h index 2dbbfd76..55a46e03 100644 --- a/src/model/Water.h +++ b/src/model/Water.h @@ -40,7 +40,7 @@ AddPropertyName(bicarbonate_ppm ) AddPropertyName(calcium_ppm ) AddPropertyName(carbonate_ppm ) AddPropertyName(chloride_ppm ) -AddPropertyName(flouride_ppm ) +AddPropertyName(fluoride_ppm ) AddPropertyName(iron_ppm ) AddPropertyName(magnesium_ppm ) AddPropertyName(mashRo_pct ) @@ -195,7 +195,7 @@ class Water : public OutlineableNamedEntity, Q_PROPERTY(std::optional iron_ppm READ iron_ppm WRITE setIron_ppm ) Q_PROPERTY(std::optional nitrate_ppm READ nitrate_ppm WRITE setNitrate_ppm ) Q_PROPERTY(std::optional nitrite_ppm READ nitrite_ppm WRITE setNitrite_ppm ) - Q_PROPERTY(std::optional flouride_ppm READ flouride_ppm WRITE setFlouride_ppm ) + Q_PROPERTY(std::optional fluoride_ppm READ fluoride_ppm WRITE setFluoride_ppm ) //============================================ "GETTER" MEMBER FUNCTIONS ============================================ /// double amount () const; @@ -219,7 +219,7 @@ class Water : public OutlineableNamedEntity, std::optional iron_ppm () const; std::optional nitrate_ppm () const; std::optional nitrite_ppm () const; - std::optional flouride_ppm () const; + std::optional fluoride_ppm () const; double ppm(Water::Ion const ion) const; @@ -246,7 +246,7 @@ class Water : public OutlineableNamedEntity, void setIron_ppm (std::optional const val); void setNitrate_ppm (std::optional const val); void setNitrite_ppm (std::optional const val); - void setFlouride_ppm (std::optional const val); + void setFluoride_ppm (std::optional const val); /// virtual Recipe * getOwningRecipe() const; @@ -277,7 +277,7 @@ class Water : public OutlineableNamedEntity, std::optional m_iron_ppm ; std::optional m_nitrate_ppm ; std::optional m_nitrite_ppm ; - std::optional m_flouride_ppm ; + std::optional m_fluoride_ppm ; }; BT_DECLARE_METATYPES(Water) diff --git a/src/model/Yeast.cpp b/src/model/Yeast.cpp index 712369de..b9aa7bc0 100644 --- a/src/model/Yeast.cpp +++ b/src/model/Yeast.cpp @@ -169,6 +169,8 @@ TypeLookup const Yeast::typeLookup { PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Yeast::killerProducingK28Toxin , Yeast::m_killerProducingK28Toxin , NonPhysicalQuantity::Bool ), PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Yeast::killerProducingKlusToxin , Yeast::m_killerProducingKlusToxin , NonPhysicalQuantity::Bool ), PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Yeast::killerNeutral , Yeast::m_killerNeutral , NonPhysicalQuantity::Bool ), + // Legacy property for BeerXML + PROPERTY_TYPE_LOOKUP_ENTRY_NO_MV(PropertyNames::Yeast::attenuationTypical_pct, Yeast::attenuationTypical_pct , NonPhysicalQuantity::Percentage ), }, // Parent classes lookup {&Ingredient::typeLookup, @@ -202,6 +204,8 @@ Yeast::Yeast(QString name) : m_killerProducingK28Toxin {std::nullopt}, m_killerProducingKlusToxin {std::nullopt}, m_killerNeutral {std::nullopt} { + + CONSTRUCTOR_END return; } @@ -211,30 +215,32 @@ Yeast::Yeast(NamedParameterBundle const & namedParameterBundle) : SET_REGULAR_FROM_NPB (m_form , namedParameterBundle, PropertyNames::Yeast::form ), SET_REGULAR_FROM_NPB (m_laboratory , namedParameterBundle, PropertyNames::Yeast::laboratory ), SET_REGULAR_FROM_NPB (m_productId , namedParameterBundle, PropertyNames::Yeast::productId ), - SET_REGULAR_FROM_NPB (m_minTemperature_c , namedParameterBundle, PropertyNames::Yeast::minTemperature_c ), - SET_REGULAR_FROM_NPB (m_maxTemperature_c , namedParameterBundle, PropertyNames::Yeast::maxTemperature_c ), + SET_REGULAR_FROM_NPB (m_minTemperature_c , namedParameterBundle, PropertyNames::Yeast::minTemperature_c , std::nullopt), + SET_REGULAR_FROM_NPB (m_maxTemperature_c , namedParameterBundle, PropertyNames::Yeast::maxTemperature_c , std::nullopt), SET_OPT_ENUM_FROM_NPB(m_flocculation, Yeast::Flocculation, namedParameterBundle, PropertyNames::Yeast::flocculation ), SET_REGULAR_FROM_NPB (m_notes , namedParameterBundle, PropertyNames::Yeast::notes ), SET_REGULAR_FROM_NPB (m_bestFor , namedParameterBundle, PropertyNames::Yeast::bestFor ), - SET_REGULAR_FROM_NPB (m_maxReuse , namedParameterBundle, PropertyNames::Yeast::maxReuse ), + SET_REGULAR_FROM_NPB (m_maxReuse , namedParameterBundle, PropertyNames::Yeast::maxReuse , std::nullopt), // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - SET_REGULAR_FROM_NPB (m_alcoholTolerance_pct , namedParameterBundle, PropertyNames::Yeast::alcoholTolerance_pct ), - SET_REGULAR_FROM_NPB (m_attenuationMin_pct , namedParameterBundle, PropertyNames::Yeast::attenuationMin_pct ), - SET_REGULAR_FROM_NPB (m_attenuationMax_pct , namedParameterBundle, PropertyNames::Yeast::attenuationMax_pct ), - SET_REGULAR_FROM_NPB (m_phenolicOffFlavorPositive , namedParameterBundle, PropertyNames::Yeast::phenolicOffFlavorPositive), - SET_REGULAR_FROM_NPB (m_glucoamylasePositive , namedParameterBundle, PropertyNames::Yeast::glucoamylasePositive ), - SET_REGULAR_FROM_NPB (m_killerProducingK1Toxin , namedParameterBundle, PropertyNames::Yeast::killerProducingK1Toxin ), - SET_REGULAR_FROM_NPB (m_killerProducingK2Toxin , namedParameterBundle, PropertyNames::Yeast::killerProducingK2Toxin ), - SET_REGULAR_FROM_NPB (m_killerProducingK28Toxin , namedParameterBundle, PropertyNames::Yeast::killerProducingK28Toxin ), - SET_REGULAR_FROM_NPB (m_killerProducingKlusToxin , namedParameterBundle, PropertyNames::Yeast::killerProducingKlusToxin ), - SET_REGULAR_FROM_NPB (m_killerNeutral , namedParameterBundle, PropertyNames::Yeast::killerNeutral ) { + SET_REGULAR_FROM_NPB (m_alcoholTolerance_pct , namedParameterBundle, PropertyNames::Yeast::alcoholTolerance_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_attenuationMin_pct , namedParameterBundle, PropertyNames::Yeast::attenuationMin_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_attenuationMax_pct , namedParameterBundle, PropertyNames::Yeast::attenuationMax_pct , std::nullopt), + SET_REGULAR_FROM_NPB (m_phenolicOffFlavorPositive , namedParameterBundle, PropertyNames::Yeast::phenolicOffFlavorPositive, std::nullopt), + SET_REGULAR_FROM_NPB (m_glucoamylasePositive , namedParameterBundle, PropertyNames::Yeast::glucoamylasePositive , std::nullopt), + SET_REGULAR_FROM_NPB (m_killerProducingK1Toxin , namedParameterBundle, PropertyNames::Yeast::killerProducingK1Toxin , std::nullopt), + SET_REGULAR_FROM_NPB (m_killerProducingK2Toxin , namedParameterBundle, PropertyNames::Yeast::killerProducingK2Toxin , std::nullopt), + SET_REGULAR_FROM_NPB (m_killerProducingK28Toxin , namedParameterBundle, PropertyNames::Yeast::killerProducingK28Toxin , std::nullopt), + SET_REGULAR_FROM_NPB (m_killerProducingKlusToxin , namedParameterBundle, PropertyNames::Yeast::killerProducingKlusToxin , std::nullopt), + SET_REGULAR_FROM_NPB (m_killerNeutral , namedParameterBundle, PropertyNames::Yeast::killerNeutral , std::nullopt) { // If we're being constructed from a BeerXML file, then we might only have typical attenuation rather than min and // max. Best we can do in that scenario is set min and max to the supplied value. if (namedParameterBundle.contains(PropertyNames::Yeast::attenuationTypical_pct)) { double attenuationTypical_pct{namedParameterBundle.val(PropertyNames::Yeast::attenuationTypical_pct)}; - if (!this->m_attenuationMin_pct) { this->m_attenuationMin_pct = attenuationTypical_pct; } - if (!this->m_attenuationMax_pct) { this->m_attenuationMax_pct = attenuationTypical_pct; } + if (!this->m_attenuationMin_pct) { ASSIGN_REGULAR_FROM_NPB(m_attenuationMin_pct, namedParameterBundle, PropertyNames::Yeast::attenuationTypical_pct); } + if (!this->m_attenuationMax_pct) { ASSIGN_REGULAR_FROM_NPB(m_attenuationMax_pct, namedParameterBundle, PropertyNames::Yeast::attenuationTypical_pct); } } + + CONSTRUCTOR_END return; } @@ -260,6 +266,8 @@ Yeast::Yeast(Yeast const & other) : m_killerProducingK28Toxin {other.m_killerProducingK28Toxin }, m_killerProducingKlusToxin {other.m_killerProducingKlusToxin }, m_killerNeutral {other.m_killerNeutral } { + + CONSTRUCTOR_END return; } diff --git a/src/serialization/ImportExport.cpp b/src/serialization/ImportExport.cpp index 4883dc78..1c0bcfbf 100644 --- a/src/serialization/ImportExport.cpp +++ b/src/serialization/ImportExport.cpp @@ -81,11 +81,30 @@ namespace { Q_ASSERT(importOrExport == ImportOrExport::EXPORT); fileChooser.setAcceptMode(QFileDialog::AcceptSave); fileChooser.setFileMode(QFileDialog::AnyFile); - // If the user doesn't specify a suffix - fileChooser.setDefaultSuffix(QString("xml")); - // TBD what's the difference between filter and nameFilter? -// QString filterStr = "BeerXML files (*.xml)"; -// fileChooser.setNameFilter(filterStr); + // + // If the user doesn't specify a suffix, we choose one for them. By default it's "json" to match the first + // filter in the list. If the user changes the filter, then we get a signal and change the default suffix + // accordingly. + // + // I think in practice QFileDialog::filterSelected will always get sent when exec() is run below, so we don't + // need the call to setDefaultSuffix here as the one in the lambda below will suffice. But it doesn't hurt to + // have this initial one, so we'll leave it for now. + // + fileChooser.setDefaultSuffix(QString("json")); + fileChooser.connect( + &fileChooser, + &QFileDialog::filterSelected, + [&fileChooser](QString const & filter) { + // The Qt docs are a bit silent about what the parameter is for the QFileDialog::filterSelected signal. + // By adding a logging statement here, we discover that we get either "*.json" or "*.xml". So we just + // need to chop off the first two characters to get default suffix. + // + // In Qt 6, we can replace `mid` with `sliced` which is faster apparently. + qDebug() << Q_FUNC_INFO << "Export filter is:" << filter; + fileChooser.setDefaultSuffix(filter.mid(2)); + return; + } + ); } if (!fileChooser.exec() ) { @@ -93,8 +112,9 @@ namespace { return std::nullopt; } - qDebug() << Q_FUNC_INFO << "Selected " << fileChooser.selectedFiles().length() << " files"; - qDebug() << Q_FUNC_INFO << "Directory " << fileChooser.directory(); + qDebug() << + Q_FUNC_INFO << "Selected" << fileChooser.selectedFiles().length() << "file(s) (from directory" << + fileChooser.directory() << "):" << fileChooser.selectedFiles(); // Remember the directory for next time fileChooserDirectory = fileChooser.directory().canonicalPath(); diff --git a/src/serialization/SerializationRecord.cpp b/src/serialization/SerializationRecord.cpp index 438bafd8..d4e923dd 100644 --- a/src/serialization/SerializationRecord.cpp +++ b/src/serialization/SerializationRecord.cpp @@ -17,7 +17,7 @@ SerializationRecord::SerializationRecord() : - m_namedParameterBundle{NamedParameterBundle::NotStrict}, + m_namedParameterBundle{NamedParameterBundle::OperationMode::NotStrict}, m_namedEntity{nullptr}, m_includeInStats{true} { return; diff --git a/src/serialization/json/BeerJson.cpp b/src/serialization/json/BeerJson.cpp index 892de491..2e1af5ec 100644 --- a/src/serialization/json/BeerJson.cpp +++ b/src/serialization/json/BeerJson.cpp @@ -503,7 +503,8 @@ namespace { {JsonRecordDefinition::FieldType::MeasurementWithUnits, "iron" , PropertyNames::Water::iron_ppm , &BEER_JSON_MASS_FRACT_OR_CONC_UNIT_MAPPER}, {JsonRecordDefinition::FieldType::MeasurementWithUnits, "nitrate" , PropertyNames::Water::nitrate_ppm , &BEER_JSON_MASS_FRACT_OR_CONC_UNIT_MAPPER}, {JsonRecordDefinition::FieldType::MeasurementWithUnits, "nitrite" , PropertyNames::Water::nitrite_ppm , &BEER_JSON_MASS_FRACT_OR_CONC_UNIT_MAPPER}, - {JsonRecordDefinition::FieldType::MeasurementWithUnits, "flouride" , PropertyNames::Water::flouride_ppm , &BEER_JSON_MASS_FRACT_OR_CONC_UNIT_MAPPER}, + // BeerJSON attribute is flouride but should be fluoride -- see https://github.com/beerjson/beerjson/issues/214 + {JsonRecordDefinition::FieldType::MeasurementWithUnits, "flouride" , PropertyNames::Water::fluoride_ppm , &BEER_JSON_MASS_FRACT_OR_CONC_UNIT_MAPPER}, {JsonRecordDefinition::FieldType::MeasurementWithUnits, "sulfate" , PropertyNames::Water::sulfate_ppm , &BEER_JSON_MASS_FRACT_OR_CONC_UNIT_MAPPER}, {JsonRecordDefinition::FieldType::MeasurementWithUnits, "chloride" , PropertyNames::Water::chloride_ppm , &BEER_JSON_MASS_FRACT_OR_CONC_UNIT_MAPPER}, {JsonRecordDefinition::FieldType::MeasurementWithUnits, "sodium" , PropertyNames::Water::sodium_ppm , &BEER_JSON_MASS_FRACT_OR_CONC_UNIT_MAPPER}, diff --git a/src/serialization/json/JsonMeasureableUnitsMapping.cpp b/src/serialization/json/JsonMeasureableUnitsMapping.cpp index 76e38ac0..4ea3cfe2 100644 --- a/src/serialization/json/JsonMeasureableUnitsMapping.cpp +++ b/src/serialization/json/JsonMeasureableUnitsMapping.cpp @@ -97,3 +97,37 @@ Measurement::Unit const * JsonMeasureableUnitsMapping::findUnit(std::string_view Measurement::Unit const * JsonMeasureableUnitsMapping::defaultUnit() const { return this->nameToUnit.cbegin()->second; } + +template +S & JsonMeasureableUnitsMapping::writeToStream(S & stream) const { + stream << + "Unit Field:" << this->unitField << "; Value Field:" << this->valueField << "; Map:\n"; + for (auto const & ii : this->nameToUnit) { + stream << QString::fromStdString(std::string{ii.first}) << "->" << *ii.second << "\n"; + } + return stream; +} + +template +S & operator<<(S & stream, JsonMeasureableUnitsMapping const & jmum) { + return jmum.writeToStream(stream); +} + +template +S & operator<<(S & stream, JsonMeasureableUnitsMapping const * jmum) { + if (jmum) { + stream << *jmum; + } else { + stream << "NULL"; + } + return stream; +} + +// +// Instantiate the above template functions for the types that are going to use them +// (This is all just a trick to allow the template definition to be here in the .cpp file and not in the header.) +// +template QDebug & operator<<(QDebug & stream, JsonMeasureableUnitsMapping const & jmum); +template QTextStream & operator<<(QTextStream & stream, JsonMeasureableUnitsMapping const & jmum); +template QDebug & operator<<(QDebug & stream, JsonMeasureableUnitsMapping const * jmum); +template QTextStream & operator<<(QTextStream & stream, JsonMeasureableUnitsMapping const * jmum); diff --git a/src/serialization/json/JsonMeasureableUnitsMapping.h b/src/serialization/json/JsonMeasureableUnitsMapping.h index 8463af9b..441c767c 100644 --- a/src/serialization/json/JsonMeasureableUnitsMapping.h +++ b/src/serialization/json/JsonMeasureableUnitsMapping.h @@ -89,6 +89,21 @@ class JsonMeasureableUnitsMapping { * unit matches our canonical metric ones.) */ Measurement::Unit const * defaultUnit() const; + + template S & writeToStream(S & stream) const; }; +/** + * \brief Convenience function to allow output of \c JsonMeasureableUnitsMapping to \c QDebug or \c QTextStream stream + */ +template +S & operator<<(S & stream, JsonMeasureableUnitsMapping const & jmum); + +/** + * \brief Convenience function to allow output of \c JsonMeasureableUnitsMapping to \c QDebug or \c QTextStream stream + */ +template +S & operator<<(S & stream, JsonMeasureableUnitsMapping const * jmum); + + #endif diff --git a/src/serialization/json/JsonRecord.cpp b/src/serialization/json/JsonRecord.cpp index 9f941c79..83541a21 100644 --- a/src/serialization/json/JsonRecord.cpp +++ b/src/serialization/json/JsonRecord.cpp @@ -1081,6 +1081,7 @@ void JsonRecord::insertValue(JsonRecordDefinition::FieldDefinition const & field JsonMeasureableUnitsMapping const * const unitsMapping = std::get(fieldDefinition.valueDecoder); Q_ASSERT(unitsMapping); + qDebug() << Q_FUNC_INFO << *unitsMapping; Measurement::Unit const * const aUnit = unitsMapping->defaultUnit(); Measurement::Unit const & canonicalUnit = aUnit->getCanonical(); qDebug() << Q_FUNC_INFO << canonicalUnit; diff --git a/src/serialization/json/JsonXPath.cpp b/src/serialization/json/JsonXPath.cpp index b9e48156..b59ff6e4 100644 --- a/src/serialization/json/JsonXPath.cpp +++ b/src/serialization/json/JsonXPath.cpp @@ -496,7 +496,7 @@ std::string JsonXPath::makePointerToLeaf(boost::json::value ** valuePointer) con return std::get(priorNode); } -std::string_view JsonXPath::asKey() const { +std::string JsonXPath::asKey() const { // It's a coding error if there is more than one path part Q_ASSERT(this->m_pathParts.size() == 1); @@ -505,9 +505,8 @@ std::string_view JsonXPath::asKey() const { // We need to get the first path part (m_pathParts[0]) and then strip the beginning '/' character from it // (.substr(1)) before we pass it to the string_view constructor. (Maybe there is a slick way to skip over the '/' - // in the string_view constructor, but I didn't yet find it.) - std::string_view key{std::get(this->m_pathParts[0]).substr(1)}; - return key; + // in the std::string constructor, but I didn't yet find it.) + return std::get(this->m_pathParts[0]).substr(1); } char const * JsonXPath::asXPath_c_str() const { diff --git a/src/serialization/json/JsonXPath.h b/src/serialization/json/JsonXPath.h index ee3311ab..56452a9e 100644 --- a/src/serialization/json/JsonXPath.h +++ b/src/serialization/json/JsonXPath.h @@ -122,7 +122,7 @@ class JsonXPath { * \brief For a trivial path, return it without the leading slash (as a \c string_view because that's what we're * going to pass to Boost.JSON). Caller's responsibility to ensure this is indeed a trivial path. */ - std::string_view asKey() const; + std::string asKey() const; /** * \brief This returns a C-style string as that's most universally usable for logging diff --git a/src/serialization/xml/BeerXml.cpp b/src/serialization/xml/BeerXml.cpp index c16a93a1..0889a2e6 100644 --- a/src/serialization/xml/BeerXml.cpp +++ b/src/serialization/xml/BeerXml.cpp @@ -456,7 +456,7 @@ namespace { {XmlRecordDefinition::FieldType::Double , "IRON_PPM" , PropertyNames::Water::iron_ppm }, {XmlRecordDefinition::FieldType::Double , "NITRATE_PPM" , PropertyNames::Water::nitrate_ppm }, {XmlRecordDefinition::FieldType::Double , "NITRITE_PPM" , PropertyNames::Water::nitrite_ppm }, - {XmlRecordDefinition::FieldType::Double , "FLOURIDE_PPM" , PropertyNames::Water::flouride_ppm }, + {XmlRecordDefinition::FieldType::Double , "FLUORIDE_PPM" , PropertyNames::Water::fluoride_ppm }, }; std::initializer_list const BeerXml_WaterType_ExclBase { // Type XPath Q_PROPERTY Value Decoder diff --git a/src/serialization/xml/XmlNamedEntityRecord.h b/src/serialization/xml/XmlNamedEntityRecord.h index 790845f0..e3e28468 100644 --- a/src/serialization/xml/XmlNamedEntityRecord.h +++ b/src/serialization/xml/XmlNamedEntityRecord.h @@ -57,8 +57,9 @@ class XmlNamedEntityRecord : public XmlRecord { virtual void constructNamedEntity() { // It's a coding error if this function is called when we already have a NamedEntity Q_ASSERT(nullptr == this->m_namedEntity.get()); - qDebug() << - Q_FUNC_INFO << "Constructing" << NE::staticMetaObject.className() << "from" << this->m_namedParameterBundle; + // Normally keep this log statement commented out otherwise it generates too many lines in the log file +// qDebug() << +// Q_FUNC_INFO << "Constructing" << NE::staticMetaObject.className() << "from" << this->m_namedParameterBundle; this->m_namedEntity = std::make_shared(this->m_namedParameterBundle); } diff --git a/src/serialization/xml/XmlRecipeRecord.cpp b/src/serialization/xml/XmlRecipeRecord.cpp index 39a889f1..7066edd8 100644 --- a/src/serialization/xml/XmlRecipeRecord.cpp +++ b/src/serialization/xml/XmlRecipeRecord.cpp @@ -18,8 +18,10 @@ #include #include +#include "model/Boil.h" #include "model/Equipment.h" #include "model/Fermentable.h" +#include "model/Fermentation.h" #include "model/Hop.h" #include "model/Misc.h" #include "model/Style.h" @@ -211,6 +213,17 @@ XmlRecord::ProcessingResult XmlRecipeRecord::normaliseAndStoreInDb(std::shared_ptr containingEntity, QTextStream & userMessage, ImportRecordCount & stats) { + auto recipe = static_cast(this->m_namedEntity.get()); + + // + // We need to turn the Recipe's calculations off temporarily. They are not meaningful until we have stored all the + // child objects such as ingredient additions, mash, boil etc, and trying to run them before all these things are + // set causes crashes. + // + // TBD: Maybe one day we should do this with RAII. + // + recipe->setCalcsEnabled(false); + // This call to the base class function will store the Recipe and all the objects it contains, as well as link the // Recipe to its Style and Equipment. XmlRecord::ProcessingResult result = XmlRecord::normaliseAndStoreInDb(containingEntity, userMessage, stats); @@ -220,6 +233,8 @@ XmlRecord::ProcessingResult XmlRecipeRecord::normaliseAndStoreInDb(std::shared_p return result; } + qDebug() << Q_FUNC_INFO << "Final tidy up for Recipe #" << recipe->key() << "(" << recipe->name() << ")"; + // // We now need to tie some other things together // @@ -236,6 +251,84 @@ XmlRecord::ProcessingResult XmlRecipeRecord::normaliseAndStoreInDb(std::shared_p // Recipe class does not (currently) have an interface for adding BrewNotes. It suffices to tell each BrewNote what // its Recipe is, something we achieve via template specialisation of XmlNamedEntityRecord::setContainingEntity + + // + // We have to go through and handle a few things that it is hard to do generically. Eg, in BeerXML, there is no + // separate Boil object, but various parameters such as BOIL_SIZE and BOIL_TIME exist directly on Recipe and we use + // property paths (eg {PropertyNames::Recipe::boil, PropertyNames::Boil::boilTime_mins}) to map them to our internal + // structure. + // + // At this point, the Recipe is stored in the database (so it has a valid ID), and we still have all the parameters + // read in from the RECIPE record (which NamedParameterBundle will have grouped into sub-bundles for us), so it's + // straightforward finish things off. + // + if (this->m_namedParameterBundle.containsBundle(PropertyNames::Recipe::boil)) { + // It's a coding error if the recipe already has a boil + Q_ASSERT(!recipe->boil()); + + auto boilBundle = this->m_namedParameterBundle.getBundle(PropertyNames::Recipe::boil); + boilBundle.insertIfNotPresent(PropertyNames::NamedEntity::name, QObject::tr("Boil for %1").arg(recipe->name())); + boilBundle.insertIfNotPresent(PropertyNames::Boil::description, QObject::tr("Automatically created by BeerXML import")); + auto boil = std::make_shared(boilBundle); + // This call will also ensure the boil gets saved in the DB + recipe->setBoil(boil); + + qDebug() << Q_FUNC_INFO << "Created Boil #" << boil->key() << "on Recipe #" << recipe->key(); + } + if (this->m_namedParameterBundle.containsBundle(PropertyNames::Recipe::fermentation)) { + // It's a coding error if the recipe already has a fermentation + Q_ASSERT(!recipe->fermentation()); + + auto fermentationBundle = this->m_namedParameterBundle.getBundle(PropertyNames::Recipe::fermentation); + fermentationBundle.insertIfNotPresent(PropertyNames::NamedEntity::name, + QObject::tr("Fermentation for %1").arg(recipe->name())); + fermentationBundle.insertIfNotPresent(PropertyNames::Fermentation::description, + QObject::tr("Automatically created by BeerXML import")); + auto fermentation = std::make_shared(fermentationBundle); + // This call will also ensure the fermentation gets saved in the DB + recipe->setFermentation(fermentation); + + qDebug() << Q_FUNC_INFO << "Created Fermentation #" << fermentation->key() << "on Recipe #" << recipe->key(); + + // + // Now we handle RECIPE > PRIMARY_AGE / PRIMARY_TEMP / SECONDARY_AGE / SECONDARY_TEMP / TERTIARY_AGE / + // TERTIARY_TEMP. + // + // To keep things simple we make the (hopefully quite reasonable) assumptions that we should ignore secondary if + // primary is not present, and ignore tertiary if secondary is not present. + // + // The call to fermentation->addStep automatically handles saving the step in the DB + // + if (fermentationBundle.containsBundle(PropertyNames::Fermentation::primary)) { + auto primaryBundle {fermentationBundle.getBundle(PropertyNames::Fermentation::primary)}; + qDebug() << Q_FUNC_INFO << primaryBundle; + primaryBundle.insertIfNotPresent(PropertyNames::NamedEntity::name, + QObject::tr("Primary Fermentation Step for %1").arg(recipe->name())); + primaryBundle.insertIfNotPresent(PropertyNames::Step::description, + QObject::tr("Automatically created by BeerXML import")); + fermentation->addStep(std::make_shared(primaryBundle)); + if (fermentationBundle.containsBundle(PropertyNames::Fermentation::secondary)) { + auto secondaryBundle {fermentationBundle.getBundle(PropertyNames::Fermentation::secondary)}; + secondaryBundle.insertIfNotPresent(PropertyNames::NamedEntity::name, + QObject::tr("Secondary Fermentation Step for %1").arg(recipe->name())); + secondaryBundle.insertIfNotPresent(PropertyNames::Step::description, + QObject::tr("Automatically created by BeerXML import")); + fermentation->addStep(std::make_shared(secondaryBundle)); + if (fermentationBundle.containsBundle(PropertyNames::Fermentation::tertiary)) { + auto tertiaryBundle {fermentationBundle.getBundle(PropertyNames::Fermentation::tertiary)}; + tertiaryBundle.insertIfNotPresent(PropertyNames::NamedEntity::name, + QObject::tr("Tertiary Fermentation Step for %1").arg(recipe->name())); + tertiaryBundle.insertIfNotPresent(PropertyNames::Step::description, + QObject::tr("Automatically created by BeerXML import")); + fermentation->addStep(std::make_shared(tertiaryBundle)); + } + } + } + + + } + + static_cast(this->m_namedEntity.get())->setCalcsEnabled(true); return XmlRecord::ProcessingResult::Succeeded; } diff --git a/src/serialization/xml/XmlRecord.cpp b/src/serialization/xml/XmlRecord.cpp index eaca7ed8..84f09f00 100644 --- a/src/serialization/xml/XmlRecord.cpp +++ b/src/serialization/xml/XmlRecord.cpp @@ -77,8 +77,6 @@ XmlRecord::~XmlRecord() = default; bool XmlRecord::load(xalanc::DOMSupport & domSupport, xalanc::XalanNode * rootNodeOfRecord, QTextStream & userMessage) { - qDebug() << Q_FUNC_INFO; - xalanc::XPathEvaluator xPathEvaluator; // // Loop through all the fields that we know/care about. Anything else is intentionally ignored. (We won't know @@ -140,7 +138,8 @@ bool XmlRecord::load(xalanc::DOMSupport & domSupport, } } auto numChildNodes = nodesForCurrentXPath.size(); - qDebug() << Q_FUNC_INFO << "Found" << numChildNodes << "node(s) for " << fieldDefinition.xPath; + // Normally keep this log statement commented out otherwise it generates too many lines in the log file +// qDebug() << Q_FUNC_INFO << "Found" << numChildNodes << "node(s) for " << fieldDefinition.xPath; if (XmlRecordDefinition::FieldType::Record == fieldDefinition.type || XmlRecordDefinition::FieldType::ListOfRecords == fieldDefinition.type) { // @@ -178,12 +177,14 @@ bool XmlRecord::load(xalanc::DOMSupport & domSupport, XQString fieldName{fieldContainerNode->getNodeName()}; xalanc::XalanNodeList const * fieldContents = fieldContainerNode->getChildNodes(); int numChildrenOfContainerNode = fieldContents->getLength(); + // Normally keep this log statement commented out otherwise it generates too many lines in the log file qDebug() << Q_FUNC_INFO << "Node " << fieldDefinition.xPath << "(" << fieldName << ":" << XALAN_NODE_TYPES[fieldContainerNode->getNodeType()] << ") has " << numChildrenOfContainerNode << " children"; if (0 == numChildrenOfContainerNode) { - qDebug() << Q_FUNC_INFO << "Empty!"; + // Normally keep this log statement commented out otherwise it generates too many lines in the log file +// qDebug() << Q_FUNC_INFO << "Empty!"; } else { { // @@ -198,7 +199,8 @@ bool XmlRecord::load(xalanc::DOMSupport & domSupport, } xalanc::XalanNode * valueNode = fieldContents->item(0); XQString value(valueNode->getNodeValue()); - qDebug() << Q_FUNC_INFO << "Value " << value; + // Normally keep this log statement commented out otherwise it generates too many lines in the log file +// qDebug() << Q_FUNC_INFO << "Value " << value; bool parsedValueOk = false; QVariant parsedValue; @@ -234,6 +236,9 @@ bool XmlRecord::load(xalanc::DOMSupport & domSupport, fieldDefinition.propertyPath.getTypeInfo(*this->m_recordDefinition.m_typeLookup).isOptional() }; + // Normally keep this log statement commented out otherwise it generates too many lines in the log file + qDebug() << Q_FUNC_INFO << "Value " << value << "; optional=" << (propertyIsOptional ? "true" : "false"); + switch (fieldDefinition.type) { case XmlRecordDefinition::FieldType::Bool: @@ -439,10 +444,11 @@ bool XmlRecord::load(xalanc::DOMSupport & domSupport, // out), we can't carry on to normal processing below. So jump straight to processing the next // node in the loop (via continue). // - qDebug() << - Q_FUNC_INFO << "Skipping " << this->m_recordDefinition.m_namedEntityClassName << " node " << - fieldDefinition.xPath << "=" << value << "(" << fieldDefinition.propertyPath.asXPath() << - ") as not useful"; + // Normally keep this log statement commented out otherwise it generates too many lines in the log file +// qDebug() << +// Q_FUNC_INFO << "Skipping " << this->m_recordDefinition.m_namedEntityClassName << " node " << +// fieldDefinition.xPath << "=" << value << "(" << fieldDefinition.propertyPath.asXPath() << +// ") as not useful"; continue; // NB: _NOT_break here. We want to jump straight to the next run through the for loop. // By default we assume it's a string @@ -464,6 +470,11 @@ bool XmlRecord::load(xalanc::DOMSupport & domSupport, break; } + // Normally keep this log statement commented out otherwise it generates too many lines in the log file + qDebug() << + Q_FUNC_INFO << "parsedValue:" << parsedValue << "; parsedValueOk:" << parsedValueOk << + "; fieldDefinition.propertyPath:" << fieldDefinition.propertyPath; + // // What we do if we couldn't parse the value depends. If it was a value that we didn't need to set on // the supplied Hop/Yeast/Recipe/Etc object, then we can just ignore the problem and carry on processing. @@ -472,8 +483,8 @@ bool XmlRecord::load(xalanc::DOMSupport & domSupport, // if (!parsedValueOk && !fieldDefinition.propertyPath.isNull()) { userMessage << - "Could not parse " << this->m_recordDefinition.m_namedEntityClassName << " node " << fieldDefinition.xPath << "=" << - value << " into " << fieldDefinition.propertyPath.asXPath(); + "Could not parse " << this->m_recordDefinition.m_namedEntityClassName << " node " << + fieldDefinition.xPath << "=" << value << " into " << fieldDefinition.propertyPath.asXPath(); return false; } @@ -494,7 +505,17 @@ bool XmlRecord::load(xalanc::DOMSupport & domSupport, // For everything but the root record, we now construct a suitable object (Hop, Recipe, etc) from the // NamedParameterBundle (which will be empty for the root record). // + // Note that this will not construct sub-objects for non-trivial property paths in m_namedParameterBundle (eg + // {PropertyNames::Recipe::boil, PropertyNames::Boil::boilTime_mins}). This is handled in subclass implementation of + // normaliseAndStoreInDb, eg XmlRecipeRecord::normaliseAndStoreInDb, (and is part of why we retain + // m_namedParameterBundle). + // if (!this->m_namedParameterBundle.isEmpty()) { + // Normally keep this log statement commented out otherwise it generates too many lines in the log file + qDebug().noquote() << + Q_FUNC_INFO << "Constructing " << this->m_recordDefinition.m_namedEntityClassName << " from " << + this->m_namedParameterBundle; + this->constructNamedEntity(); } @@ -771,8 +792,8 @@ bool XmlRecord::normaliseAndStoreChildRecordsInDb(QTextStream & userMessage, auto constructorWrapper = childRecordDefinition.xmlRecordConstructorWrapper; this->m_childRecordSets.push_back(XmlRecord::ChildRecordSet{&parentFieldDefinition, {}}); qDebug() << - Q_FUNC_INFO << "this->m_childRecordSets for" << this->m_recordDefinition << "has" << - this->m_childRecordSets.size() << "entries"; + Q_FUNC_INFO << "childRecordDefinition" << childRecordDefinition << ". m_childRecordSets for" << + this->m_recordDefinition << "has" << this->m_childRecordSets.size() << "entries"; XmlRecord::ChildRecordSet & childRecordSet = this->m_childRecordSets.back(); for (xalanc::XalanNode * childRecordNode : nodesForCurrentXPath) { // @@ -792,7 +813,8 @@ bool XmlRecord::normaliseAndStoreChildRecordsInDb(QTextStream & userMessage, // Requesting the HOPS/HOP subpath of RECIPE will not return FOO or BAR // XQString childRecordName{childRecordNode->getNodeName()}; - qDebug() << Q_FUNC_INFO << childRecordName; + // Normally keep this log statement commented out otherwise it generates too many lines in the log file +// qDebug() << Q_FUNC_INFO << childRecordName; std::unique_ptr childRecord{ constructorWrapper(this->m_coding, childRecordDefinition) diff --git a/src/tableModels/ItemDelegate.h b/src/tableModels/ItemDelegate.h index fefc9bee..46b8ed4d 100644 --- a/src/tableModels/ItemDelegate.h +++ b/src/tableModels/ItemDelegate.h @@ -263,13 +263,13 @@ class ItemDelegate { if (fieldType == NonPhysicalQuantity::Enum) { BtComboBox * comboBox = qobject_cast(editor); - model->setData(index, comboBox->getValue(typeInfo), Qt::EditRole); + model->setData(index, comboBox->getAsVariant(), Qt::EditRole); return; } if (fieldType == NonPhysicalQuantity::Bool) { BtBoolComboBox * boolComboBox = qobject_cast(editor); - model->setData(index, boolComboBox->getValue(typeInfo), Qt::EditRole); + model->setData(index, boolComboBox->getAsVariant(), Qt::EditRole); return; } @@ -295,7 +295,7 @@ class ItemDelegate { // Selector for editable amount that can be more than one physical quantity. (See comment above in // getEditWidget() for more details.) BtComboBox * comboBox = qobject_cast(editor); - model->setData(index, comboBox->getValue(typeInfo), Qt::EditRole); + model->setData(index, comboBox->getAsVariant(), Qt::EditRole); return; } diff --git a/src/trees/TreeModel.h b/src/trees/TreeModel.h index 1b9d326c..c6dba170 100644 --- a/src/trees/TreeModel.h +++ b/src/trees/TreeModel.h @@ -35,19 +35,10 @@ #include "trees/TreeNode.h" // Forward declarations -class BrewNote; -class Folder; class BtStringConst; -class TreeView; -class Equipment; -class Fermentable; -class Hop; -class Misc; class NamedEntity; class Recipe; -class Style; -class Water; -class Yeast; +class TreeView; /*! * \class TreeModel @@ -74,6 +65,7 @@ class TreeModel : public QAbstractItemModel { Style = (1 << 7), Folder = (1 << 8), Water = (1 << 9), + Mash = (1 << 10), }; Q_DECLARE_FLAGS(TypeMasks, TypeMask) diff --git a/src/trees/TreeView.cpp b/src/trees/TreeView.cpp index fc32ac2f..2236f064 100644 --- a/src/trees/TreeView.cpp +++ b/src/trees/TreeView.cpp @@ -325,7 +325,7 @@ void TreeView::newNamedEntity() { if (m_type.testFlag(TreeModel::TypeMask::Misc )) { qobject_cast(m_editor)->newEditItem(folder); return; } if (m_type.testFlag(TreeModel::TypeMask::Style )) { qobject_cast(m_editor)->newEditItem(folder); return; } if (m_type.testFlag(TreeModel::TypeMask::Yeast )) { qobject_cast(m_editor)->newEditItem(folder); return; } - if (m_type.testFlag(TreeModel::TypeMask::Water )) { qobject_cast(m_editor)->newWater (folder); return; } + if (m_type.testFlag(TreeModel::TypeMask::Water )) { qobject_cast(m_editor)->newEditItem(folder); return; } qWarning() << Q_FUNC_INFO << "Unrecognized mask" << m_type; return; @@ -743,42 +743,12 @@ void TreeView::versionedRecipe(Recipe * descendant) { emit recipeSpawn(descendant); } -// Bad form likely - -RecipeTreeView::RecipeTreeView(QWidget * parent) : TreeView(parent, TreeModel::TypeMask::Recipe) { - connect(m_model, &TreeModel::recipeSpawn, this, &TreeView::versionedRecipe); -} - -EquipmentTreeView::EquipmentTreeView(QWidget * parent) - : TreeView(parent, TreeModel::TypeMask::Equipment) { -} - -// Icky ick ikcy -FermentableTreeView::FermentableTreeView(QWidget * parent) - : TreeView(parent, TreeModel::TypeMask::Fermentable) { -} - -// More Ick -HopTreeView::HopTreeView(QWidget * parent) - : TreeView(parent, TreeModel::TypeMask::Hop) { -} - -// Ick some more -MiscTreeView::MiscTreeView(QWidget * parent) - : TreeView(parent, TreeModel::TypeMask::Misc) { -} - -// Will this ick never end? -YeastTreeView::YeastTreeView(QWidget * parent) - : TreeView(parent, TreeModel::TypeMask::Yeast) { -} - -// Nope. Apparently not, cause I keep adding more -StyleTreeView::StyleTreeView(QWidget * parent) - : TreeView(parent, TreeModel::TypeMask::Style) { -} - -// Cthulhu take me -WaterTreeView::WaterTreeView(QWidget * parent) - : TreeView(parent, TreeModel::TypeMask::Water) { -} +TREE_VIEW_COMMON_CODE(Recipe) +TREE_VIEW_COMMON_CODE(Equipment) +TREE_VIEW_COMMON_CODE(Fermentable) +TREE_VIEW_COMMON_CODE(Hop) +TREE_VIEW_COMMON_CODE(Misc) +TREE_VIEW_COMMON_CODE(Yeast) +TREE_VIEW_COMMON_CODE(Style) +TREE_VIEW_COMMON_CODE(Water) +TREE_VIEW_COMMON_CODE(Mash) diff --git a/src/trees/TreeView.h b/src/trees/TreeView.h index 5be633bf..2a59d7c2 100644 --- a/src/trees/TreeView.h +++ b/src/trees/TreeView.h @@ -26,6 +26,7 @@ #include "trees/TreeNode.h" #include "trees/TreeFilterProxyModel.h" +#include "utils/CuriouslyRecurringTemplateBase.h" // Forward declarations. class TreeModel; @@ -33,6 +34,7 @@ class Recipe; class Equipment; class Fermentable; class Hop; +class Mash; class Misc; class Yeast; class BrewNote; @@ -138,10 +140,10 @@ class TreeView : public QTreeView { public slots: void newNamedEntity(); + void versionedRecipe(Recipe * descendant); private slots: void expandFolder(TreeModel::TypeMasks kindaThing, QModelIndex fIdx); - void versionedRecipe(Recipe * descendant); void showAncestors(); void hideAncestors(); @@ -176,98 +178,62 @@ private slots: QString verifyCopy(QString tag, QString name, bool * abort); QMimeData * mimeData(QModelIndexList indexes); }; - -//! -// \class RecipeTreeView -// \brief subclasses TreeView to only show recipes. -class RecipeTreeView : public TreeView { - Q_OBJECT -public: - //! \brief Constructs the tree view, sets up the filter proxy and sets a - // few options on the tree that can only be set after the model - RecipeTreeView(QWidget * parent = nullptr); - -}; - -//! -// \class EquipmentTreeView -// \brief subclasses TreeView to only show equipment. -class EquipmentTreeView : public TreeView { - Q_OBJECT -public: - //! \brief Constructs the tree view, sets up the filter proxy and sets a - // few options on the tree that can only be set after the model - EquipmentTreeView(QWidget * parent = nullptr); -}; - -//! -// \class FermentableTreeView -// \brief subclasses TreeView to only show fermentables. -class FermentableTreeView : public TreeView { - Q_OBJECT -public: - //! \brief Constructs the tree view, sets up the filter proxy and sets a - // few options on the tree that can only be set after the model - FermentableTreeView(QWidget * parent = nullptr); - -}; - -//! -// \class HopTreeView -// \brief subclasses TreeView to only show hops. -class HopTreeView : public TreeView { - Q_OBJECT -public: - //! \brief Constructs the tree view, sets up the filter proxy and sets a - // few options on the tree that can only be set after the model - HopTreeView(QWidget * parent = nullptr); - -}; - -//! -// \class MiscTreeView -// \brief subclasses TreeView to only show miscs. -class MiscTreeView : public TreeView { - Q_OBJECT +//====================================================================================================================== +template class TreeViewPhantom; +template +class TreeViewBase : public CuriouslyRecurringTemplateBase { public: - //! \brief Constructs the tree view, sets up the filter proxy and sets a - // few options on the tree that can only be set after the model - MiscTreeView(QWidget * parent = nullptr); -}; + TreeViewBase() { + return; + } + ~TreeViewBase() = default; -//! -// \class YeastTreeView -// \brief subclasses TreeView to only show yeasts. -class YeastTreeView : public TreeView { - Q_OBJECT -public: - //! \brief Constructs the tree view, sets up the filter proxy and sets a - // few options on the tree that can only be set after the model - YeastTreeView(QWidget * parent = nullptr); + void doConnections() requires std::same_as { + this->derived().connect(this->derived().m_model, &TreeModel::recipeSpawn, &this->derived(), &TreeView::versionedRecipe); + return; + } -}; + void doConnections() requires (!std::same_as) { + return; + } -//! -// \class StyleTreeView -// \brief subclasses TreeView to only show styles. -class StyleTreeView : public TreeView { - Q_OBJECT -public: - //! \brief Constructs the tree view, sets up the filter proxy and sets a - // few options on the tree that can only be set after the model - StyleTreeView(QWidget * parent = nullptr); }; +//====================================================================================================================== +#define TREE_VIEW_COMMON_DECL(NeName) \ +class NeName##TreeView : public TreeView, \ + public TreeViewBase { \ + Q_OBJECT \ + \ + /* This allows TreeViewBase to call protected and private members of Derived */ \ + friend class TreeViewBase; \ + \ + public: \ + /* Constructs the tree view, sets up the filter proxy and sets a */ \ + /* few options on the tree that can only be set after the model */ \ + NeName##TreeView(QWidget * parent = nullptr); \ + virtual ~NeName##TreeView(); \ +}; \ + +TREE_VIEW_COMMON_DECL(Recipe) +TREE_VIEW_COMMON_DECL(Equipment) +TREE_VIEW_COMMON_DECL(Fermentable) +TREE_VIEW_COMMON_DECL(Hop) +TREE_VIEW_COMMON_DECL(Misc) +TREE_VIEW_COMMON_DECL(Yeast) +TREE_VIEW_COMMON_DECL(Style) +TREE_VIEW_COMMON_DECL(Water) +TREE_VIEW_COMMON_DECL(Mash) + +#define TREE_VIEW_COMMON_CODE(NeName) \ + NeName##TreeView::NeName##TreeView(QWidget * parent) : \ + TreeView(parent, TreeModel::TypeMask::NeName), \ + TreeViewBase() { \ + this->doConnections(); \ + return; \ + } \ + \ + NeName##TreeView::~NeName##TreeView() = default; \ -//! -// \class WaterTreeView -// \brief subclasses TreeView to only show waters. -class WaterTreeView : public TreeView { - Q_OBJECT -public: - //! \brief Constructs the tree view, sets up the filter proxy and sets a - // few options on the tree that can only be set after the model - WaterTreeView(QWidget * parent = nullptr); -}; #endif diff --git a/src/utils/TypeLookup.cpp b/src/utils/TypeLookup.cpp index c3c8ff19..5281f0bb 100644 --- a/src/utils/TypeLookup.cpp +++ b/src/utils/TypeLookup.cpp @@ -56,6 +56,8 @@ TypeLookup::TypeLookup(char const * const } TypeInfo const * TypeLookup::typeInfoFor(BtStringConst const & propertyName) const { + // Normally keep this log statement commented out otherwise it generates too many lines in the log file +// qDebug() << Q_FUNC_INFO << this << "Searching for" << *propertyName; auto match = std::find_if( this->m_lookupMap.begin(), this->m_lookupMap.end(), diff --git a/src/utils/TypeLookup.h b/src/utils/TypeLookup.h index 1a09df5d..967faa8b 100644 --- a/src/utils/TypeLookup.h +++ b/src/utils/TypeLookup.h @@ -292,6 +292,21 @@ template struct MemberFunctionRetu template using MemberFunctionReturnType_t = typename MemberFunctionReturnType::type; //! @} +/** + * \brief Using the same trick as \c MemberFunctionReturnType_t, we can, with a bit of extra work, also get the type of + * the first parameter to a member function. + */ +//! @{ +template struct FirstTypeInPack { + using type = First; +}; +template struct MemberFunctionFirstParamType; +template struct MemberFunctionFirstParamType { + using type = typename FirstTypeInPack::type; +}; +template using MemberFunctionFirstParamType_t = typename MemberFunctionFirstParamType::type; +//! @} + /** * \brief Similar to \c PROPERTY_TYPE_LOOKUP_ENTRY but used when we do not have a member variable and instead must use * the return value of a getter member function. This is usually when we have some combo getters/setters that @@ -302,6 +317,9 @@ template using MemberFunctionReturnType_t = typename MemberFunct * * PROPERTY_TYPE_LOOKUP_ENTRY_NO_MV(PropertyNames::Fermentable::betaGlucanWithUnits, Fermentable::betaGlucanWithUnits, Measurement::PqEitherMassOrVolumeConcentration), * + * It would be neat to include the following in the macro: + * static_assert(std::is_member_function_pointer_v) + * However, because the macro is designed to be used inside an initialiser list, we can't. */ #define PROPERTY_TYPE_LOOKUP_ENTRY_NO_MV(propNameConstVar, getterMemberFunction, ...) \ {&propNameConstVar, TypeInfo::construct>(propNameConstVar, TypeLookupOf>::value __VA_OPT__ (, __VA_ARGS__))} diff --git a/src/widgets/BtBoolComboBox.cpp b/src/widgets/BtBoolComboBox.cpp index a014ea75..1fd4451f 100644 --- a/src/widgets/BtBoolComboBox.cpp +++ b/src/widgets/BtBoolComboBox.cpp @@ -67,7 +67,8 @@ void BtBoolComboBox::init(char const * const editorName , QString const & unsetDisplay , QString const & setDisplay , TypeInfo const & typeInfo ) { - qDebug() << Q_FUNC_INFO << comboBoxFqName << ":" << typeInfo; + // Normally keep this log statement commented out otherwise it generates too many lines in the log file +// qDebug() << Q_FUNC_INFO << comboBoxFqName << ":" << typeInfo; // It's a coding error to call init twice Q_ASSERT(!this->pimpl->m_initialised); @@ -128,6 +129,21 @@ void BtBoolComboBox::setNull() { return; } +void BtBoolComboBox::setDefault() { + this->setCurrentIndex(0); + return; +} + +void BtBoolComboBox::setFromVariant(QVariant const & value) { + Q_ASSERT(this->pimpl->m_initialised); + if (this->pimpl->m_typeInfo->isOptional()) { + this->setValue(value.value>()); + } else { + this->setValue(value.value()); + } + return; +} + [[nodiscard]] bool BtBoolComboBox::getNonOptBoolValue() const { Q_ASSERT(!this->isOptional()); QString const rawValue = this->currentData().toString(); @@ -146,8 +162,8 @@ void BtBoolComboBox::setNull() { return rawValue == trueValue; } -[[nodiscard]] QVariant BtBoolComboBox::getValue(TypeInfo const & typeInfo) const { - if (typeInfo.isOptional()) { +[[nodiscard]] QVariant BtBoolComboBox::getAsVariant() const { + if (this->pimpl->m_typeInfo->isOptional()) { return QVariant::fromValue(this->getOptBoolValue()); } return QVariant::fromValue(this->getNonOptBoolValue()); diff --git a/src/widgets/BtBoolComboBox.h b/src/widgets/BtBoolComboBox.h index 3abc8974..c093fb85 100644 --- a/src/widgets/BtBoolComboBox.h +++ b/src/widgets/BtBoolComboBox.h @@ -85,6 +85,13 @@ Q_OBJECT void setNull(); + void setDefault(); + + /** + * \brief Similar to \c SmartField::setFromVariant + */ + void setFromVariant(QVariant const & value); + /** * \brief Get value of a combo box for a non-optional bool */ @@ -96,9 +103,9 @@ Q_OBJECT [[nodiscard]] std::optional getOptBoolValue() const; /** - * \brief Get value of a combo box, using \c typeInfo to determine whether it is optional or not + * \brief Similar to \c SmartField::getAsVariant */ - [[nodiscard]] QVariant getValue(TypeInfo const & typeInfo) const; + [[nodiscard]] QVariant getAsVariant() const; private: // Private implementation details - see https://herbsutter.com/gotw/_100/ diff --git a/src/widgets/BtComboBox.cpp b/src/widgets/BtComboBox.cpp index c79f4d3b..8be3d81b 100644 --- a/src/widgets/BtComboBox.cpp +++ b/src/widgets/BtComboBox.cpp @@ -116,6 +116,11 @@ void BtComboBox::init(char const * const editorName , } void BtComboBox::autoSetFromControlledField() { + // Normally keep this log statement commented out otherwise it generates too many lines in the log file +// qDebug().noquote() << +// Q_FUNC_INFO << this->pimpl->m_comboBoxFqName << ":" << this->pimpl->m_typeInfo->fieldType << +// Logging::getStackTrace(); + // It's a coding error to call this when there is no controlled field Q_ASSERT(this->pimpl->m_controlledField); @@ -153,8 +158,32 @@ void BtComboBox::setValue(int value) { return; } -QVariant BtComboBox::getValue(TypeInfo const & typeInfo) const { - if (typeInfo.isOptional()) { + +void BtComboBox::setDefault() { + this->setCurrentIndex(0); + return; +} + +void BtComboBox::setFromVariant(QVariant const & value) { + Q_ASSERT(this->pimpl->m_initialised); + // We assume the QVariant holds an int or an optional int, as we do for serialisation etc, as otherwise it gets hard + // to handle strongly-typed enums generically at runtime. + if (this->pimpl->m_typeInfo->isOptional()) { + auto vv {value.value>()}; + if (vv) { + this->setValue(*vv); + } else { + this->setNull(); + } + } else { + this->setValue(value.value()); + } + return; +} + +QVariant BtComboBox::getAsVariant() const { + Q_ASSERT(this->pimpl->m_initialised); + if (this->pimpl->m_typeInfo->isOptional()) { return QVariant::fromValue(this->getOptIntValue()); } return QVariant::fromValue(this->getNonOptIntValue()); diff --git a/src/widgets/BtComboBox.h b/src/widgets/BtComboBox.h index 959d7be1..0b4990fa 100644 --- a/src/widgets/BtComboBox.h +++ b/src/widgets/BtComboBox.h @@ -138,9 +138,9 @@ Q_OBJECT } /** - * \brief Get value of a combo box, using \c typeInfo to determine whether it is optional or not + * \brief Get value of a combo box */ - [[nodiscard]] QVariant getValue(TypeInfo const & typeInfo) const; + [[nodiscard]] QVariant getAsVariant() const; /** * \brief Called from templated version of \c setValue, but also used in generic code (eg \c ItemDelegate) where we @@ -154,6 +154,13 @@ Q_OBJECT */ void setValue(int value); + void setDefault(); + + /** + * \brief Similar to \c SmartField::setFromVariant + */ + void setFromVariant(QVariant const & value); + [[nodiscard]] std::optional getOptIntValue() const; [[nodiscard]] int getNonOptIntValue() const; diff --git a/src/widgets/SmartField.cpp b/src/widgets/SmartField.cpp index 9c68074a..65a02b81 100644 --- a/src/widgets/SmartField.cpp +++ b/src/widgets/SmartField.cpp @@ -358,12 +358,16 @@ void SmartField::setAmount(Measurement::Amount const & amount) { // For the moment, I'm going to say this function should _only_ be called for ChoiceOfPhysicalQuantity Q_ASSERT(std::holds_alternative(*this->getTypeInfo().fieldType)); + // Usually leave this debug log commented out unless trouble-shooting as it generates a lot of logging +// qDebug() << Q_FUNC_INFO << this->pimpl->m_fieldFqName << "amount:" << amount; this->setRawText(this->displayAmount(amount, this->pimpl->m_precision)); return; } void SmartField::setAmount(std::optional const & amount) { Q_ASSERT(this->pimpl->m_initialised); + // Usually leave this debug log commented out unless trouble-shooting as it generates a lot of logging +// qDebug() << Q_FUNC_INFO << this->pimpl->m_fieldFqName << "amount:" << amount; if (!amount) { this->setRawText(""); return; @@ -372,6 +376,12 @@ void SmartField::setAmount(std::optional const & amount) { return; } +void SmartField::setDefault() { + Q_ASSERT(this->pimpl->m_initialised); + this->setRawText(""); + return; +} + // Instantiate the above template functions for the types that are going to use it // (This is all just a trick to allow the template definition to be here in the .cpp file and not in the header, which // saves having to put a bunch of std::string stuff there.) @@ -418,9 +428,9 @@ void SmartField::setFromVariant(QVariant const & value) { } } else if (ti == typeid(Measurement::Amount)) { if (typeInfo.isOptional()) { - this->setAmount(value.value()); - } else { this->setAmount(value.value>()); + } else { + this->setAmount(value.value()); } } else if (ti == typeid(QString)) { Q_ASSERT(!typeInfo.isOptional()); diff --git a/src/widgets/SmartField.h b/src/widgets/SmartField.h index 386df0e1..4ddc2783 100644 --- a/src/widgets/SmartField.h +++ b/src/widgets/SmartField.h @@ -257,6 +257,12 @@ class SmartField : public SmartBase { void setAmount(std::optional const & amount); + /** + * \brief At the moment, this sets the field value to blank text, but we could imagine it being more sophisticated in + * future. + */ + void setDefault(); + /** * \brief Wrapper to call \c setAmount or correct version of \c setQuantity, from a \c QVariant parameter that would * typically have been obtained from Qt property system, eg via \c QObject::property(). diff --git a/ui/fermentableEditor.ui b/ui/fermentableEditor.ui index 580bcee7..6a65c49e 100644 --- a/ui/fermentableEditor.ui +++ b/ui/fermentableEditor.ui @@ -69,6 +69,19 @@ + + + + Required + + + Type + + + comboBox_type + + + @@ -77,12 +90,6 @@ 0 - - - 100 - 16777215 - - Fermantable Type @@ -114,19 +121,6 @@ - - - - Required - - - Type - - - comboBox_type - - - @@ -189,12 +183,12 @@ - + Required - GrainGroup + Grain Group comboBox_grainGroup diff --git a/ui/fermentationEditor.ui b/ui/fermentationEditor.ui index e5aa1599..fc7a5d36 100644 --- a/ui/fermentationEditor.ui +++ b/ui/fermentationEditor.ui @@ -56,7 +56,7 @@ - + Notes diff --git a/ui/hopEditor.ui b/ui/hopEditor.ui index 7adf2879..3b81d7a0 100644 --- a/ui/hopEditor.ui +++ b/ui/hopEditor.ui @@ -814,7 +814,7 @@ - + Notes diff --git a/ui/mainWindow.ui b/ui/mainWindow.ui index dcd74e77..04c0fa0d 100644 --- a/ui/mainWindow.ui +++ b/ui/mainWindow.ui @@ -236,6 +236,25 @@ + + + + + :/images/iconMash.svg:/images/iconMash.svg + + + + + + Mashes + + + + + + + + @@ -2527,6 +2546,11 @@ QWidget
StyleRangeWidget.h
+ + MashTreeView + QTreeView +
trees/TreeView.h
+
MiscTreeView QTreeView diff --git a/ui/miscEditor.ui b/ui/miscEditor.ui index 8e11f2fa..4d161d5c 100644 --- a/ui/miscEditor.ui +++ b/ui/miscEditor.ui @@ -129,29 +129,6 @@ - - - - Qt::CustomContextMenu - - - Time - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - lineEdit_time - - - - - - - Time - - - @@ -225,7 +202,7 @@ - + Use for @@ -345,7 +322,6 @@ tabWidget_editor lineEdit_name comboBox_type - lineEdit_time lineEdit_inventory pushButton_new pushButton_save diff --git a/ui/styleEditor.ui b/ui/styleEditor.ui index 8255c8e1..f52c8308 100644 --- a/ui/styleEditor.ui +++ b/ui/styleEditor.ui @@ -36,7 +36,7 @@ Main - + Required @@ -49,14 +49,20 @@ - + + + + 10 + 0 + + Name - + Required @@ -69,21 +75,15 @@ - + Category - + - - - 0 - 0 - - Required @@ -95,21 +95,15 @@ - + Category number - + - - - 0 - 0 - - Required @@ -121,21 +115,15 @@ - + Style letter - + - - - 0 - 0 - - Required @@ -147,21 +135,15 @@ - + Style guide - + - - - 0 - 0 - - Required @@ -170,7 +152,7 @@ - + Type of beverage @@ -197,17 +179,17 @@ Ranges - - + + - Max + Min - - + + - Min + Max diff --git a/ui/waterEditor.ui b/ui/waterEditor.ui index 117b3073..b43ea65b 100644 --- a/ui/waterEditor.ui +++ b/ui/waterEditor.ui @@ -46,7 +46,7 @@ QLayout::SetMinimumSize - + @@ -59,7 +59,7 @@ - + @@ -69,7 +69,7 @@ - + @@ -85,7 +85,7 @@ - + @@ -99,15 +99,22 @@ 0 - + + + + + + Qt::Horizontal + + - 16777215 - 16777215 + 40 + 20 - + - + @@ -123,7 +130,7 @@ - + @@ -137,15 +144,9 @@ 0 - - - 16777215 - 16777215 - - - + @@ -161,7 +162,7 @@ - + @@ -175,15 +176,9 @@ 0 - - - 16777215 - 16777215 - - - + @@ -202,7 +197,7 @@ - + @@ -216,15 +211,9 @@ 0 - - - 16777215 - 16777215 - - - + @@ -240,7 +229,7 @@ - + @@ -254,15 +243,9 @@ 0 - - - 16777215 - 16777215 - - - + @@ -281,7 +264,7 @@ - + @@ -295,15 +278,9 @@ 0 - - - 16777215 - 16777215 - - - + @@ -319,7 +296,7 @@ - + @@ -333,16 +310,10 @@ 0 - - - 16777215 - 16777215 - - - - + + 0 @@ -360,8 +331,8 @@ - - + + 0 @@ -380,50 +351,159 @@ - - - - - - + + + - Notes + Carbonate + + + lineEdit_carbonate - - - - - 0 - 0 - + + + + + + + + Potassium + + + lineEdit_potassium + + + + + + + + + + + Iron + + + lineEdit_iron + + + + + + + + + + + Flouride + + + lineEdit_fluoride + + + + + + + + + + + Nitrate + + + lineEdit_nitrate + + + + + + + + + + + Nitrite + + + lineEdit_nitrite + + + + + + + + Notes + + + + + + + + + + + + New hop + + + + + + + :/images/smallPlus.svg:/images/smallPlus.svg + + + + + + + Save and close + + + + + + + :/images/filesave.svg:/images/filesave.svg + + + + + + + Discard and close + + + + + + + :/images/exit.svg:/images/exit.svg + + + + + - - - - - 0 - 0 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - + + BtBoolComboBox + QComboBox +
widgets/BtBoolComboBox.h
+
SmartLabel QLabel diff --git a/ui/yeastEditor.ui b/ui/yeastEditor.ui index bebe8a15..7412c2d4 100644 --- a/ui/yeastEditor.ui +++ b/ui/yeastEditor.ui @@ -382,32 +382,6 @@
- - - - - 0 - 0 - - - - Add to Secondary - - - - - - - - 0 - 0 - - - - Whether to add this yeast to secondary instead of primary - - - @@ -538,7 +512,7 @@ - + Best For @@ -674,7 +648,6 @@ comboBox_yeastFlocculation lineEdit_maxReuse lineEdit_timesCultured - boolCombo_addToSecondary pushButton_new pushButton_save pushButton_cancel