From 8bed2b435c28d79964fe5b97cf2b29e57ad407d8 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Mon, 21 Oct 2024 19:22:13 +0200 Subject: [PATCH 1/8] Various fixes and tidy-up --- .github/workflows/linux-ubuntu.yml | 11 + CHANGES.markdown | 2 +- meson.build | 5 +- scripts/buildTool.py | 1 + src/AncestorDialog.cpp | 4 +- src/BrewDayFormatter.cpp | 51 +- src/BrewDayScrollWidget.cpp | 172 +++--- src/BrewDayScrollWidget.h | 12 +- src/CMakeLists.txt | 1 - src/InstructionWidget.cpp | 114 ---- src/InstructionWidget.h | 62 -- src/MainWindow.cpp | 2 +- src/PrintAndPreviewDialog.cpp | 4 +- src/RecipeFormatter.cpp | 17 +- src/TimerListDialog.cpp | 66 ++- src/WaterDialog.cpp | 1 - src/database/DatabaseSchemaHelper.cpp | 93 ++- src/database/ObjectStore.cpp | 7 + src/database/ObjectStoreTyped.cpp | 346 ++++++------ src/editors/BoilStepEditor.cpp | 6 +- src/editors/FermentationStepEditor.cpp | 4 +- src/editors/MashStepEditor.cpp | 6 +- src/model/Boil.cpp | 33 +- src/model/Boil.h | 26 +- src/model/BoilStep.cpp | 5 +- src/model/BoilStep.h | 8 + src/model/BrewNote.h | 6 +- src/model/Equipment.h | 13 +- src/model/Fermentation.cpp | 26 +- src/model/Fermentation.h | 49 +- src/model/FermentationStep.cpp | 3 +- src/model/FermentationStep.h | 10 +- src/model/FolderBase.h | 32 +- src/model/Ingredient.h | 4 +- src/model/Instruction.cpp | 110 ++-- src/model/Instruction.h | 65 ++- src/model/Mash.cpp | 27 +- src/model/Mash.h | 22 +- src/model/MashStep.cpp | 10 +- src/model/MashStep.h | 16 +- src/model/Misc.h | 4 +- src/model/NamedEntity.h | 10 +- src/model/OwnedByRecipe.cpp | 8 +- src/model/OwnedByRecipe.h | 13 +- src/model/Recipe.cpp | 517 ++++++++--------- src/model/Recipe.h | 132 +++-- src/model/RecipeAdditionFermentable.cpp | 6 - src/model/RecipeAdditionFermentable.h | 6 +- src/model/RecipeAdditionHop.cpp | 4 - src/model/RecipeAdditionHop.h | 6 +- src/model/RecipeAdditionMisc.h | 6 +- src/model/RecipeAdditionYeast.cpp | 6 - src/model/RecipeAdditionYeast.h | 6 +- src/model/RecipeAdjustmentSalt.cpp | 5 - src/model/RecipeAdjustmentSalt.h | 6 +- src/model/RecipeUseOfWater.h | 4 +- src/model/Step.cpp | 32 -- src/model/Step.h | 85 +-- src/model/StepBase.h | 259 ++++++--- src/model/StepOwnerBase.h | 473 ++-------------- src/model/SteppedBase.h | 219 +++++++ src/model/SteppedOwnerBase.h | 533 ++++++++++++++++++ src/model/Style.h | 4 +- src/model/Water.cpp | 15 - src/model/Water.h | 17 +- src/serialization/ImportExport.cpp | 2 +- src/serialization/json/BeerJson.cpp | 22 +- src/serialization/xml/BeerXml.cpp | 24 +- src/serialization/xml/XmlMashRecord.cpp | 4 +- src/serialization/xml/XmlRecipeRecord.cpp | 261 +-------- src/tableModels/BoilStepTableModel.cpp | 6 +- .../FermentationStepTableModel.cpp | 4 +- src/tableModels/MashStepTableModel.cpp | 4 +- src/tableModels/MashStepTableModel.h | 44 -- src/tableModels/SaltTableModel.h | 24 - src/tableModels/StepTableModelBase.h | 2 +- src/tableModels/TableModelBase.h | 9 +- src/trees/TreeModel.cpp | 14 +- translations/bt_ca.ts | 22 +- translations/bt_cs.ts | 22 +- translations/bt_de.ts | 22 +- translations/bt_el.ts | 22 +- translations/bt_en.ts | 39 +- translations/bt_es.ts | 22 +- translations/bt_et.ts | 39 +- translations/bt_eu.ts | 39 +- translations/bt_fr.ts | 22 +- translations/bt_gl.ts | 34 +- translations/bt_hu.ts | 22 +- translations/bt_it.ts | 22 +- translations/bt_lv.ts | 39 +- translations/bt_nb.ts | 22 +- translations/bt_nl.ts | 22 +- translations/bt_pl.ts | 22 +- translations/bt_pt.ts | 22 +- translations/bt_ru.ts | 22 +- translations/bt_sr.ts | 32 +- translations/bt_sv.ts | 22 +- translations/bt_tr.ts | 22 +- translations/bt_zh.ts | 22 +- ui/instructionWidget.ui | 98 ---- 101 files changed, 2408 insertions(+), 2512 deletions(-) delete mode 100644 src/InstructionWidget.cpp delete mode 100644 src/InstructionWidget.h create mode 100644 src/model/SteppedBase.h create mode 100644 src/model/SteppedOwnerBase.h delete mode 100644 ui/instructionWidget.ui diff --git a/.github/workflows/linux-ubuntu.yml b/.github/workflows/linux-ubuntu.yml index 7aa48ba14..d27f9c05b 100644 --- a/.github/workflows/linux-ubuntu.yml +++ b/.github/workflows/linux-ubuntu.yml @@ -133,6 +133,17 @@ jobs: umask 022 ../bt package + # + # Since we're running on Ubuntu, we ought at least to be able to test installing the .deb packages. There should + # be just one .deb file created on each run, but we use pattern matching to save having to construct the exact + # name. + # + - name: Install deb Package + working-directory: ${{github.workspace}}/mbuild/packages/linux + shell: bash + run: | + sudo dpkg -i brewtarget*.deb + - name: Upload Linux Packages (Installers) if: ${{ success() }} uses: actions/upload-artifact@v4 diff --git a/CHANGES.markdown b/CHANGES.markdown index 48df1dafe..fe5520f5d 100644 --- a/CHANGES.markdown +++ b/CHANGES.markdown @@ -20,7 +20,7 @@ Bug fixes and minor enhancements. * None ### Bug Fixes - +* Clicking 'Generate Instructions' on the Brewday tab overwrites existing instructions without warning [862](https://github.com/Brewtarget/brewtarget/issues/862) ### Release Timestamp Tue, 16 Oct 2024 04:00:08 +0100 diff --git a/meson.build b/meson.build index 456667920..73bc023c9 100644 --- a/meson.build +++ b/meson.build @@ -191,7 +191,7 @@ project('brewtarget', 'cpp', version: '4.0.8', license: 'GPL-3.0-or-later', - meson_version: '>=0.60.0', + meson_version: '>=0.63.0', default_options : ['cpp_std=c++20', 'warning_level=3', # 'prefer_static=true', See comment above for why this is commented-out for now @@ -614,7 +614,6 @@ commonSourceFiles = files([ 'src/Html.cpp', 'src/HydrometerTool.cpp', 'src/IbuGuSlider.cpp', - 'src/InstructionWidget.cpp', 'src/InventoryFormatter.cpp', 'src/Localization.cpp', 'src/Logging.cpp', @@ -851,7 +850,6 @@ mocHeaders = files([ 'src/HelpDialog.h', 'src/HydrometerTool.h', 'src/IbuGuSlider.h', - 'src/InstructionWidget.h', 'src/MainWindow.h', 'src/MashDesigner.h', 'src/MashWizard.h', @@ -1021,7 +1019,6 @@ uiFiles = files([ 'ui/fermentationEditor.ui', 'ui/fermentationStepEditor.ui', 'ui/hopEditor.ui', - 'ui/instructionWidget.ui', 'ui/mainWindow.ui', 'ui/mashDesigner.ui', 'ui/mashEditor.ui', diff --git a/scripts/buildTool.py b/scripts/buildTool.py index cae6d324f..bd3ac48e6 100755 --- a/scripts/buildTool.py +++ b/scripts/buildTool.py @@ -1969,6 +1969,7 @@ def doPackage(): # pathsToSearch = os.environ['PATH'].split(os.pathsep) for extraLib in [ + 'libb2', # BLAKE hash functions -- https://en.wikipedia.org/wiki/BLAKE_(hash_function) 'libbrotlicommon', # Brotli compression -- see https://en.wikipedia.org/wiki/Brotli 'libbrotlidec', # Brotli compression 'libbrotlienc', # Brotli compression diff --git a/src/AncestorDialog.cpp b/src/AncestorDialog.cpp index 2b17302bb..db1035542 100644 --- a/src/AncestorDialog.cpp +++ b/src/AncestorDialog.cpp @@ -70,7 +70,7 @@ void AncestorDialog::buildAncestorBox() { QList recipes = ObjectStoreWrapper::getAllRaw(); std::sort(recipes.begin(), recipes.end(), AncestorDialog::recipeLessThan); - foreach (Recipe * recipe, recipes) { + for (auto recipe : recipes) { if (recipe->display()) { comboBox_ancestor->addItem(recipe->name(), recipe->key()); } @@ -84,7 +84,7 @@ void AncestorDialog::buildDescendantBox(Recipe * ignore) { std::sort(recipes.begin(), recipes.end(), recipeLessThan); // The rules of what can be a target are complex - foreach (Recipe * recipe, recipes) { + for (auto recipe : recipes) { // if we are ignoring the recipe, skip if (recipe == ignore) { continue; diff --git a/src/BrewDayFormatter.cpp b/src/BrewDayFormatter.cpp index 099837e15..cc5ea3359 100644 --- a/src/BrewDayFormatter.cpp +++ b/src/BrewDayFormatter.cpp @@ -196,32 +196,30 @@ QString BrewDayFormatter::buildInstructionHtml() { .arg(tr("Time")) .arg(tr("Step")); - QList instructions = recObs->instructions(); - int size = instructions.size(); - for (int i = 0; i < size; ++i) { + bool useAlt = true; + for (auto instruction : recObs->steps()) { + useAlt = !useAlt; QString stepTime, tmp; QList reagents; - Instruction * ins = instructions[i]; - - if (ins->interval() > 0.0) { - stepTime = Measurement::displayAmount(Measurement::Amount{ins->interval(), Measurement::Units::minutes}, 0); + if (instruction->interval() > 0.0) { + stepTime = Measurement::displayAmount(Measurement::Amount{instruction->interval(), Measurement::Units::minutes}, 0); } else { stepTime = "--"; } tmp = ""; - // TODO: comparing ins->name() with these untranslated strings means this + // TODO: comparing instruction->name() with these untranslated strings means this // doesn't work in other languages. Find a better way. - if (ins->name() == tr("Add grains")) { + if (instruction->name() == tr("Add grains")) { reagents = recObs->getReagents(recObs->fermentableAdditions()); - } else if (ins->name() == tr("Heat water")) { + } else if (instruction->name() == tr("Heat water")) { if (recObs->mash()) { reagents = recObs->getReagents(recObs->mash()->mashSteps()); } } else { - reagents = ins->reagents(); + reagents = instruction->reagents(); } if (reagents.size() > 1) { @@ -234,15 +232,15 @@ QString BrewDayFormatter::buildInstructionHtml() { } else if (reagents.size() == 1) { tmp = reagents.at(0); } else { - tmp = ins->directions(); + tmp = instruction->directions(); } - QString altTag = i % 2 ? "alt" : "norm"; + QString altTag = useAlt ? "alt" : "norm"; middle += QString("%2%3 : %4") .arg(altTag) .arg(stepTime) - .arg(ins->name()) + .arg(instruction->name()) .arg(tmp); } middle += ""; @@ -254,7 +252,6 @@ QList BrewDayFormatter::buildInstructionList() { QList ret; QStringList row; - int i, size; row.append(tr("Completed")); row.append(tr("Time")); @@ -262,43 +259,39 @@ QList BrewDayFormatter::buildInstructionList() { ret.append(row); row.clear(); - QList instructions = recObs->instructions(); - size = instructions.size(); - for (i = 0; i < size; ++i) { + for (auto instruction : recObs->steps()) { QString stepTime, tmp; QList reagents; - Instruction * ins = instructions[i]; - - if (ins->interval() > 0.0) { - stepTime = Measurement::displayAmount(Measurement::Amount{ins->interval(), Measurement::Units::minutes}, 0); + if (instruction->interval() > 0.0) { + stepTime = Measurement::displayAmount(Measurement::Amount{instruction->interval(), Measurement::Units::minutes}, 0); } else { stepTime = "--"; } - // TODO: comparing ins->name() with these untranslated strings means this + // TODO: comparing instruction->name() with these untranslated strings means this // doesn't work in other languages. Find a better way. - if (ins->name() == tr("Add grains")) { + if (instruction->name() == tr("Add grains")) { reagents = recObs->getReagents(recObs->fermentableAdditions()); - } else if (ins->name() == tr("Heat water")) { + } else if (instruction->name() == tr("Heat water")) { if (recObs->mash()) { reagents = recObs->getReagents(recObs->mash()->mashSteps()); } } else { - reagents = ins->reagents(); + reagents = instruction->reagents(); } tmp = ""; if (reagents.size() > 0) { - foreach (QString reagent, reagents) { + for (QString reagent : reagents) { tmp += QString("\t%1\n").arg(reagent); } } else { - tmp = ins->directions(); + tmp = instruction->directions(); } row.append(stepTime); - row.append(QString("%1 : %2").arg(ins->name()).arg(tmp)); + row.append(QString("%1 : %2").arg(instruction->name()).arg(tmp)); ret.append(row); row.clear(); } diff --git a/src/BrewDayScrollWidget.cpp b/src/BrewDayScrollWidget.cpp index 9de680732..e8ce545dd 100644 --- a/src/BrewDayScrollWidget.cpp +++ b/src/BrewDayScrollWidget.cpp @@ -25,12 +25,13 @@ #include #include +#include #include #include #include "database/ObjectStoreWrapper.h" #include "Html.h" -#include "InstructionWidget.h" +///#include "InstructionWidget.h" #include "measurement/Measurement.h" #include "measurement/UnitSystem.h" #include "model/Equipment.h" @@ -60,7 +61,7 @@ namespace { BrewDayScrollWidget::BrewDayScrollWidget(QWidget* parent) : QWidget{parent}, - recObs{nullptr} { + m_recObs{nullptr} { this->setupUi(this); this->setObjectName("BrewDayScrollWidget"); @@ -78,26 +79,27 @@ BrewDayScrollWidget::BrewDayScrollWidget(QWidget* parent) : QWidget{parent}, BrewDayScrollWidget::~BrewDayScrollWidget() = default; void BrewDayScrollWidget::saveInstruction() { - this->recObs->instructions()[ listWidget->currentRow() ]->setDirections( btTextEdit->toPlainText() ); - return; + this->m_recObs->steps()[ listWidget->currentRow() ]->setDirections( btTextEdit->toPlainText() ); + return; } void BrewDayScrollWidget::showInstruction(int insNdx) { - if (this->recObs == nullptr) { + if (!this->m_recObs) { return; } - int size = recIns.size(); + int size = m_recIns.size(); if (insNdx < 0 || insNdx >= size) { return; } // Block signals to avoid setPlainText() from triggering saveInstruction(). - btTextEdit->setPlainText((recIns[insNdx])->directions()); + btTextEdit->setPlainText((m_recIns[insNdx])->directions()); + return; } void BrewDayScrollWidget::generateInstructions() { - if (this->recObs == nullptr) { + if (!this->m_recObs) { return; } @@ -105,7 +107,27 @@ void BrewDayScrollWidget::generateInstructions() { btTextEdit->setEnabled(true); } - this->recObs->generateInstructions(); + // + // If the Recipe already has instructions, then they will get erased when we call generateInstructions. In case the + // user has done manual edits to the existing instructions, we should get confirmation to proceed. + // + // TODO: Would be neat to make this an undoable action + // + bool proceed = true; + if (this->m_recObs->numSteps() > 0) { + proceed = QMessageBox::Yes == QMessageBox::question( + this, + tr("Overwrite Existing Instructions"), + tr("Generating instructions will overwrite the existing ones. This is not undoable. Do you want to proceed?"), + QMessageBox::Yes, + QMessageBox::No + ); + } + + if (proceed) { + this->m_recObs->generateInstructions(); + } + return; } QSize BrewDayScrollWidget::sizeHint() const { @@ -113,7 +135,7 @@ QSize BrewDayScrollWidget::sizeHint() const { } void BrewDayScrollWidget::removeSelectedInstruction() { - if (this->recObs == nullptr) { + if (this->m_recObs == nullptr) { return; } @@ -121,17 +143,17 @@ void BrewDayScrollWidget::removeSelectedInstruction() { if (row < 0) { return; } - this->recObs->remove(ObjectStoreWrapper::getSharedFromRaw(this->recIns[row])); + this->m_recObs->removeStep(this->m_recIns[row]); // After updating the model, this is the simplest way to update the display - this->setRecipe(this->recObs); + this->setRecipe(this->m_recObs); - if (this->recIns.isEmpty()) { + if (this->m_recIns.isEmpty()) { btTextEdit->clear(); btTextEdit->setEnabled(false); } else { - if (row > this->recIns.size()) { - row = this->recIns.size(); + if (row > this->m_recIns.size()) { + row = this->m_recIns.size(); } listWidget->setCurrentRow(row); } @@ -140,7 +162,7 @@ void BrewDayScrollWidget::removeSelectedInstruction() { } void BrewDayScrollWidget::pushInstructionUp() { - if (this->recObs == nullptr) { + if (this->m_recObs == nullptr) { return; } @@ -149,17 +171,17 @@ void BrewDayScrollWidget::pushInstructionUp() { return; } - this->recObs->swapInstructions(this->recIns[row], this->recIns[row-1]); + this->m_recObs->swapSteps(*this->m_recIns[row], *this->m_recIns[row-1]); // After updating the model, this is the simplest way to update the display - this->setRecipe(this->recObs); + this->setRecipe(this->m_recObs); listWidget->setCurrentRow(row-1); return; } void BrewDayScrollWidget::pushInstructionDown() { - if (this->recObs == nullptr) { + if (this->m_recObs == nullptr) { return; } @@ -168,28 +190,28 @@ void BrewDayScrollWidget::pushInstructionDown() { return; } - this->recObs->swapInstructions(this->recIns[row], this->recIns[row+1]); + this->m_recObs->swapSteps(*this->m_recIns[row], *this->m_recIns[row+1]); // After updating the model, this is the simplest way to update the display - this->setRecipe(this->recObs); + this->setRecipe(this->m_recObs); listWidget->setCurrentRow(row+1); return; } bool BrewDayScrollWidget::loadComplete(bool ok) { - this->doc->print(this->printer); + this->m_doc->print(this->m_printer); return ok; } void BrewDayScrollWidget::print(QPrinter *mainPrinter, int action, QFile* outFile) { - if (this->recObs == nullptr) { + if (this->m_recObs == nullptr) { return; } // Connect the webview's signal if (action == PRINT) { - this->printer = mainPrinter; + this->m_printer = mainPrinter; } // Start building the document to be printed. The HTML doesn't work with @@ -199,14 +221,14 @@ void BrewDayScrollWidget::print(QPrinter *mainPrinter, int action, QFile* outFil pDoc += buildFooterTable(); pDoc += tr("

Notes

"); - if (this->recObs->notes() != "" ) - pDoc += QString("
%1
\n").arg(recObs->notes()); + if (this->m_recObs->notes() != "" ) + pDoc += QString("
%1
\n").arg(m_recObs->notes()); pDoc += ""; - this->doc->setHtml(pDoc); + this->m_doc->setHtml(pDoc); if (action == PREVIEW) { - this->doc->show(); + this->m_doc->show(); } else if ( action == HTML ) { QTextStream out(outFile); out << pDoc; @@ -219,20 +241,20 @@ void BrewDayScrollWidget::print(QPrinter *mainPrinter, int action, QFile* outFil void BrewDayScrollWidget::setRecipe(Recipe* rec) { // Disconnect old notifier. - if (this->recObs) { - disconnect(this->recObs, &Recipe::changed, this, &BrewDayScrollWidget::acceptChanges ); + if (this->m_recObs) { + disconnect(this->m_recObs, &Recipe::changed, this, &BrewDayScrollWidget::acceptChanges ); } - this->recObs = rec; - connect(this->recObs, &Recipe::changed, this, &BrewDayScrollWidget::acceptChanges); + this->m_recObs = rec; + connect(this->m_recObs, &Recipe::changed, this, &BrewDayScrollWidget::acceptChanges); - recIns = this->recObs->instructions(); - for (Instruction* ins : recIns) { - connect(ins, &Instruction::changed, this, &BrewDayScrollWidget::acceptInsChanges); + m_recIns = this->m_recObs->steps(); + for (auto ins : m_recIns) { + connect(ins.get(), &Instruction::changed, this, &BrewDayScrollWidget::acceptInsChanges); } btTextEdit->clear(); - if (recIns.isEmpty()) { + if (m_recIns.isEmpty()) { btTextEdit->setEnabled(false); } else { btTextEdit->setEnabled(true); @@ -243,7 +265,7 @@ void BrewDayScrollWidget::setRecipe(Recipe* rec) { } void BrewDayScrollWidget::insertInstruction() { - if (this->recObs == nullptr) { + if (this->m_recObs == nullptr) { return; } @@ -260,30 +282,30 @@ void BrewDayScrollWidget::insertInstruction() { } qDebug() << Q_FUNC_INFO << "Inserting instruction '" << lineEdit_name->text() << "' at posistion" << pos; - auto ins = std::make_shared(); - ins->setName(lineEdit_name->text()); - ObjectStoreWrapper::insert(ins); + auto instruction = std::make_shared(); + instruction->setName(lineEdit_name->text()); + ObjectStoreWrapper::insert(instruction); lineEdit_name->clear(); - pos = qBound(1, pos, this->recIns.size()); - this->recObs->insertInstruction(*ins.get(), pos); + pos = qBound(1, pos, this->m_recIns.size()); + this->m_recObs->insertStep(instruction, pos); // After updating the model, this is the simplest way to update the display - this->setRecipe(this->recObs); + this->setRecipe(this->m_recObs); listWidget->setCurrentRow(pos - 1); return; } void BrewDayScrollWidget::acceptChanges(QMetaProperty prop, QVariant /*value*/) { - if (recObs && QString(prop.name()) == "instructions") { + if (m_recObs && QString(prop.name()) == PropertyNames::SteppedOwnerBase::steps) { // An instruction has been added or deleted, so update internal list. - foreach( Instruction* ins, recIns ) { - disconnect(ins, nullptr, this, nullptr); + for (auto ins : m_recIns ) { + disconnect(ins.get(), nullptr, this, nullptr); } - recIns =this->recObs->instructions(); // Already sorted by instruction numbers. - foreach( Instruction* ins, recIns ) { - connect(ins, &Instruction::changed, this, &BrewDayScrollWidget::acceptInsChanges); + m_recIns = this->m_recObs->steps(); // Already sorted by instruction numbers. + for (auto ins : m_recIns ) { + connect(ins.get(), &Instruction::changed, this, &BrewDayScrollWidget::acceptInsChanges); } showChanges(); } @@ -294,7 +316,7 @@ void BrewDayScrollWidget::acceptInsChanges(QMetaProperty prop, QVariant /*value* QString propName = prop.name(); if (propName == "instructionNumber") { // The order changed, so resort our internal list. - std::sort(recIns.begin(), recIns.end(), insPtrLtByNumber); + std::sort(m_recIns.begin(), m_recIns.end()); showChanges(); } else if (propName == PropertyNames::Instruction::directions) { // This will make the displayed text directions update. @@ -310,7 +332,7 @@ void BrewDayScrollWidget::clear() { void BrewDayScrollWidget::showChanges() { this->clear(); - if (this->recObs == nullptr) { + if (this->m_recObs == nullptr) { return; } @@ -321,17 +343,17 @@ void BrewDayScrollWidget::showChanges() { void BrewDayScrollWidget::repopulateListWidget() { this->listWidget->clear(); - if (this->recObs == nullptr) { + if (this->m_recObs == nullptr) { return; } - foreach( Instruction* ins, this->recIns ) { + for (auto ins : this->m_recIns ) { //QString text = tr("Step %1: %2").arg(i).arg(ins->name()); - QString text = tr("Step %1: %2").arg(ins->instructionNumber()).arg(ins->name()); + QString text = tr("Step %1: %2").arg(ins->stepNumber()).arg(ins->name()); listWidget->addItem(new QListWidgetItem(text)); } - if (this->recIns.size() > 0 ) { + if (this->m_recIns.size() > 0 ) { this->listWidget->setCurrentRow(0); } else { this->listWidget->setCurrentRow(-1); @@ -341,13 +363,13 @@ void BrewDayScrollWidget::repopulateListWidget() { QString BrewDayScrollWidget::buildTitleTable(bool includeImage) { // Do the style sheet first - if (this->cssName == nullptr) { - this->cssName = ":/css/brewday.css"; + if (this->m_cssName == nullptr) { + this->m_cssName = ":/css/brewday.css"; } - QString header = Html::createHeader(BrewDayScrollWidget::tr("Brewday"), cssName); + QString header = Html::createHeader(BrewDayScrollWidget::tr("Brewday"), m_cssName); - QString body = QString("

%1

").arg(recObs->name()); + QString body = QString("

%1

").arg(m_recObs->name()); if (includeImage) { body += QString("").arg("qrc:/images/title.svg"); } @@ -358,7 +380,7 @@ QString BrewDayScrollWidget::buildTitleTable(bool includeImage) { body += QString("%1") .arg(tr("Style")); body += QString("%1") - .arg(styleName(recObs->style().get())); + .arg(styleName(m_recObs->style().get())); body += QString("%1") .arg(tr("Date")); body += QString("%1") @@ -367,30 +389,30 @@ QString BrewDayScrollWidget::buildTitleTable(bool includeImage) { // second row: boil time and efficiency. body += QString("%1%2%3%4") .arg(tr("Boil Time")) - .arg(boilTime(recObs->equipment().get())) + .arg(boilTime(m_recObs->equipment().get())) .arg(tr("Efficiency")) - .arg(Measurement::displayQuantity(recObs->efficiency_pct(), 0)); + .arg(Measurement::displayQuantity(m_recObs->efficiency_pct(), 0)); // third row: pre-Boil Volume and Preboil Gravity body += QString("%1%2%3%4") .arg(tr("Boil Volume")) - .arg(Measurement::displayAmount(Measurement::Amount{recObs->boilVolume_l(), Measurement::Units::liters}, 2)) + .arg(Measurement::displayAmount(Measurement::Amount{m_recObs->boilVolume_l(), Measurement::Units::liters}, 2)) .arg(tr("Preboil Gravity")) - .arg(Measurement::displayAmount(Measurement::Amount{recObs->boilGrav(), Measurement::Units::specificGravity}, 3)); + .arg(Measurement::displayAmount(Measurement::Amount{m_recObs->boilGrav(), Measurement::Units::specificGravity}, 3)); // fourth row: Final volume and starting gravity body += QString("%1%2%3%4") .arg(tr("Final Volume")) - .arg(Measurement::displayAmount(Measurement::Amount{recObs->finalVolume_l(), Measurement::Units::liters}, 2)) + .arg(Measurement::displayAmount(Measurement::Amount{m_recObs->finalVolume_l(), Measurement::Units::liters}, 2)) .arg(tr("Starting Gravity")) - .arg(Measurement::displayAmount(Measurement::Amount{recObs->og(), Measurement::Units::specificGravity}, 3)); + .arg(Measurement::displayAmount(Measurement::Amount{m_recObs->og(), Measurement::Units::specificGravity}, 3)); // fifth row: IBU and Final gravity body += QString("%1%2%3%4") .arg(tr("IBU")) - .arg( Measurement::displayQuantity(recObs->IBU(), 1)) + .arg( Measurement::displayQuantity(m_recObs->IBU(), 1)) .arg(tr("Final Gravity")) - .arg(Measurement::displayAmount(Measurement::Amount{recObs->fg(), Measurement::Units::specificGravity}, 3)); + .arg(Measurement::displayAmount(Measurement::Amount{m_recObs->fg(), Measurement::Units::specificGravity}, 3)); // sixth row: ABV and estimate calories bool metricVolume = ( @@ -399,9 +421,9 @@ QString BrewDayScrollWidget::buildTitleTable(bool includeImage) { ); body += QString("%1%2%%3%4") .arg(tr("ABV")) - .arg(Measurement::displayQuantity(recObs->ABV_pct(), 1) ) + .arg(Measurement::displayQuantity(m_recObs->ABV_pct(), 1) ) .arg(metricVolume ? tr("Estimated calories (per 33 cl)") : tr("Estimated calories (per 12 oz)")) - .arg(Measurement::displayQuantity(metricVolume ? this->recObs->caloriesPer33cl() : this->recObs->caloriesPerUs12oz(), 0) ); + .arg(Measurement::displayQuantity(metricVolume ? this->m_recObs->caloriesPer33cl() : this->m_recObs->caloriesPerUs12oz(), 0) ); body += ""; @@ -416,12 +438,12 @@ QString BrewDayScrollWidget::buildInstructionTable() { .arg(tr("Time")) .arg(tr("Step")); - QList instructions = this->recObs->instructions(); - auto mashSteps = this->recObs->mash()->mashSteps(); + auto instructions = this->m_recObs->steps(); + auto mashSteps = this->m_recObs->mash()->mashSteps(); int size = instructions.size(); for (int i = 0; i < size; ++i ) { - Instruction* ins = instructions[i]; + auto ins = instructions[i]; QString stepTime; if (ins->interval() > 0.0 ) { @@ -436,9 +458,9 @@ QString BrewDayScrollWidget::buildInstructionTable() { // doesn't work in other languages. Find a better way. QList reagents; if ( ins->name() == tr("Add grains") ) { - reagents = this->recObs->getReagents( this->recObs->fermentableAdditions() ); + reagents = this->m_recObs->getReagents( this->m_recObs->fermentableAdditions() ); } else if ( ins->name() == tr("Heat water") ) { - reagents = this->recObs->getReagents( this->recObs->mash()->mashSteps() ); + reagents = this->m_recObs->getReagents( this->m_recObs->mash()->mashSteps() ); } else { reagents = ins->reagents(); } diff --git a/src/BrewDayScrollWidget.h b/src/BrewDayScrollWidget.h index 1a61f0a76..64acb3e11 100644 --- a/src/BrewDayScrollWidget.h +++ b/src/BrewDayScrollWidget.h @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * BrewDayScrollWidget.h is part of Brewtarget, and is copyright the following authors 2009-2022: + * BrewDayScrollWidget.h is part of Brewtarget, and is copyright the following authors 2009-2024: * • Jeff Bailey * • Mark de Wever * • Matt Young @@ -92,14 +92,14 @@ private slots: QString buildInstructionTable(); QString buildFooterTable(); - Recipe* recObs; - QPrinter* printer; - QTextBrowser* doc; + Recipe * m_recObs; + QPrinter * m_printer; + QTextBrowser * m_doc; //! Internal list of recipe instructions, always sorted by instruction number. - QList recIns; + QList> m_recIns; - QString cssName; + QString m_cssName; private slots: bool loadComplete(bool ok); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 00333498d..d51da5a58 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -60,7 +60,6 @@ set(filesToCompile_cpp ${repoDir}/src/Html.cpp ${repoDir}/src/HydrometerTool.cpp ${repoDir}/src/IbuGuSlider.cpp - ${repoDir}/src/InstructionWidget.cpp ${repoDir}/src/InventoryFormatter.cpp ${repoDir}/src/Localization.cpp ${repoDir}/src/Logging.cpp diff --git a/src/InstructionWidget.cpp b/src/InstructionWidget.cpp deleted file mode 100644 index d299a7696..000000000 --- a/src/InstructionWidget.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * InstructionWidget.cpp is part of Brewtarget, and is copyright the following authors 2009-2022: - * • Aidan Roberts - * • Brian Rower - * • Matt Young - * • Mik Firestone - * • Philip Greggory Lee - * - * Brewtarget 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. - * - * Brewtarget is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with this program. If not, see - * . - ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌*/ -#include "InstructionWidget.h" - -#include "model/Instruction.h" -#include "TimerWidget.h" - -InstructionWidget::InstructionWidget(QWidget* parent) : - QWidget(parent), insObs(0) -{ - setupUi(this); - timer->setVisible(false); - - connect( checkBox_completed, &QCheckBox::stateChanged, this, &InstructionWidget::setCompleted ); - connect( timer, SIGNAL(timerSet(QString)), this, SLOT(setTimerValue(QString)) ); - connect( textEdit, &QTextEdit::textChanged, this, &InstructionWidget::setDirections ); -} - -InstructionWidget::~InstructionWidget() -{ -} - -QSize InstructionWidget::sizeHint() const -{ - return QSize(0,0); -} - -void InstructionWidget::setInstruction(Instruction* ins) -{ - if( insObs ) - disconnect( insObs, 0, this, 0 ); - - insObs = ins; - if( insObs ) - connect( insObs, SIGNAL(changed(QMetaProperty,QVariant)), this, SLOT(changed(QMetaProperty,QVariant)) ); - - showChanges(); -} - -void InstructionWidget::showChanges() -{ - if( insObs == 0 ) - return; - - textEdit->setPlainText(insObs->directions()); - checkBox_showTimer->setCheckState( insObs->hasTimer() ? Qt::Checked : Qt::Unchecked ); - checkBox_completed->setCheckState( insObs->completed() ? Qt::Checked : Qt::Unchecked ); -} - -void InstructionWidget::changed([[maybe_unused]] QMetaProperty prop, - [[maybe_unused]] QVariant val) { - if (sender() != insObs) { - return; - } - - showChanges(); -} - -void InstructionWidget::setCompleted() -{ - if( insObs == 0 ) - return; - - bool completed = (checkBox_completed->checkState() == Qt::Checked)? true : false; - insObs->setCompleted( completed ); - - // Want to inactivate certain things sometimes. - if( completed ) - { - // Gray out everything except checkBox_completed. - textEdit->setEnabled(false); - checkBox_showTimer->setEnabled(false); - timer->setEnabled(false); - } - else - { - textEdit->setEnabled(true); - checkBox_showTimer->setEnabled(true); - timer->setEnabled(true); - } -} - -void InstructionWidget::setTimerValue(QString value) -{ - if( insObs == 0 ) - return; - - insObs->setTimerValue(value); -} - -void InstructionWidget::setDirections() -{ - if( insObs == 0 ) - return; - - insObs->setDirections(textEdit->toPlainText()); -} diff --git a/src/InstructionWidget.h b/src/InstructionWidget.h deleted file mode 100644 index b9142bc7a..000000000 --- a/src/InstructionWidget.h +++ /dev/null @@ -1,62 +0,0 @@ -/*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * InstructionWidget.h is part of Brewtarget, and is copyright the following authors 2009-2014: - * • Aidan Roberts - * • Brian Rower - * • Jeff Bailey - * • Philip Greggory Lee - * - * Brewtarget 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. - * - * Brewtarget 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 INSTRUCTIONWIDGET_H -#define INSTRUCTIONWIDGET_H -#pragma once - -#include "ui_instructionWidget.h" -#include -#include -#include -#include - -// Forward declarations. -class TimerWidget; -class Instruction; - -/*! - * \class InstructionWidget - * - * \brief View/controller widget that views/edits recipe instructions. - */ -class InstructionWidget : public QWidget, public Ui::instructionWidget { - Q_OBJECT -public: - InstructionWidget(QWidget* parent=0); - virtual ~InstructionWidget(); - //! View/edit the given instruction. - void setInstruction(Instruction* ins); - - virtual QSize sizeHint() const; // From QWidget - -public slots: - void setDirections(); - void setTimerValue(QString value); - void setCompleted(); - - void changed(QMetaProperty,QVariant); -private: - void showChanges(); - void makeEverythingInactive(); - - Instruction* insObs; - TimerWidget* timer; -}; - -#endif diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 731896be5..12eb27e94 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1972,7 +1972,7 @@ void MainWindow::showChanges(QMetaProperty* prop) { if (this->pimpl->m_recipeObs->boil() && (updateAll || propName == PropertyNames::Recipe::boil || - propName == PropertyNames::Boil::boilSteps)) { + propName == PropertyNames::SteppedOwnerBase::steps)) { this->pimpl->m_boilStepTableModel->setBoil(this->pimpl->m_recipeObs->boil()); } // See if we need to change the fermentation in the table. diff --git a/src/PrintAndPreviewDialog.cpp b/src/PrintAndPreviewDialog.cpp index 08a1dcb40..a6b17ea98 100644 --- a/src/PrintAndPreviewDialog.cpp +++ b/src/PrintAndPreviewDialog.cpp @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * PrintAndPreviewDialog.cpp is part of Brewtarget, and is copyright the following authors 2021-2022: + * PrintAndPreviewDialog.cpp is part of Brewtarget, and is copyright the following authors 2021-2024: * • Mattias Måhl * • Matt Young * @@ -146,7 +146,7 @@ void PrintAndPreviewDialog::collectSupportedPageSizes() { qDebug() << "generating a list of page sizes as there is no printer intalled on the system"; supportedPageSizeList = generatePageSizeList(); } - foreach(QPageSize pageSize, supportedPageSizeList) { + for (QPageSize pageSize : supportedPageSizeList) { PageSizeMap.insert(pageSize.name(), pageSize); comboBox_PaperFormatSelector->addItem(pageSize.name()); } diff --git a/src/RecipeFormatter.cpp b/src/RecipeFormatter.cpp index 86fd09583..ea6b31bdb 100644 --- a/src/RecipeFormatter.cpp +++ b/src/RecipeFormatter.cpp @@ -924,7 +924,7 @@ class RecipeFormatter::impl { return ""; } - QList instructions = this->rec->instructions(); + auto instructions = this->rec->steps(); int size = instructions.size(); if ( size < 1 ) { return ""; @@ -934,7 +934,7 @@ class RecipeFormatter::impl { itable += "
    "; for (int ii = 0; ii < size; ++ii) { - Instruction* ins = instructions[ii]; + auto ins = instructions[ii]; itable += QString("
  1. %1
  2. ").arg( ins->directions()); } @@ -952,12 +952,11 @@ class RecipeFormatter::impl { QStringList num, text; - QList instructions = rec->instructions(); + auto instructions = rec->steps(); int size = instructions.size(); if ( size > 0 ) { - Instruction* ins; for (int ii = 0; ii < size; ++ii) { - ins = instructions[ii]; + auto ins = instructions[ii]; num.append(QString("%1").arg(ii)); //Wrap instruction text to 75 ( 80 (text separator length) - 5 (num colunm lenght) ) text.append(QString("- %1").arg(wrapText(ins->directions(), 75))); @@ -989,14 +988,14 @@ class RecipeFormatter::impl { } QString bnTable = ""; - QList brewNotes = rec->brewNotes(); + auto const brewNotes = rec->brewNotes(); int size = brewNotes.size(); if ( size < 1 ) { return bnTable; } for(int ii = 0; ii < size; ++ii) { - BrewNote* note = brewNotes[ii]; + auto note = brewNotes[ii]; bnTable += QString("

    %1 %2

    ").arg(tr("Brew Date")).arg(note->brewDate_short()); @@ -1102,12 +1101,12 @@ QString RecipeFormatter::getHtmlFormat(QList recipes) { // build a toc -- why do I do this to myself? hDoc += "
      "; - foreach ( Recipe* foo, recipes ) { + for (auto foo : recipes) { hDoc += QString("
    • %1
    • ").arg(foo->name()); } hDoc += "
    "; - foreach (Recipe* foo, recipes) { + for (auto foo : recipes) { this->pimpl->rec = foo; hDoc += QString("").arg(foo->name()); hDoc += this->pimpl->buildStatTableHtml(); diff --git a/src/TimerListDialog.cpp b/src/TimerListDialog.cpp index 96c4882e0..2fa38ef08 100644 --- a/src/TimerListDialog.cpp +++ b/src/TimerListDialog.cpp @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * TimerListDialog.cpp is part of Brewtarget, and is copyright the following authors 2009-2022: + * TimerListDialog.cpp is part of Brewtarget, and is copyright the following authors 2009-2024: * • Aidan Roberts * • Matt Young * • Philip Greggory Lee @@ -22,44 +22,42 @@ #include "TimerWidget.h" TimerListDialog::TimerListDialog(QWidget* parent, QList* timers) : QDialog(parent) { - this->setWindowTitle(tr("Addition Timers")); - - QVBoxLayout* mainLayout = new QVBoxLayout(this); - this->setLayout(mainLayout); - - scrollArea = new QScrollArea(this); - mainLayout->addWidget(scrollArea); - scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - scrollArea->setWidgetResizable(true); - scrollWidget = new QWidget(scrollArea); - layout = new QVBoxLayout(scrollWidget); - scrollWidget->setLayout(layout); - scrollArea->setWidget(scrollWidget); - setTimers(timers); + this->setWindowTitle(tr("Addition Timers")); + + QVBoxLayout* mainLayout = new QVBoxLayout(this); + this->setLayout(mainLayout); + + scrollArea = new QScrollArea(this); + mainLayout->addWidget(scrollArea); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setWidgetResizable(true); + scrollWidget = new QWidget(scrollArea); + layout = new QVBoxLayout(scrollWidget); + scrollWidget->setLayout(layout); + scrollArea->setWidget(scrollWidget); + setTimers(timers); + return; } -TimerListDialog::~TimerListDialog() -{ - -} +TimerListDialog::~TimerListDialog() = default; -void TimerListDialog::setTimers(QList* timers) -{ - if (!timers->isEmpty()) { - foreach (TimerWidget* t, *timers) { - layout->addWidget(t); - } - } +void TimerListDialog::setTimers(QList* timers) { + if (!timers->isEmpty()) { + for (TimerWidget* t : *timers) { + layout->addWidget(t); + } + } + return; } -void TimerListDialog::setTimerVisible(TimerWidget *t) -{ - //Focus scrollArea on timer t - scrollArea->verticalScrollBar()->setValue(t->y()); +void TimerListDialog::setTimerVisible(TimerWidget *t) { + //Focus scrollArea on timer t + scrollArea->verticalScrollBar()->setValue(t->y()); + return; } -void TimerListDialog::hideTimers() -{ - this->hide(); +void TimerListDialog::hideTimers() { + this->hide(); + return; } diff --git a/src/WaterDialog.cpp b/src/WaterDialog.cpp index b8c3c8014..d75eab043 100644 --- a/src/WaterDialog.cpp +++ b/src/WaterDialog.cpp @@ -129,7 +129,6 @@ WaterDialog::WaterDialog(QWidget* parent) : m_total_digits[static_cast(Salt::Type::NaCl )] = btDigit_totalnacl ; m_total_digits[static_cast(Salt::Type::NaHCO3)] = btDigit_totalnahco3; - // foreach( SmartDigitWidget* i, m_ppm_digits ) { for (int ii = 0; ii < Water::ionStringMapping.size(); ++ii) { m_ppm_digits[ii]->setLimits(0.0,1000.0); m_ppm_digits[ii]->setQuantity(0.0); diff --git a/src/database/DatabaseSchemaHelper.cpp b/src/database/DatabaseSchemaHelper.cpp index 05f93c4ea..ff2e5fe34 100644 --- a/src/database/DatabaseSchemaHelper.cpp +++ b/src/database/DatabaseSchemaHelper.cpp @@ -36,7 +36,7 @@ #include "database/DbTransaction.h" #include "database/ObjectStoreTyped.h" -int constexpr DatabaseSchemaHelper::latestVersion = 13; +int constexpr DatabaseSchemaHelper::latestVersion = 14; // Default namespace hides functions from everything outside this file. namespace { @@ -2170,6 +2170,93 @@ namespace { return executeSqlQueries(q, migrationQueries); } + /** + * \brief Correct flouride to fluoride on water table + * Fix Instructions to be stored the same way as BrewNotes, RecipeAdditions etc + * // TODO: On next DB update, correct water.flouride_ppm to water.fluoride_ppm +. + */ + bool migrate_to_14([[maybe_unused]] Database & db, BtSqlQuery & q) { + // + // See below for why we create a new version of the instruction table here. While we're at it, we make the + // column names snake_case, and add units, to be more consistent with the rest of the DB schema. + // + // NOTE I am not convinced that has_timer, timer_value and completed columns are actually used + // + QString createNewInstructionsSql; + QTextStream createNewInstructionsSqlStream(&createNewInstructionsSql); + createNewInstructionsSqlStream << + "CREATE TABLE new_instruction (" + "id" " " << db.getDbNativePrimaryKeyDeclaration() << ", " + "name" " " << db.getDbNativeTypeName() << ", " + "display" " " << db.getDbNativeTypeName() << ", " + "deleted" " " << db.getDbNativeTypeName() << ", " + "recipe_id" " " << db.getDbNativeTypeName() << ", " + "step_number" " " << db.getDbNativeTypeName() << ", " + "directions" " " << db.getDbNativeTypeName() << ", " + "has_timer" " " << db.getDbNativeTypeName() << ", " + "timer_value" " " << db.getDbNativeTypeName() << ", " + "completed" " " << db.getDbNativeTypeName() << ", " + "interval_mins" " " << db.getDbNativeTypeName() << ", " + "FOREIGN KEY(recipe_id) REFERENCES recipe(id) " + ");"; + + QVector const migrationQueries{ + // + // This is a naming error in BeerJSON that we copied-and-pasted into the code. BeerJSON will get a fix at some + // point -- see https://github.com/beerjson/beerjson/issues/214. But we can fix our DB column name in the + // meantime. + // + {QString("ALTER TABLE water RENAME COLUMN flouride_ppm TO fluoride_ppm")}, + // + // The existence of the instruction_in_recipe table implies that Instruction objects can be shared between + // multiple Recipe objects. However, since they are only managed inside each Recipe, and are often + // automatically generated, it doesn't make a whole lot of sense for an Instruction to have a separate + // existence outside of Recipe. So we try to make them more like BrewNotes here. + // + // At the risk of being overly cautious, we assume it is at least conceivable there could be some user + // databases where instructions are shared between recipes (even though this should not happen). To avoid any + // potential data loss, we therefore allow for the possibility that we might need to create copies of existing + // "shared" instructions (since, after the schema update it will be impossible for one instruction row to + // related to more than one recipe row). This being the case, it seems simpler to create a new table rather + // than fill in the blanks on the existing one. + // + {createNewInstructionsSql}, + {QString("INSERT INTO new_instruction ( " + "name , " + "display , " + "deleted , " + "recipe_id , " + "step_number, " + "directions , " + "has_timer , " + "timer_value, " + "completed , " + "interval_mins " + ") " + "SELECT ii.name , " + "ii.display , " + "ii.deleted , " + "jj.recipe_id , " + "jj.instruction_number, " + "ii.directions , " + "ii.hasTimer , " // NB: hasTimer -> has_timer + "ii.timerValue , " // NB: timerValue -> timer_value + "ii.completed , " + "ii.interval " // NB: interval -> interval_mins + "FROM instruction AS ii, " + "instruction_in_recipe AS jj " + "WHERE jj.instruction_id = ii.id")}, + // Now we've copied all the data to the new table, we can safely throw the old tables away + {QString("DROP TABLE instruction_in_recipe")}, + {QString("DROP TABLE instruction")}, + // And finally we can rename the new table replace the old one + {QString("ALTER TABLE new_instruction RENAME TO instruction")}, + + }; + return executeSqlQueries(q, migrationQueries); + } + /*! * \brief Migrate from version \c oldVersion to \c oldVersion+1 */ @@ -2216,7 +2303,9 @@ namespace { case 12: ret &= migrate_to_13(database, sqlQuery); break; - // TODO: On next DB update, correct water.flouride_ppm to water.fluoride_ppm + case 13: + ret &= migrate_to_14(database, sqlQuery); + break; default: qCritical() << QString("Unknown version %1").arg(oldVersion); return false; diff --git a/src/database/ObjectStore.cpp b/src/database/ObjectStore.cpp index 6aa812c6e..7a4aad0ef 100644 --- a/src/database/ObjectStore.cpp +++ b/src/database/ObjectStore.cpp @@ -1063,6 +1063,8 @@ class ObjectStore::impl { BtSqlQuery sqlQuery{connection}; sqlQuery.prepare(queryString); QVariant propertyBindValue{object.property(*propertyName)}; + // It's a coding error if the property we are trying to read from does not exist + Q_ASSERT(propertyBindValue.isValid()); auto fieldDefn = std::find_if( this->primaryTable.tableFields.begin(), this->primaryTable.tableFields.end(), @@ -1195,6 +1197,11 @@ class ObjectStore::impl { auto const & fieldDefn = this->primaryTable.tableFields[ii]; QVariant bindValue{object.property(*fieldDefn.propertyName)}; + // Uncomment the following line if the assert below is firing + qDebug() << Q_FUNC_INFO << fieldDefn.propertyName << ":" << bindValue; + + // It's a coding error if the property we are trying to read from does not exist + Q_ASSERT(bindValue.isValid()); // Fix-up the QVariant if needed, including converting enums to strings this->unwrapAndMapAsNeeded(this->primaryTable, fieldDefn, bindValue); diff --git a/src/database/ObjectStoreTyped.cpp b/src/database/ObjectStoreTyped.cpp index 9341792fc..c894e9623 100644 --- a/src/database/ObjectStoreTyped.cpp +++ b/src/database/ObjectStoreTyped.cpp @@ -88,7 +88,7 @@ namespace { {ObjectStore::FieldType::String, "name" , PropertyNames::NamedEntity::name }, {ObjectStore::FieldType::Bool , "display" , PropertyNames::NamedEntity::display }, {ObjectStore::FieldType::Bool , "deleted" , PropertyNames::NamedEntity::deleted }, - {ObjectStore::FieldType::String, "folder" , PropertyNames::FolderBase::folder }, + {ObjectStore::FieldType::String, "folder" , PropertyNames::FolderBase::folder }, {ObjectStore::FieldType::Double, "fermenter_batch_size_l" , PropertyNames::Equipment::fermenterBatchSize_l }, {ObjectStore::FieldType::Double, "boiling_point" , PropertyNames::Equipment::boilingPoint_c }, {ObjectStore::FieldType::Double, "kettle_boil_size_l" , PropertyNames::Equipment::kettleBoilSize_l }, @@ -159,44 +159,44 @@ namespace { template<> ObjectStore::TableDefinition const PRIMARY_TABLE { "fermentable", { - {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, - {ObjectStore::FieldType::String, "name" , PropertyNames::NamedEntity::name }, - {ObjectStore::FieldType::Bool , "deleted" , PropertyNames::NamedEntity::deleted }, - {ObjectStore::FieldType::Bool , "display" , PropertyNames::NamedEntity::display }, - {ObjectStore::FieldType::String, "folder" , PropertyNames::FolderBase::folder }, - {ObjectStore::FieldType::Double, "coarse_fine_diff" , PropertyNames::Fermentable::coarseFineDiff_pct }, - {ObjectStore::FieldType::Double, "color" , PropertyNames::Fermentable::color_srm }, - {ObjectStore::FieldType::Double, "diastatic_power" , PropertyNames::Fermentable::diastaticPower_lintner }, - {ObjectStore::FieldType::Enum , "ftype" , PropertyNames::Fermentable::type , &Fermentable::typeStringMapping}, - {ObjectStore::FieldType::Double, "ibu_gal_per_lb" , PropertyNames::Fermentable::ibuGalPerLb }, - {ObjectStore::FieldType::Double, "max_in_batch" , PropertyNames::Fermentable::maxInBatch_pct }, - {ObjectStore::FieldType::Double, "moisture" , PropertyNames::Fermentable::moisture_pct }, - {ObjectStore::FieldType::String, "notes" , PropertyNames::Fermentable::notes }, - {ObjectStore::FieldType::String, "origin" , PropertyNames::Fermentable::origin }, - {ObjectStore::FieldType::String, "supplier" , PropertyNames::Fermentable::supplier }, - {ObjectStore::FieldType::Double, "protein" , PropertyNames::Fermentable::protein_pct }, - {ObjectStore::FieldType::Bool , "recommend_mash" , PropertyNames::Fermentable::recommendMash }, + {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, + {ObjectStore::FieldType::String, "name" , PropertyNames::NamedEntity::name }, + {ObjectStore::FieldType::Bool , "deleted" , PropertyNames::NamedEntity::deleted }, + {ObjectStore::FieldType::Bool , "display" , PropertyNames::NamedEntity::display }, + {ObjectStore::FieldType::String, "folder" , PropertyNames::FolderBase::folder }, + {ObjectStore::FieldType::Double, "coarse_fine_diff" , PropertyNames::Fermentable::coarseFineDiff_pct }, + {ObjectStore::FieldType::Double, "color" , PropertyNames::Fermentable::color_srm }, + {ObjectStore::FieldType::Double, "diastatic_power" , PropertyNames::Fermentable::diastaticPower_lintner}, + {ObjectStore::FieldType::Enum , "ftype" , PropertyNames::Fermentable::type , &Fermentable::typeStringMapping}, + {ObjectStore::FieldType::Double, "ibu_gal_per_lb" , PropertyNames::Fermentable::ibuGalPerLb }, + {ObjectStore::FieldType::Double, "max_in_batch" , PropertyNames::Fermentable::maxInBatch_pct }, + {ObjectStore::FieldType::Double, "moisture" , PropertyNames::Fermentable::moisture_pct }, + {ObjectStore::FieldType::String, "notes" , PropertyNames::Fermentable::notes }, + {ObjectStore::FieldType::String, "origin" , PropertyNames::Fermentable::origin }, + {ObjectStore::FieldType::String, "supplier" , PropertyNames::Fermentable::supplier }, + {ObjectStore::FieldType::Double, "protein" , PropertyNames::Fermentable::protein_pct }, + {ObjectStore::FieldType::Bool , "recommend_mash" , PropertyNames::Fermentable::recommendMash }, // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - {ObjectStore::FieldType::Enum , "grain_group" , PropertyNames::Fermentable::grainGroup , &Fermentable::grainGroupStringMapping}, - {ObjectStore::FieldType::String, "producer" , PropertyNames::Fermentable::producer }, - {ObjectStore::FieldType::String, "product_id" , PropertyNames::Fermentable::productId }, - {ObjectStore::FieldType::Double, "fine_grind_yield_pct" , PropertyNames::Fermentable::fineGrindYield_pct }, // Replaces yield / yield_pct - {ObjectStore::FieldType::Double, "coarse_grind_yield_pct" , PropertyNames::Fermentable::coarseGrindYield_pct }, - {ObjectStore::FieldType::Double, "potential_yield_sg" , PropertyNames::Fermentable::potentialYield_sg }, - {ObjectStore::FieldType::Double, "alpha_amylase_dext_units" , PropertyNames::Fermentable::alphaAmylase_dextUnits }, - {ObjectStore::FieldType::Double, "kolbach_index_pct" , PropertyNames::Fermentable::kolbachIndex_pct }, - {ObjectStore::FieldType::Double, "hardness_prp_glassy_pct" , PropertyNames::Fermentable::hardnessPrpGlassy_pct }, - {ObjectStore::FieldType::Double, "hardness_prp_half_pct" , PropertyNames::Fermentable::hardnessPrpHalf_pct }, - {ObjectStore::FieldType::Double, "hardness_prp_mealy_pct" , PropertyNames::Fermentable::hardnessPrpMealy_pct }, - {ObjectStore::FieldType::Double, "kernel_size_prp_plump_pct" , PropertyNames::Fermentable::kernelSizePrpPlump_pct }, - {ObjectStore::FieldType::Double, "kernel_size_prp_thin_pct" , PropertyNames::Fermentable::kernelSizePrpThin_pct }, - {ObjectStore::FieldType::Double, "friability_pct" , PropertyNames::Fermentable::friability_pct }, - {ObjectStore::FieldType::Double, "di_ph" , PropertyNames::Fermentable::di_ph }, - {ObjectStore::FieldType::Double, "viscosity_cp" , PropertyNames::Fermentable::viscosity_cP }, - {ObjectStore::FieldType::Double, "dmsp_ppm" , PropertyNames::Fermentable::dmsP_ppm }, - {ObjectStore::FieldType::Double, "fan_ppm" , PropertyNames::Fermentable::fan_ppm }, - {ObjectStore::FieldType::Double, "fermentability_pct" , PropertyNames::Fermentable::fermentability_pct }, - {ObjectStore::FieldType::Double, "beta_glucan_ppm" , PropertyNames::Fermentable::betaGlucan_ppm }, + {ObjectStore::FieldType::Enum , "grain_group" , PropertyNames::Fermentable::grainGroup , &Fermentable::grainGroupStringMapping}, + {ObjectStore::FieldType::String, "producer" , PropertyNames::Fermentable::producer }, + {ObjectStore::FieldType::String, "product_id" , PropertyNames::Fermentable::productId }, + {ObjectStore::FieldType::Double, "fine_grind_yield_pct" , PropertyNames::Fermentable::fineGrindYield_pct }, // Replaces yield / yield_pct + {ObjectStore::FieldType::Double, "coarse_grind_yield_pct" , PropertyNames::Fermentable::coarseGrindYield_pct }, + {ObjectStore::FieldType::Double, "potential_yield_sg" , PropertyNames::Fermentable::potentialYield_sg }, + {ObjectStore::FieldType::Double, "alpha_amylase_dext_units" , PropertyNames::Fermentable::alphaAmylase_dextUnits}, + {ObjectStore::FieldType::Double, "kolbach_index_pct" , PropertyNames::Fermentable::kolbachIndex_pct }, + {ObjectStore::FieldType::Double, "hardness_prp_glassy_pct" , PropertyNames::Fermentable::hardnessPrpGlassy_pct }, + {ObjectStore::FieldType::Double, "hardness_prp_half_pct" , PropertyNames::Fermentable::hardnessPrpHalf_pct }, + {ObjectStore::FieldType::Double, "hardness_prp_mealy_pct" , PropertyNames::Fermentable::hardnessPrpMealy_pct }, + {ObjectStore::FieldType::Double, "kernel_size_prp_plump_pct", PropertyNames::Fermentable::kernelSizePrpPlump_pct}, + {ObjectStore::FieldType::Double, "kernel_size_prp_thin_pct" , PropertyNames::Fermentable::kernelSizePrpThin_pct }, + {ObjectStore::FieldType::Double, "friability_pct" , PropertyNames::Fermentable::friability_pct }, + {ObjectStore::FieldType::Double, "di_ph" , PropertyNames::Fermentable::di_ph }, + {ObjectStore::FieldType::Double, "viscosity_cp" , PropertyNames::Fermentable::viscosity_cP }, + {ObjectStore::FieldType::Double, "dmsp_ppm" , PropertyNames::Fermentable::dmsP_ppm }, + {ObjectStore::FieldType::Double, "fan_ppm" , PropertyNames::Fermentable::fan_ppm }, + {ObjectStore::FieldType::Double, "fermentability_pct" , PropertyNames::Fermentable::fermentability_pct }, + {ObjectStore::FieldType::Double, "beta_glucan_ppm" , PropertyNames::Fermentable::betaGlucan_ppm }, } }; @@ -260,7 +260,7 @@ namespace { template<> ObjectStore::TableDefinition const PRIMARY_TABLE { "hop_in_inventory", { - {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, + {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, {ObjectStore::FieldType::Int , "hop_id" , PropertyNames::Inventory::ingredientId , &PRIMARY_TABLE}, {ObjectStore::FieldType::Double, "quantity", PropertyNames::IngredientAmount::quantity}, {ObjectStore::FieldType::Unit , "unit" , PropertyNames::IngredientAmount::unit , &Measurement::Units::unitStringMapping}, @@ -268,27 +268,6 @@ namespace { }; template<> ObjectStore::JunctionTableDefinitions const JUNCTION_TABLES {}; - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Database field mappings for Instruction - // NB: instructions aren't displayed in trees, and get no folder - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - template<> ObjectStore::TableDefinition const PRIMARY_TABLE { - "instruction", - { - {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, - {ObjectStore::FieldType::String, "name" , PropertyNames::NamedEntity::name }, - {ObjectStore::FieldType::Bool , "display" , PropertyNames::NamedEntity::display }, - {ObjectStore::FieldType::Bool , "deleted" , PropertyNames::NamedEntity::deleted }, - {ObjectStore::FieldType::String, "directions", PropertyNames::Instruction::directions}, - {ObjectStore::FieldType::Bool , "hasTimer" , PropertyNames::Instruction::hasTimer }, - {ObjectStore::FieldType::String, "timervalue", PropertyNames::Instruction::timerValue}, - {ObjectStore::FieldType::Bool , "completed" , PropertyNames::Instruction::completed }, - {ObjectStore::FieldType::Double, "interval" , PropertyNames::Instruction::interval }, - } - }; - // Instructions don't have children - template<> ObjectStore::JunctionTableDefinitions const JUNCTION_TABLES {}; - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Database field mappings for Mash /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -299,7 +278,7 @@ namespace { {ObjectStore::FieldType::String, "name" , PropertyNames::NamedEntity::name }, {ObjectStore::FieldType::Bool , "deleted" , PropertyNames::NamedEntity::deleted }, {ObjectStore::FieldType::Bool , "display" , PropertyNames::NamedEntity::display }, - {ObjectStore::FieldType::String, "folder" , PropertyNames::FolderBase::folder }, + {ObjectStore::FieldType::String, "folder" , PropertyNames::FolderBase::folder }, {ObjectStore::FieldType::Bool , "equip_adjust" , PropertyNames::Mash::equipAdjust }, {ObjectStore::FieldType::Double, "grain_temp" , PropertyNames::Mash::grainTemp_c }, {ObjectStore::FieldType::String, "notes" , PropertyNames::Mash::notes }, @@ -320,27 +299,27 @@ namespace { template<> ObjectStore::TableDefinition const PRIMARY_TABLE { "mash_step", { - {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, - {ObjectStore::FieldType::String, "name" , PropertyNames::NamedEntity::name }, - {ObjectStore::FieldType::Bool , "deleted" , PropertyNames::NamedEntity::deleted }, - {ObjectStore::FieldType::Bool , "display" , PropertyNames::NamedEntity::display }, + {ObjectStore::FieldType::Int , "id" , PropertyNames:: NamedEntity::key }, + {ObjectStore::FieldType::String, "name" , PropertyNames:: NamedEntity::name }, + {ObjectStore::FieldType::Bool , "deleted" , PropertyNames:: NamedEntity::deleted }, + {ObjectStore::FieldType::Bool , "display" , PropertyNames:: NamedEntity::display }, // NB: MashSteps don't have folders, as each one is owned by a Mash - {ObjectStore::FieldType::Double, "end_temp_c" , PropertyNames:: Step::endTemp_c }, - {ObjectStore::FieldType::Double, "infuse_temp_c" , PropertyNames::MashStep::infuseTemp_c }, - {ObjectStore::FieldType::Int , "mash_id" , PropertyNames:: Step::ownerId , &PRIMARY_TABLE}, - {ObjectStore::FieldType::Enum , "mstype" , PropertyNames::MashStep::type , &MashStep::typeStringMapping}, - {ObjectStore::FieldType::Double, "ramp_time_mins" , PropertyNames:: Step::rampTime_mins }, - {ObjectStore::FieldType::Int , "step_number" , PropertyNames:: Step::stepNumber }, - {ObjectStore::FieldType::Double, "step_temp_c" , PropertyNames:: Step::startTemp_c }, - {ObjectStore::FieldType::Double, "step_time_mins" , PropertyNames:: Step::stepTime_mins }, + {ObjectStore::FieldType::Double, "end_temp_c" , PropertyNames:: Step::endTemp_c }, + {ObjectStore::FieldType::Double, "infuse_temp_c" , PropertyNames:: MashStep::infuseTemp_c }, + {ObjectStore::FieldType::Int , "mash_id" , PropertyNames::SteppedBase::ownerId , &PRIMARY_TABLE}, + {ObjectStore::FieldType::Enum , "mstype" , PropertyNames:: MashStep::type , &MashStep::typeStringMapping}, + {ObjectStore::FieldType::Double, "ramp_time_mins" , PropertyNames:: StepBase::rampTime_mins }, + {ObjectStore::FieldType::Int , "step_number" , PropertyNames::SteppedBase::stepNumber }, + {ObjectStore::FieldType::Double, "step_temp_c" , PropertyNames:: StepBase::startTemp_c }, + {ObjectStore::FieldType::Double, "step_time_mins" , PropertyNames:: StepBase::stepTime_mins }, // Now we support BeerJSON, amount_l unifies and replaces infuseAmount_l and decoctionAmount_l // See comment in model/MashStep.h for more info - {ObjectStore::FieldType::Double, "amount_l" , PropertyNames::MashStep::amount_l }, + {ObjectStore::FieldType::Double, "amount_l" , PropertyNames:: MashStep::amount_l }, // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - {ObjectStore::FieldType::String, "description" , PropertyNames:: Step::description }, - {ObjectStore::FieldType::Double, "liquor_to_grist_ratio_lkg", PropertyNames::MashStep::liquorToGristRatio_lKg}, - {ObjectStore::FieldType::Double, "start_acidity_ph" , PropertyNames:: Step::startAcidity_pH }, - {ObjectStore::FieldType::Double, "end_acidity_ph" , PropertyNames:: Step::endAcidity_pH }, + {ObjectStore::FieldType::String, "description" , PropertyNames:: Step::description }, + {ObjectStore::FieldType::Double, "liquor_to_grist_ratio_lkg", PropertyNames:: MashStep::liquorToGristRatio_lKg}, + {ObjectStore::FieldType::Double, "start_acidity_ph" , PropertyNames:: Step::startAcidity_pH }, + {ObjectStore::FieldType::Double, "end_acidity_ph" , PropertyNames:: Step::endAcidity_pH }, } }; // MashSteps don't have children @@ -372,23 +351,23 @@ namespace { template<> ObjectStore::TableDefinition const PRIMARY_TABLE { "boil_step", { - {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, - {ObjectStore::FieldType::String, "name" , PropertyNames::NamedEntity::name }, - {ObjectStore::FieldType::Bool , "deleted" , PropertyNames::NamedEntity::deleted }, - {ObjectStore::FieldType::Bool , "display" , PropertyNames::NamedEntity::display }, + {ObjectStore::FieldType::Int , "id" , PropertyNames:: NamedEntity::key }, + {ObjectStore::FieldType::String, "name" , PropertyNames:: NamedEntity::name }, + {ObjectStore::FieldType::Bool , "deleted" , PropertyNames:: NamedEntity::deleted }, + {ObjectStore::FieldType::Bool , "display" , PropertyNames:: NamedEntity::display }, // NB: BoilSteps don't have folders, as each one is owned by a Boil - {ObjectStore::FieldType::Double, "step_time_mins" , PropertyNames::Step::stepTime_mins }, - {ObjectStore::FieldType::Double, "start_temp_c" , PropertyNames::Step::startTemp_c }, - {ObjectStore::FieldType::Double, "end_temp_c" , PropertyNames::Step::endTemp_c }, - {ObjectStore::FieldType::Double, "ramp_time_mins" , PropertyNames::Step::rampTime_mins }, - {ObjectStore::FieldType::Int , "step_number" , PropertyNames::Step::stepNumber }, - {ObjectStore::FieldType::Int , "boil_id" , PropertyNames::Step::ownerId , &PRIMARY_TABLE}, - {ObjectStore::FieldType::String, "description" , PropertyNames::Step::description }, - {ObjectStore::FieldType::Double, "start_acidity_ph", PropertyNames::Step::startAcidity_pH }, - {ObjectStore::FieldType::Double, "end_acidity_ph" , PropertyNames::Step::endAcidity_pH }, + {ObjectStore::FieldType::Double, "step_time_mins" , PropertyNames:: StepBase::stepTime_mins }, + {ObjectStore::FieldType::Double, "start_temp_c" , PropertyNames:: StepBase::startTemp_c }, + {ObjectStore::FieldType::Double, "end_temp_c" , PropertyNames:: Step::endTemp_c }, + {ObjectStore::FieldType::Double, "ramp_time_mins" , PropertyNames:: StepBase::rampTime_mins }, + {ObjectStore::FieldType::Int , "step_number" , PropertyNames:: SteppedBase::stepNumber }, + {ObjectStore::FieldType::Int , "boil_id" , PropertyNames:: SteppedBase::ownerId , &PRIMARY_TABLE}, + {ObjectStore::FieldType::String, "description" , PropertyNames:: Step::description }, + {ObjectStore::FieldType::Double, "start_acidity_ph", PropertyNames:: Step::startAcidity_pH}, + {ObjectStore::FieldType::Double, "end_acidity_ph" , PropertyNames:: Step::endAcidity_pH }, {ObjectStore::FieldType::Double, "start_gravity_sg", PropertyNames::StepExtended::startGravity_sg}, {ObjectStore::FieldType::Double, "end_gravity_sg" , PropertyNames::StepExtended:: endGravity_sg}, - {ObjectStore::FieldType::Enum , "chilling_type" , PropertyNames::BoilStep::chillingType , &BoilStep::chillingTypeStringMapping}, + {ObjectStore::FieldType::Enum , "chilling_type" , PropertyNames:: BoilStep::chillingType , &BoilStep::chillingTypeStringMapping}, } }; // BoilSteps don't have children @@ -400,13 +379,13 @@ namespace { template<> ObjectStore::TableDefinition const PRIMARY_TABLE { "fermentation", { - {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, - {ObjectStore::FieldType::String, "name" , PropertyNames::NamedEntity::name }, - {ObjectStore::FieldType::Bool , "deleted" , PropertyNames::NamedEntity::deleted }, - {ObjectStore::FieldType::Bool , "display" , PropertyNames::NamedEntity::display }, - {ObjectStore::FieldType::String, "folder" , PropertyNames::FolderBase::folder }, - {ObjectStore::FieldType::String, "description" , PropertyNames::Fermentation::description }, - {ObjectStore::FieldType::String, "notes" , PropertyNames::Fermentation::notes }, + {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, + {ObjectStore::FieldType::String, "name" , PropertyNames::NamedEntity::name }, + {ObjectStore::FieldType::Bool , "deleted" , PropertyNames::NamedEntity::deleted }, + {ObjectStore::FieldType::Bool , "display" , PropertyNames::NamedEntity::display }, + {ObjectStore::FieldType::String, "folder" , PropertyNames::FolderBase::folder }, + {ObjectStore::FieldType::String, "description" , PropertyNames::Fermentation::description}, + {ObjectStore::FieldType::String, "notes" , PropertyNames::Fermentation::notes }, } }; // Fermentations don't have children, and the link with their FermentationSteps is stored in the FermentationStep (as between Recipe and BrewNotes) @@ -423,23 +402,23 @@ namespace { template<> ObjectStore::TableDefinition const PRIMARY_TABLE { "fermentation_step", { - {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, - {ObjectStore::FieldType::String, "name" , PropertyNames::NamedEntity::name }, - {ObjectStore::FieldType::Bool , "deleted" , PropertyNames::NamedEntity::deleted }, - {ObjectStore::FieldType::Bool , "display" , PropertyNames::NamedEntity::display }, + {ObjectStore::FieldType::Int , "id" , PropertyNames:: NamedEntity::key }, + {ObjectStore::FieldType::String, "name" , PropertyNames:: NamedEntity::name }, + {ObjectStore::FieldType::Bool , "deleted" , PropertyNames:: NamedEntity::deleted }, + {ObjectStore::FieldType::Bool , "display" , PropertyNames:: NamedEntity::display }, // NB: FermentationSteps don't have folders, as each one is owned by a Fermentation - {ObjectStore::FieldType::Double, "step_time_mins" , PropertyNames::Step::stepTime_mins }, - {ObjectStore::FieldType::Double, "start_temp_c" , PropertyNames::Step::startTemp_c }, - {ObjectStore::FieldType::Double, "end_temp_c" , PropertyNames::Step::endTemp_c }, - {ObjectStore::FieldType::Int , "step_number" , PropertyNames::Step::stepNumber }, - {ObjectStore::FieldType::Int , "fermentation_id" , PropertyNames::Step::ownerId , &PRIMARY_TABLE}, - {ObjectStore::FieldType::String, "description" , PropertyNames::Step::description }, - {ObjectStore::FieldType::Double, "start_acidity_ph", PropertyNames::Step::startAcidity_pH }, - {ObjectStore::FieldType::Double, "end_acidity_ph" , PropertyNames::Step:: endAcidity_pH }, - {ObjectStore::FieldType::Double, "start_gravity_sg", PropertyNames::StepExtended::startGravity_sg}, - {ObjectStore::FieldType::Double, "end_gravity_sg" , PropertyNames::StepExtended:: endGravity_sg}, - {ObjectStore::FieldType::Bool , "free_rise" , PropertyNames::FermentationStep::freeRise }, - {ObjectStore::FieldType::String, "vessel" , PropertyNames::FermentationStep::vessel }, + {ObjectStore::FieldType::Double, "step_time_mins" , PropertyNames:: StepBase::stepTime_mins }, + {ObjectStore::FieldType::Double, "start_temp_c" , PropertyNames:: StepBase::startTemp_c }, + {ObjectStore::FieldType::Double, "end_temp_c" , PropertyNames:: Step::endTemp_c }, + {ObjectStore::FieldType::Int , "step_number" , PropertyNames:: SteppedBase::stepNumber }, + {ObjectStore::FieldType::Int , "fermentation_id" , PropertyNames:: SteppedBase::ownerId , &PRIMARY_TABLE}, + {ObjectStore::FieldType::String, "description" , PropertyNames:: Step::description }, + {ObjectStore::FieldType::Double, "start_acidity_ph", PropertyNames:: Step::startAcidity_pH}, + {ObjectStore::FieldType::Double, "end_acidity_ph" , PropertyNames:: Step:: endAcidity_pH}, + {ObjectStore::FieldType::Double, "start_gravity_sg", PropertyNames:: StepExtended::startGravity_sg}, + {ObjectStore::FieldType::Double, "end_gravity_sg" , PropertyNames:: StepExtended:: endGravity_sg}, + {ObjectStore::FieldType::Bool , "free_rise" , PropertyNames::FermentationStep::freeRise }, + {ObjectStore::FieldType::String, "vessel" , PropertyNames::FermentationStep::vessel }, } }; // FermentationSteps don't have children @@ -451,17 +430,17 @@ namespace { template<> ObjectStore::TableDefinition const PRIMARY_TABLE { "misc", { - {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, - {ObjectStore::FieldType::String, "name" , PropertyNames::NamedEntity::name }, - {ObjectStore::FieldType::Bool , "deleted" , PropertyNames::NamedEntity::deleted }, - {ObjectStore::FieldType::Bool , "display" , PropertyNames::NamedEntity::display }, - {ObjectStore::FieldType::String, "folder" , PropertyNames::FolderBase::folder }, - {ObjectStore::FieldType::Enum , "mtype" , PropertyNames::Misc::type , &Misc::typeStringMapping}, - {ObjectStore::FieldType::String, "use_for" , PropertyNames::Misc::useFor }, - {ObjectStore::FieldType::String, "notes" , PropertyNames::Misc::notes }, + {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, + {ObjectStore::FieldType::String, "name" , PropertyNames::NamedEntity::name }, + {ObjectStore::FieldType::Bool , "deleted" , PropertyNames::NamedEntity::deleted}, + {ObjectStore::FieldType::Bool , "display" , PropertyNames::NamedEntity::display}, + {ObjectStore::FieldType::String, "folder" , PropertyNames::FolderBase::folder }, + {ObjectStore::FieldType::Enum , "mtype" , PropertyNames::Misc::type , &Misc::typeStringMapping}, + {ObjectStore::FieldType::String, "use_for" , PropertyNames::Misc::useFor }, + {ObjectStore::FieldType::String, "notes" , PropertyNames::Misc::notes }, // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - {ObjectStore::FieldType::String, "producer" , PropertyNames::Misc::producer }, - {ObjectStore::FieldType::String, "product_id" , PropertyNames::Misc::productId }, + {ObjectStore::FieldType::String, "producer" , PropertyNames::Misc::producer }, + {ObjectStore::FieldType::String, "product_id" , PropertyNames::Misc::productId }, } }; @@ -471,7 +450,7 @@ namespace { template<> ObjectStore::TableDefinition const PRIMARY_TABLE { "misc_in_inventory", { - {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, + {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, {ObjectStore::FieldType::Int , "misc_id" , PropertyNames::Inventory::ingredientId , &PRIMARY_TABLE}, {ObjectStore::FieldType::Double, "quantity", PropertyNames::IngredientAmount::quantity}, {ObjectStore::FieldType::Unit , "unit" , PropertyNames::IngredientAmount::unit , &Measurement::Units::unitStringMapping}, @@ -489,7 +468,7 @@ namespace { {ObjectStore::FieldType::String, "name" , PropertyNames::NamedEntity::name }, {ObjectStore::FieldType::Bool , "deleted" , PropertyNames::NamedEntity::deleted }, {ObjectStore::FieldType::Bool , "display" , PropertyNames::NamedEntity::display }, - {ObjectStore::FieldType::String, "folder" , PropertyNames::FolderBase::folder }, + {ObjectStore::FieldType::String, "folder" , PropertyNames::FolderBase::folder }, /// {ObjectStore::FieldType::Bool , "is_acid" , PropertyNames::Salt::isAcid }, {ObjectStore::FieldType::Double, "percent_acid" , PropertyNames::Salt::percentAcid }, {ObjectStore::FieldType::Enum , "stype" , PropertyNames::Salt::type , &Salt::typeStringMapping}, @@ -504,7 +483,7 @@ namespace { template<> ObjectStore::TableDefinition const PRIMARY_TABLE { "salt_in_inventory", { - {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, + {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, {ObjectStore::FieldType::Int , "salt_id" , PropertyNames::Inventory::ingredientId , &PRIMARY_TABLE}, {ObjectStore::FieldType::Double, "quantity", PropertyNames::IngredientAmount::quantity}, {ObjectStore::FieldType::Unit , "unit" , PropertyNames::IngredientAmount::unit , &Measurement::Units::unitStringMapping}, @@ -522,7 +501,7 @@ namespace { {ObjectStore::FieldType::String, "name" , PropertyNames::NamedEntity::name }, {ObjectStore::FieldType::Bool , "display" , PropertyNames::NamedEntity::display }, {ObjectStore::FieldType::Bool , "deleted" , PropertyNames::NamedEntity::deleted }, - {ObjectStore::FieldType::String, "folder" , PropertyNames::FolderBase::folder }, + {ObjectStore::FieldType::String, "folder" , PropertyNames::FolderBase::folder }, {ObjectStore::FieldType::Double, "abv_max" , PropertyNames::Style::abvMax_pct }, {ObjectStore::FieldType::Double, "abv_min" , PropertyNames::Style::abvMin_pct }, {ObjectStore::FieldType::Double, "carb_max" , PropertyNames::Style::carbMax_vol }, @@ -562,7 +541,7 @@ namespace { {ObjectStore::FieldType::String, "name" , PropertyNames::NamedEntity::name }, {ObjectStore::FieldType::Bool , "display" , PropertyNames::NamedEntity::display }, {ObjectStore::FieldType::Bool , "deleted" , PropertyNames::NamedEntity::deleted }, - {ObjectStore::FieldType::String, "folder" , PropertyNames::FolderBase::folder }, + {ObjectStore::FieldType::String, "folder" , PropertyNames::FolderBase::folder }, {ObjectStore::FieldType::String, "notes" , PropertyNames::Water::notes }, {ObjectStore::FieldType::Double, "calcium" , PropertyNames::Water::calcium_ppm }, {ObjectStore::FieldType::Double, "bicarbonate" , PropertyNames::Water::bicarbonate_ppm }, @@ -582,8 +561,7 @@ 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 }, - // .:TODO:. We should correct the typo in this column name (copy-and-paste from BeerJSON - {ObjectStore::FieldType::Double, "flouride_ppm" , PropertyNames::Water::fluoride_ppm }, + {ObjectStore::FieldType::Double, "fluoride_ppm" , PropertyNames::Water::fluoride_ppm }, } }; @@ -593,32 +571,32 @@ namespace { template<> ObjectStore::TableDefinition const PRIMARY_TABLE { "yeast", { - {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, - {ObjectStore::FieldType::String, "name" , PropertyNames::NamedEntity::name }, - {ObjectStore::FieldType::Bool , "display" , PropertyNames::NamedEntity::display }, - {ObjectStore::FieldType::Bool , "deleted" , PropertyNames::NamedEntity::deleted }, - {ObjectStore::FieldType::String, "folder" , PropertyNames::FolderBase::folder }, - {ObjectStore::FieldType::Double, "max_temperature" , PropertyNames::Yeast::maxTemperature_c }, - {ObjectStore::FieldType::Double, "min_temperature" , PropertyNames::Yeast::minTemperature_c }, - {ObjectStore::FieldType::Enum , "flocculation" , PropertyNames::Yeast::flocculation , &Yeast::flocculationStringMapping}, - {ObjectStore::FieldType::Enum , "form" , PropertyNames::Yeast::form , &Yeast::formStringMapping }, - {ObjectStore::FieldType::Enum , "ytype" , PropertyNames::Yeast::type , &Yeast::typeStringMapping }, - {ObjectStore::FieldType::Int , "max_reuse" , PropertyNames::Yeast::maxReuse }, - {ObjectStore::FieldType::String, "best_for" , PropertyNames::Yeast::bestFor }, - {ObjectStore::FieldType::String, "laboratory" , PropertyNames::Yeast::laboratory }, - {ObjectStore::FieldType::String, "notes" , PropertyNames::Yeast::notes }, - {ObjectStore::FieldType::String, "product_id" , PropertyNames::Yeast::productId }, // Manufacturer's product ID, so, unlike other blah_id fields, not a foreign key! + {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, + {ObjectStore::FieldType::String, "name" , PropertyNames::NamedEntity::name }, + {ObjectStore::FieldType::Bool , "display" , PropertyNames::NamedEntity::display }, + {ObjectStore::FieldType::Bool , "deleted" , PropertyNames::NamedEntity::deleted }, + {ObjectStore::FieldType::String, "folder" , PropertyNames::FolderBase::folder }, + {ObjectStore::FieldType::Double, "max_temperature" , PropertyNames::Yeast::maxTemperature_c }, + {ObjectStore::FieldType::Double, "min_temperature" , PropertyNames::Yeast::minTemperature_c }, + {ObjectStore::FieldType::Enum , "flocculation" , PropertyNames::Yeast::flocculation , &Yeast::flocculationStringMapping}, + {ObjectStore::FieldType::Enum , "form" , PropertyNames::Yeast::form , &Yeast::formStringMapping }, + {ObjectStore::FieldType::Enum , "ytype" , PropertyNames::Yeast::type , &Yeast::typeStringMapping }, + {ObjectStore::FieldType::Int , "max_reuse" , PropertyNames::Yeast::maxReuse }, + {ObjectStore::FieldType::String, "best_for" , PropertyNames::Yeast::bestFor }, + {ObjectStore::FieldType::String, "laboratory" , PropertyNames::Yeast::laboratory }, + {ObjectStore::FieldType::String, "notes" , PropertyNames::Yeast::notes }, + {ObjectStore::FieldType::String, "product_id" , PropertyNames::Yeast::productId }, // Manufacturer's product ID, so, unlike other blah_id fields, not a foreign key! // ⮜⮜⮜ All below added for BeerJSON support ⮞⮞⮞ - {ObjectStore::FieldType::Double, "alcohol_tolerance_pct" , PropertyNames::Yeast::alcoholTolerance_pct }, - {ObjectStore::FieldType::Double, "attenuation_min_pct" , PropertyNames::Yeast::attenuationMin_pct }, - {ObjectStore::FieldType::Double, "attenuation_max_pct" , PropertyNames::Yeast::attenuationMax_pct }, - {ObjectStore::FieldType::Bool , "phenolic_off_flavor_positive", PropertyNames::Yeast::phenolicOffFlavorPositive }, - {ObjectStore::FieldType::Bool , "glucoamylase_positive" , PropertyNames::Yeast::glucoamylasePositive }, - {ObjectStore::FieldType::Bool , "killer_producing_k1_toxin" , PropertyNames::Yeast::killerProducingK1Toxin }, - {ObjectStore::FieldType::Bool , "killer_producing_k2_toxin" , PropertyNames::Yeast::killerProducingK2Toxin }, - {ObjectStore::FieldType::Bool , "killer_producing_k28_toxin" , PropertyNames::Yeast::killerProducingK28Toxin }, - {ObjectStore::FieldType::Bool , "killer_producing_klus_toxin" , PropertyNames::Yeast::killerProducingKlusToxin }, - {ObjectStore::FieldType::Bool , "killer_neutral" , PropertyNames::Yeast::killerNeutral }, + {ObjectStore::FieldType::Double, "alcohol_tolerance_pct" , PropertyNames::Yeast::alcoholTolerance_pct }, + {ObjectStore::FieldType::Double, "attenuation_min_pct" , PropertyNames::Yeast::attenuationMin_pct }, + {ObjectStore::FieldType::Double, "attenuation_max_pct" , PropertyNames::Yeast::attenuationMax_pct }, + {ObjectStore::FieldType::Bool , "phenolic_off_flavor_positive", PropertyNames::Yeast::phenolicOffFlavorPositive}, + {ObjectStore::FieldType::Bool , "glucoamylase_positive" , PropertyNames::Yeast::glucoamylasePositive }, + {ObjectStore::FieldType::Bool , "killer_producing_k1_toxin" , PropertyNames::Yeast::killerProducingK1Toxin }, + {ObjectStore::FieldType::Bool , "killer_producing_k2_toxin" , PropertyNames::Yeast::killerProducingK2Toxin }, + {ObjectStore::FieldType::Bool , "killer_producing_k28_toxin" , PropertyNames::Yeast::killerProducingK28Toxin }, + {ObjectStore::FieldType::Bool , "killer_producing_klus_toxin" , PropertyNames::Yeast::killerProducingKlusToxin }, + {ObjectStore::FieldType::Bool , "killer_neutral" , PropertyNames::Yeast::killerNeutral }, } }; @@ -628,7 +606,7 @@ namespace { template<> ObjectStore::TableDefinition const PRIMARY_TABLE { "yeast_in_inventory", { - {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, + {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, {ObjectStore::FieldType::Int , "yeast_id" , PropertyNames::Inventory::ingredientId , &PRIMARY_TABLE}, {ObjectStore::FieldType::Double, "quantity" , PropertyNames::IngredientAmount::quantity}, {ObjectStore::FieldType::Unit , "unit" , PropertyNames::IngredientAmount::unit , &Measurement::Units::unitStringMapping}, @@ -679,34 +657,7 @@ namespace { } }; - template<> ObjectStore::JunctionTableDefinitions const JUNCTION_TABLES { - // - // .:TODO:. This is the wrong way to model Instructions. We should treat them more like BrewNotes. In both case - // the objects cannot meaningfully exist without a corresponding Recipe. - // - // Having the "instruction" table (see PRIMARY_TABLE) and this instruction_in_recipe - // junction table implies the possibility for an existence of Instruction objects independently of Recipe - // ones, which is incorrect. - // - // There is a lot of boiler-plate here, and we could have gone for a much more compact representation of junction - // tables, but this keeps the definition format relatively closely aligned with that of primary tables. - // - // NOTE that the tables for ingredient additions (such as fermentable_in_recipe for RecipeAdditionFermentable) - // could, in principle, be treated as a junction tables, because they really are modelling a many-to-many - // relationship between, eg, Recipe and Fermentable. However, because they are also storing other properties - // (to do with the timing and amount of the addition), and because the Recipe class is already quite large, - // we choose to model the ingredient addition tables as freestanding entities. - // - { - "instruction_in_recipe", - { - {ObjectStore::FieldType::Int, "id" }, - {ObjectStore::FieldType::Int, "recipe_id", PropertyNames::NamedEntity::key, &PRIMARY_TABLE }, - {ObjectStore::FieldType::Int, "instruction_id", PropertyNames::Recipe::instructionIds, &PRIMARY_TABLE}, - {ObjectStore::FieldType::Int, "instruction_number" }, - } - } - }; + template<> ObjectStore::JunctionTableDefinitions const JUNCTION_TABLES {}; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Database field mappings for RecipeAdditionFermentable @@ -884,6 +835,29 @@ namespace { // BrewNotes don't have children template<> ObjectStore::JunctionTableDefinitions const JUNCTION_TABLES {}; + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Database field mappings for Instruction + // NB: instructions aren't displayed in trees, and get no folder + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + template<> ObjectStore::TableDefinition const PRIMARY_TABLE { + "instruction", + { + {ObjectStore::FieldType::Int , "id" , PropertyNames::NamedEntity::key }, + {ObjectStore::FieldType::String, "name" , PropertyNames::NamedEntity::name }, + {ObjectStore::FieldType::Bool , "display" , PropertyNames::NamedEntity::display }, + {ObjectStore::FieldType::Bool , "deleted" , PropertyNames::NamedEntity::deleted }, + {ObjectStore::FieldType::Int , "recipe_id" , PropertyNames::SteppedBase::ownerId , &PRIMARY_TABLE}, + {ObjectStore::FieldType::Int , "step_number" , PropertyNames::SteppedBase::stepNumber}, + {ObjectStore::FieldType::String, "directions" , PropertyNames::Instruction::directions}, + {ObjectStore::FieldType::Bool , "has_timer" , PropertyNames::Instruction::hasTimer }, + {ObjectStore::FieldType::String, "timer_value" , PropertyNames::Instruction::timerValue}, + {ObjectStore::FieldType::Bool , "completed" , PropertyNames::Instruction::completed }, + {ObjectStore::FieldType::Double, "interval_mins", PropertyNames::Instruction::interval }, + } + }; + // Instructions don't have children + template<> ObjectStore::JunctionTableDefinitions const JUNCTION_TABLES {}; + } diff --git a/src/editors/BoilStepEditor.cpp b/src/editors/BoilStepEditor.cpp index eddd5e8d4..8b4f5960a 100755 --- a/src/editors/BoilStepEditor.cpp +++ b/src/editors/BoilStepEditor.cpp @@ -26,9 +26,9 @@ BoilStepEditor::BoilStepEditor(QWidget* parent, QString const editorName) : 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_startTemp , lineEdit_startTemp , StepBase::startTemp_c , 1), + EDITOR_FIELD_NORM(BoilStep, label_stepTime , lineEdit_stepTime , StepBase::stepTime_mins , 0), + EDITOR_FIELD_NORM(BoilStep, label_rampTime , lineEdit_rampTime , StepBase::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), diff --git a/src/editors/FermentationStepEditor.cpp b/src/editors/FermentationStepEditor.cpp index a2072cb20..ce8279107 100755 --- a/src/editors/FermentationStepEditor.cpp +++ b/src/editors/FermentationStepEditor.cpp @@ -29,8 +29,8 @@ FermentationStepEditor::FermentationStepEditor(QWidget* parent, QString const ed // 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_startTemp , lineEdit_startTemp , StepBase::startTemp_c , 1), + EDITOR_FIELD_NORM(FermentationStep, label_stepTime , lineEdit_stepTime , StepBase::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), diff --git a/src/editors/MashStepEditor.cpp b/src/editors/MashStepEditor.cpp index cff3caaf4..b85c9ccdc 100755 --- a/src/editors/MashStepEditor.cpp +++ b/src/editors/MashStepEditor.cpp @@ -37,9 +37,9 @@ MashStepEditor::MashStepEditor(QWidget* parent, QString const editorName) : 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_stepTemp , lineEdit_stepTemp , StepBase::startTemp_c , 1), + EDITOR_FIELD_NORM(MashStep, label_stepTime , lineEdit_stepTime , StepBase::stepTime_mins , 0), + EDITOR_FIELD_NORM(MashStep, label_rampTime , lineEdit_rampTime , StepBase::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), diff --git a/src/model/Boil.cpp b/src/model/Boil.cpp index 22ff3c6ef..ec7cf4c89 100755 --- a/src/model/Boil.cpp +++ b/src/model/Boil.cpp @@ -17,6 +17,7 @@ #include +#include "database/ObjectStoreWrapper.h" #include "model/Fermentation.h" #include "model/FermentationStep.h" #include "model/Mash.h" @@ -33,8 +34,10 @@ bool Boil::isEqualTo(NamedEntity const & other) const { return ( Utils::AutoCompare(this->m_description , rhs.m_description ) && Utils::AutoCompare(this->m_notes , rhs.m_notes ) && - Utils::AutoCompare(this->m_preBoilSize_l, rhs.m_preBoilSize_l) - // .:TBD:. Should we check BoilSteps too? + Utils::AutoCompare(this->m_preBoilSize_l, rhs.m_preBoilSize_l) && + // Parent classes have to be equal too + this->FolderBase::doIsEqualTo(rhs) && + this->StepOwnerBase::doIsEqualTo(rhs) ); } @@ -50,11 +53,11 @@ TypeLookup const Boil::typeLookup { PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Boil::preBoilSize_l, Boil::m_preBoilSize_l, Measurement::PhysicalQuantity::Volume), PROPERTY_TYPE_LOOKUP_ENTRY_NO_MV(PropertyNames::Boil::boilTime_mins, Boil::boilTime_mins, Measurement::PhysicalQuantity::Time), - PROPERTY_TYPE_LOOKUP_ENTRY_NO_MV(PropertyNames::Boil::boilSteps, Boil::boilSteps), }, // Parent classes lookup {&NamedEntity::typeLookup, - std::addressof(FolderBase::typeLookup)} + std::addressof(FolderBase::typeLookup), + std::addressof(StepOwnerBase::typeLookup)} }; static_assert(std::is_base_of, Boil>::value); @@ -150,19 +153,8 @@ void Boil::setBoilTime_mins(double const val) { return; } -void Boil::acceptStepChange([[maybe_unused]] QMetaProperty prop, - [[maybe_unused]] QVariant val) { - BoilStep * stepSender = qobject_cast(sender()); - if (!stepSender) { - return; - } - - // If one of our steps changed, our pseudo properties may also change, so we need to emit some signals - if (stepSender->ownerId() == this->key()) { - emit changed(metaProperty(*PropertyNames::Boil::boilTime_mins), QVariant()); - emit changed(metaProperty(*PropertyNames::Boil::boilSteps ), QVariant()); - } - +void Boil::acceptStepChange(QMetaProperty prop, QVariant val) { + this->doAcceptStepChange(this->sender(), prop, val, {&PropertyNames::Boil::boilTime_mins}); return; } @@ -173,7 +165,12 @@ void Boil::ensureStandardProfile() { // post-boil). If it turns out that there are recipes with more complicated boil profiles then we might need to // revisit this. // - auto recipe = this->getOwningRecipe(); + // We are sort of assuming that, if this function needs to do anything then probably the Boil is only used by one + // Recipe -- because we're typically doing this when we're creating a new Boil for a Recipe being read in from a + // BeerXML file. That said, I don't think anything would break too horribly if in the event the Boil were used by + // more than one Recipe. + // + auto recipe = ObjectStoreWrapper::findFirstMatching([this](Recipe * rec) {return rec->uses(*this);}); auto boilSteps = this->steps(); if (boilSteps.size() == 0 || boilSteps.at(0)->startTemp_c().value_or(100.0) > Boil::minimumBoilTemperature_c) { diff --git a/src/model/Boil.h b/src/model/Boil.h index 6cdebecd0..85e49ff32 100755 --- a/src/model/Boil.h +++ b/src/model/Boil.h @@ -35,8 +35,8 @@ AddPropertyName(description ) AddPropertyName(notes ) AddPropertyName(preBoilSize_l) -AddPropertyName(boilTime_mins) -AddPropertyName(boilSteps ) +AddPropertyName(boilTime_mins) // Only used for BeerXML +///AddPropertyName(boilSteps ) #undef AddPropertyName //=========================================== End of property name constants =========================================== //====================================================================================================================== @@ -59,7 +59,9 @@ AddPropertyName(boilSteps ) * MashStep and BoilStep, which saves us duplicating code. * * Our \c Boil class maps closely to a BeerJSON "boil procedure", with the exception that, in BeerJSON "a - * boil procedure with no steps is the same as a standard single step boil." + * boil procedure with no steps is the same as a standard single step boil." We treat steps as required + * (for consistency with \c Mash::mashSteps and \c Fermentation::fermentationSteps) and map "no list of boil + * steps" to "empty list of boil steps". */ class Boil : public NamedEntity, public FolderBase, @@ -68,6 +70,11 @@ class Boil : public NamedEntity, FOLDER_BASE_DECL(Boil) STEP_OWNER_COMMON_DECL(Boil, boil) + // See model/FolderBase.h for info, getters and setters for these properties + Q_PROPERTY(QString folder READ folder WRITE setFolder ) + // See model/SteppedOwnerBase.h for info, getters and setters for these properties + Q_PROPERTY(QList> steps READ steps WRITE setSteps STORED false) + Q_PROPERTY(unsigned int numSteps READ numSteps STORED false) public: /** @@ -109,8 +116,6 @@ class Boil : public NamedEntity, static constexpr double minimumBoilTemperature_c{81.0}; //=================================================== 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 ) @@ -132,13 +137,6 @@ class Boil : public NamedEntity, * TBD: It's possible we should make this optional if we desire to support "no boil" recipes in future. */ Q_PROPERTY(double boilTime_mins READ boilTime_mins WRITE setBoilTime_mins STORED false) - /** - * \brief The individual boil steps. (See \c StepOwnerBase for getter/setter implementation.) - * Technically this is optional in BeerJSON, but we'll treat it as required (for consistency with - * \c Mash::mashSteps and \c Fermentation::fermentationSteps) and map "no list of boil steps" to "empty list - * of boil steps". - */ - Q_PROPERTY(QList> boilSteps READ boilSteps WRITE setBoilSteps STORED false) //============================================ "GETTER" MEMBER FUNCTIONS ============================================ QString description () const; @@ -179,8 +177,8 @@ public slots: void stepsChanged(); protected: - virtual bool isEqualTo(NamedEntity const & other) const; - virtual ObjectStore & getObjectStoreTypedInstance() const; + virtual bool isEqualTo(NamedEntity const & other) const override; + virtual ObjectStore & getObjectStoreTypedInstance() const override; private: QString m_description ; diff --git a/src/model/BoilStep.cpp b/src/model/BoilStep.cpp index bea79002f..edf1cd9d1 100755 --- a/src/model/BoilStep.cpp +++ b/src/model/BoilStep.cpp @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * model/BoilStep.cpp is part of Brewtarget, and is copyright the following authors 2023: + * model/BoilStep.cpp is part of Brewtarget, and is copyright the following authors 2023-2024: * • Matt Young * * Brewtarget is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -48,7 +48,8 @@ TypeLookup const BoilStep::typeLookup { PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::BoilStep::chillingType, BoilStep::m_chillingType, NonPhysicalQuantity::Enum), }, // Parent class lookup. NB: StepExtended not NamedEntity! - {&StepExtended::typeLookup} + {&StepExtended::typeLookup, + std::addressof(StepBase::typeLookup)} }; static_assert(std::is_base_of::value); diff --git a/src/model/BoilStep.h b/src/model/BoilStep.h index 9645c616e..5423730ad 100755 --- a/src/model/BoilStep.h +++ b/src/model/BoilStep.h @@ -46,6 +46,14 @@ class BoilStep : public StepExtended, public StepBase stepTime_mins READ stepTime_mins WRITE setStepTime_mins) + Q_PROPERTY(std::optional stepTime_days READ stepTime_days WRITE setStepTime_days) + Q_PROPERTY(std::optional startTemp_c READ startTemp_c WRITE setStartTemp_c ) + Q_PROPERTY(std::optional rampTime_mins READ rampTime_mins WRITE setRampTime_mins) public: /** diff --git a/src/model/BrewNote.h b/src/model/BrewNote.h index acb006767..871e8ed7f 100644 --- a/src/model/BrewNote.h +++ b/src/model/BrewNote.h @@ -224,11 +224,11 @@ class BrewNote : public OwnedByRecipe { double calculateAttenuation_pct(); signals: - void brewDateChanged(const QDate &); + void brewDateChanged(QDate const &); protected: - virtual bool isEqualTo(NamedEntity const & other) const; - virtual ObjectStore & getObjectStoreTypedInstance() const; + virtual bool isEqualTo(NamedEntity const & other) const override; + virtual ObjectStore & getObjectStoreTypedInstance() const override; private: diff --git a/src/model/Equipment.h b/src/model/Equipment.h index 37a79cb91..819efaf8f 100644 --- a/src/model/Equipment.h +++ b/src/model/Equipment.h @@ -125,6 +125,8 @@ class Equipment : public NamedEntity, public FolderBase { Q_OBJECT FOLDER_BASE_DECL(Equipment) + // See model/FolderBase.h for info, getters and setters for these properties + Q_PROPERTY(QString folder READ folder WRITE setFolder ) public: /** @@ -160,8 +162,6 @@ class Equipment : public NamedEntity, //! @} //=================================================== PROPERTIES ==================================================== - //! \brief Folder. See model/FolderBase for implementation of the getter & setter. - Q_PROPERTY(QString folder READ folder WRITE setFolder) /** * \brief The boil size in liters: the pre-boil volume used in this particular instance for this equipment setup. * Note that this may be a calculated value depending on the calcBoilVolume property. @@ -457,13 +457,11 @@ class Equipment : public NamedEntity, //! \brief Calculate how much wort is left immediately at knockout. double wortEndOfBoil_l( double kettleWort_l ) const; -/// virtual Recipe * getOwningRecipe() const; - signals: protected: - virtual bool isEqualTo(NamedEntity const & other) const; - virtual ObjectStore & getObjectStoreTypedInstance() const; + virtual bool isEqualTo(NamedEntity const & other) const override; + virtual ObjectStore & getObjectStoreTypedInstance() const override; private: double m_kettleBoilSize_l ; @@ -516,9 +514,6 @@ class Equipment : public NamedEntity, QString m_agingVesselNotes ; QString m_packagingVesselNotes ; - - - // Calculate the boil size. void doCalculations(); }; diff --git a/src/model/Fermentation.cpp b/src/model/Fermentation.cpp index 8bc0265ff..838012113 100755 --- a/src/model/Fermentation.cpp +++ b/src/model/Fermentation.cpp @@ -25,8 +25,10 @@ bool Fermentation::isEqualTo(NamedEntity const & other) const { // Base class will already have ensured names are equal return ( this->m_description == rhs.m_description && - this->m_notes == rhs.m_notes - // .:TBD:. Should we check FermentationSteps too? + this->m_notes == rhs.m_notes && + // Parent classes have to be equal too + this->FolderBase::doIsEqualTo(rhs) && + this->StepOwnerBase::doIsEqualTo(rhs) ); } @@ -40,7 +42,6 @@ TypeLookup const Fermentation::typeLookup { PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Fermentation::description, Fermentation::m_description, NonPhysicalQuantity::String), PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Fermentation::notes , Fermentation::m_notes , NonPhysicalQuantity::String), - PROPERTY_TYPE_LOOKUP_ENTRY_NO_MV(PropertyNames::Fermentation::fermentationSteps, Fermentation::fermentationSteps), PROPERTY_TYPE_LOOKUP_ENTRY_NO_MV(PropertyNames::Fermentation::primary , Fermentation::primary ), PROPERTY_TYPE_LOOKUP_ENTRY_NO_MV(PropertyNames::Fermentation::secondary , Fermentation::secondary ), PROPERTY_TYPE_LOOKUP_ENTRY_NO_MV(PropertyNames::Fermentation::tertiary , Fermentation::tertiary ), @@ -59,8 +60,8 @@ Fermentation::Fermentation(QString name) : NamedEntity{name, true}, FolderBase{}, StepOwnerBase{}, - m_description {"" }, - m_notes {"" } { + m_description {""}, + m_notes {""} { CONSTRUCTOR_END return; @@ -81,8 +82,8 @@ Fermentation::Fermentation(Fermentation const & other) : NamedEntity{other}, FolderBase{other}, StepOwnerBase{other}, - m_description {other.m_description }, - m_notes {other.m_notes } { + m_description {other.m_description}, + m_notes {other.m_notes } { CONSTRUCTOR_END return; @@ -98,8 +99,15 @@ QString Fermentation::notes () const { return this->m_notes ; } void Fermentation::setDescription (QString const & val) { SET_AND_NOTIFY(PropertyNames::Fermentation::description, this->m_description, val); return; } void Fermentation::setNotes (QString const & val) { SET_AND_NOTIFY(PropertyNames::Fermentation::notes , this->m_notes , val); return; } -void Fermentation::acceptStepChange([[maybe_unused]] QMetaProperty prop, - [[maybe_unused]] QVariant val) { +std::shared_ptr Fermentation::primary () const { return this->stepAt(1); } +std::shared_ptr Fermentation::secondary() const { return this->stepAt(2); } +std::shared_ptr Fermentation::tertiary () const { return this->stepAt(3); } +void Fermentation::setPrimary (std::shared_ptr val) { this->setStepAt(val, 1); return; } +void Fermentation::setSecondary(std::shared_ptr val) { this->setStepAt(val, 2); return; } +void Fermentation::setTertiary (std::shared_ptr val) { this->setStepAt(val, 3); return; } + +void Fermentation::acceptStepChange(QMetaProperty prop, QVariant val) { + this->doAcceptStepChange(this->sender(), prop, val, {}); return; } diff --git a/src/model/Fermentation.h b/src/model/Fermentation.h index 7abf735fd..3d54e6e5e 100755 --- a/src/model/Fermentation.h +++ b/src/model/Fermentation.h @@ -35,7 +35,7 @@ // See comment in model/NamedEntity.h #define AddPropertyName(property) namespace PropertyNames::Fermentation { BtStringConst const property{#property}; } AddPropertyName(description ) -AddPropertyName(fermentationSteps) +///AddPropertyName(fermentationSteps) AddPropertyName(notes ) AddPropertyName(primary ) AddPropertyName(secondary ) @@ -54,6 +54,11 @@ class Fermentation : public NamedEntity, Q_OBJECT FOLDER_BASE_DECL(Fermentation) STEP_OWNER_COMMON_DECL(Fermentation, fermentation) + // See model/FolderBase.h for info, getters and setters for these properties + Q_PROPERTY(QString folder READ folder WRITE setFolder ) + // See model/SteppedOwnerBase.h for info, getters and setters for these properties + Q_PROPERTY(QList> steps READ steps WRITE setSteps STORED false) + Q_PROPERTY(unsigned int numSteps READ numSteps STORED false) public: /** @@ -75,41 +80,49 @@ class Fermentation : public NamedEntity, virtual ~Fermentation(); //=================================================== 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 ) - //! \brief The individual fermentation steps. (See \c StepOwnerBase for getter/setter implementation.) - Q_PROPERTY(QList> fermentationSteps READ fermentationSteps WRITE setFermentationSteps STORED false) - - //! \brief Number of fermentation steps -- for BeerXML. NB: Read-only. (See \c StepOwnerBase for getter/setter implementation.) - Q_PROPERTY(unsigned int numSteps READ numSteps STORED false) - //! \brief Convenience property for accessing the first fermentation step (See \c StepOwnerBase for getter/setter implementation.) + //! \brief Convenience property for accessing the first fermentation step Q_PROPERTY(std::shared_ptr primary READ primary WRITE setPrimary STORED false) - //! \brief Convenience property for accessing the second fermentation step (See \c StepOwnerBase for getter/setter implementation.) + //! \brief Convenience property for accessing the second fermentation step Q_PROPERTY(std::shared_ptr secondary READ secondary WRITE setSecondary STORED false) - //! \brief Convenience property for accessing the third fermentation step (See \c StepOwnerBase for getter/setter implementation.) + //! \brief Convenience property for accessing the third fermentation step Q_PROPERTY(std::shared_ptr tertiary READ tertiary WRITE setTertiary STORED false) //============================================ "GETTER" MEMBER FUNCTIONS ============================================ - QString description () const; - QString notes () const; + QString description() const; + QString notes () const; //============================================ "SETTER" MEMBER FUNCTIONS ============================================ - void setDescription (QString const & val); - void setNotes (QString const & val); + void setDescription(QString const & val); + void setNotes (QString const & val); + + /** + * \brief A set of convenience functions for accessing the first, second and third steps. Note that calling + * \c setSecondary or \c setTertiary with something other than \c std::nullopt needs to ensure the right + * number of prior step(s) exist, if necessary by creating default ones. + * + * We don't put the step name in these getters/setters as it would become unwieldy - eg + * \c setSecondaryFermentationStep() + */ + std::shared_ptr primary () const; + std::shared_ptr secondary() const; + std::shared_ptr tertiary () const; + void setPrimary (std::shared_ptr val); + void setSecondary(std::shared_ptr val); + void setTertiary (std::shared_ptr val); public slots: void acceptStepChange(QMetaProperty, QVariant); signals: - // Emitted when the number of steps change, or when you should call fermentationSteps() again. + //! Emitted when the number of steps change, or when you should call fermentationSteps() again. void stepsChanged(); protected: - virtual bool isEqualTo(NamedEntity const & other) const; - virtual ObjectStore & getObjectStoreTypedInstance() const; + virtual bool isEqualTo(NamedEntity const & other) const override; + virtual ObjectStore & getObjectStoreTypedInstance() const override; private: QString m_description; diff --git a/src/model/FermentationStep.cpp b/src/model/FermentationStep.cpp index a5479e5fa..8f8b156c7 100755 --- a/src/model/FermentationStep.cpp +++ b/src/model/FermentationStep.cpp @@ -39,7 +39,8 @@ TypeLookup const FermentationStep::typeLookup { PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::FermentationStep::vessel , FermentationStep::m_vessel , NonPhysicalQuantity::String), }, // Parent class lookup. NB: StepExtended not NamedEntity! - {&StepExtended::typeLookup} + {&StepExtended::typeLookup, + std::addressof(StepBase::typeLookup)} }; static_assert(std::is_base_of::value); diff --git a/src/model/FermentationStep.h b/src/model/FermentationStep.h index 8fdcc3e8a..b23bf07c8 100755 --- a/src/model/FermentationStep.h +++ b/src/model/FermentationStep.h @@ -47,6 +47,14 @@ class FermentationStep : public StepExtended, public StepBase stepTime_mins READ stepTime_mins WRITE setStepTime_mins) + Q_PROPERTY(std::optional stepTime_days READ stepTime_days WRITE setStepTime_days) + Q_PROPERTY(std::optional startTemp_c READ startTemp_c WRITE setStartTemp_c ) + Q_PROPERTY(std::optional rampTime_mins READ rampTime_mins WRITE setRampTime_mins) public: /** @@ -93,7 +101,7 @@ class FermentationStep : public StepExtended, public StepBase m_freeRise; diff --git a/src/model/FolderBase.h b/src/model/FolderBase.h index 75c20155c..88f09881b 100755 --- a/src/model/FolderBase.h +++ b/src/model/FolderBase.h @@ -44,6 +44,15 @@ class FolderBase : public CuriouslyRecurringTemplateBasem_folder; } @@ -88,12 +98,22 @@ TypeLookup const FolderBase::typeLookup { TypeLookupOf::m_folder)>::value )} }, - // Parent class lookup: none as we are at the top of this arm of the inheritance tree + // Parent class lookup: none as we are at the top of this branch of the inheritance tree {} }; /** - * \brief Derived classes should include this in their header file, right after Q_OBJECT + * \brief Concrete derived classes should (either directly or via inclusion in an intermediate class's equivalent macro) + * include this in their header file, right after Q_OBJECT. Concrete derived classes also need to include the + * following block (see comment in model/StepBase.h for why): + * + * // See model/FolderBase.h for info, getters and setters for these properties + * Q_PROPERTY(QString folder READ folder WRITE setFolder ) + * + * Comments for these properties: + * + * \c folder : Currently this is the name of the folder, but ultimately we'd like to make it the \c Folder + * object itself. * * Note we have to be careful about comment formats in macro definitions */ @@ -102,10 +122,10 @@ TypeLookup const FolderBase::typeLookup { friend class FolderBase; \ \ public: \ - /*=========================== FB "GETTER" MEMBER FUNCTIONS ===========================*/ \ - virtual QString const & folder() const; \ - /*=========================== FB "SETTER" MEMBER FUNCTIONS ===========================*/ \ - virtual void setFolder(QString const & val); \ + /*=========================== FB "GETTER" MEMBER FUNCTIONS ===========================*/ \ + virtual QString const & folder() const; \ + /*=========================== FB "SETTER" MEMBER FUNCTIONS ===========================*/ \ + virtual void setFolder(QString const & val); \ /** * \brief Derived classes should include this in their .cpp file diff --git a/src/model/Ingredient.h b/src/model/Ingredient.h index 1a4e504d0..568f0a4eb 100755 --- a/src/model/Ingredient.h +++ b/src/model/Ingredient.h @@ -43,6 +43,8 @@ class Ingredient : public OutlineableNamedEntity, public FolderBase { Q_OBJECT FOLDER_BASE_DECL(Ingredient) + // See model/FolderBase.h for info, getters and setters for these properties + Q_PROPERTY(QString folder READ folder WRITE setFolder ) public: /** @@ -63,8 +65,6 @@ class Ingredient : public OutlineableNamedEntity, virtual ~Ingredient(); //=================================================== PROPERTIES ==================================================== - //! \brief Folder. See model/FolderBase for implementation of the getter & setter. - Q_PROPERTY(QString folder READ folder WRITE setFolder) /** * \brief For the moment, we have a single "total amount" inventory for a given \c Ingredient instance (eg \c Hop etc * instance). This property and its associated accessors allow the total to be read and modified without diff --git a/src/model/Instruction.cpp b/src/model/Instruction.cpp index cf425590d..7c6c2423a 100644 --- a/src/model/Instruction.cpp +++ b/src/model/Instruction.cpp @@ -22,50 +22,6 @@ #include "model/NamedParameterBundle.h" #include "model/Recipe.h" -// This private implementation class holds all private non-virtual members of Instruction -class Instruction::impl { -public: - - /** - * Constructor - */ - impl(Instruction & instruction) : - instruction{instruction}, - recipe{} { - return; - } - - /** - * Destructor - */ - ~impl() = default; - - std::shared_ptr getRecipe() { - // If we already know which recipe we're in, we just return that... - if (this->recipe) { - return this->recipe; - } - - // ...otherwise we have to ask the recipe object store to find our recipe - auto result = ObjectStoreTyped::getInstance().findFirstMatching( - [this](std::shared_ptr rec) {return rec->uses(instruction);} - ); - - if (!result) { - qCritical() << Q_FUNC_INFO << "Unable to find Recipe for Instruction #" << this->instruction.key(); - return nullptr; - } - - this->recipe = result; - - return result; - } - -private: - Instruction & instruction; - std::shared_ptr recipe; -}; - QString Instruction::localisedName() { return tr("Instruction"); } bool Instruction::isEqualTo(NamedEntity const & other) const { @@ -75,14 +31,12 @@ bool Instruction::isEqualTo(NamedEntity const & other) const { return ( this->m_directions == rhs.m_directions && this->m_hasTimer == rhs.m_hasTimer && - this->m_timerValue == rhs.m_timerValue + this->m_timerValue == rhs.m_timerValue && + // Parent classes have to be equal too + this->SteppedBase::doIsEqualTo(rhs) ); } -ObjectStore & Instruction::getObjectStoreTypedInstance() const { - return ObjectStoreTyped::getInstance(); -} - TypeLookup const Instruction::typeLookup { "Instruction", { @@ -93,25 +47,26 @@ TypeLookup const Instruction::typeLookup { PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Instruction::timerValue, Instruction::m_timerValue), }, // Parent class lookup - {&NamedEntity::typeLookup} + {&NamedEntity::typeLookup, + std::addressof(SteppedBase::typeLookup)} }; Instruction::Instruction(QString name) : - NamedEntity (name, true), - pimpl {std::make_unique(*this)}, - m_directions(""), - m_hasTimer (false), - m_timerValue(""), - m_completed (false), - m_interval (0.0) { + NamedEntity {name }, + SteppedBase{}, + m_directions {"" }, + m_hasTimer {false}, + m_timerValue {"" }, + m_completed {false}, + m_interval {0.0 } { CONSTRUCTOR_END return; } Instruction::Instruction(NamedParameterBundle const & namedParameterBundle) : - NamedEntity {namedParameterBundle}, - pimpl {std::make_unique(*this)}, + NamedEntity{namedParameterBundle}, + SteppedBase{namedParameterBundle}, SET_REGULAR_FROM_NPB (m_directions, namedParameterBundle, PropertyNames::Instruction::directions), SET_REGULAR_FROM_NPB (m_hasTimer , namedParameterBundle, PropertyNames::Instruction::hasTimer ), SET_REGULAR_FROM_NPB (m_timerValue, namedParameterBundle, PropertyNames::Instruction::timerValue), @@ -123,20 +78,18 @@ Instruction::Instruction(NamedParameterBundle const & namedParameterBundle) : } Instruction::Instruction(Instruction const & other) : - NamedEntity {other}, - pimpl {std::make_unique(*this)}, - m_directions{other.m_directions}, - m_hasTimer {other.m_hasTimer }, - m_timerValue{other.m_timerValue}, - m_completed {other.m_completed }, - m_interval {other.m_interval } { + NamedEntity {other}, + SteppedBase{other}, + m_directions {other.m_directions}, + m_hasTimer {other.m_hasTimer }, + m_timerValue {other.m_timerValue}, + m_completed {other.m_completed }, + m_interval {other.m_interval } { CONSTRUCTOR_END return; } -// See https://herbsutter.com/gotw/_100/ for why we need to explicitly define the destructor here (and not in the -// header file) Instruction::~Instruction() = default; // Setters ==================================================================== @@ -176,20 +129,27 @@ void Instruction::addReagent(QString const & reagent) { // Accessors ================================================================== QString Instruction::directions() { return m_directions; } -bool Instruction::hasTimer() { return m_hasTimer; } +bool Instruction::hasTimer() { return m_hasTimer; } QString Instruction::timerValue() { return m_timerValue; } -bool Instruction::completed() { return m_completed; } +bool Instruction::completed() { return m_completed; } QList Instruction::reagents() { return m_reagents; } double Instruction::interval() { return m_interval; } -int Instruction::instructionNumber() const { - return this->pimpl->getRecipe()->instructionNumber(*this); -} +///int Instruction::instructionNumber() const { +/// return this->pimpl->getRecipe()->instructionNumber(*this); +///} + +///std::shared_ptr Instruction::owningRecipe() const { +/// return ObjectStoreWrapper::findFirstMatching( [this](std::shared_ptr rec) {return rec->uses(*this);} ); +///} -std::shared_ptr Instruction::owningRecipe() const { - return ObjectStoreWrapper::findFirstMatching( [this](std::shared_ptr rec) {return rec->uses(*this);} ); +bool operator<(Instruction & lhs, Instruction & rhs) { + return lhs.stepNumber() < rhs.stepNumber(); } + +// Insert boiler-plate wrapper functions that call down to SteppedBase +STEPPED_COMMON_CODE(Instruction) diff --git a/src/model/Instruction.h b/src/model/Instruction.h index 50abfd42d..cf8a06e85 100644 --- a/src/model/Instruction.h +++ b/src/model/Instruction.h @@ -21,13 +21,12 @@ #define MODEL_INSTRUCTION_H #pragma once -#include // For PImpl - #include #include #include #include "model/NamedEntity.h" +#include "model/SteppedBase.h" //====================================================================================================================== //========================================== Start of property name constants ========================================== @@ -42,19 +41,29 @@ AddPropertyName(timerValue) //=========================================== End of property name constants =========================================== //====================================================================================================================== +// Forward declarations; +class Recipe; /*! * \class Instruction * * \brief Model class for an instruction record in the database. * - * This class is completely outside the BeerXML spec. + * NOTE that \c Instruction is not part of the official BeerXML or BeerJSON standards. We add it in to our + * BeerXML files, because we can, but TBD whether this is possible with BeerJSON. * - * TODO: We should make this inherit from \c OwnedByRecipe and bring it more into line with \c BrewNote + * NB: We do not inherit from \c OwnedByRecipe, because doing so would duplicate part of what we get from + * \c SteppedBase. */ -class Instruction : public NamedEntity { +class Instruction : public NamedEntity, + public SteppedBase { Q_OBJECT + STEPPED_COMMON_DECL(Instruction, Recipe) + // See model/SteppedBase.h for info, getters and setters for these properties + Q_PROPERTY(int ownerId READ ownerId WRITE setOwnerId ) + Q_PROPERTY(int stepNumber READ stepNumber WRITE setStepNumber) + public: /** * \brief See comment in model/NamedEntity.h @@ -74,23 +83,15 @@ class Instruction : public NamedEntity { virtual ~Instruction(); - Q_PROPERTY(QString directions READ directions WRITE setDirections /*NOTIFY changed*/ /*changedDirections*/ ) - Q_PROPERTY(bool hasTimer READ hasTimer WRITE setHasTimer /*NOTIFY changed*/ /*changedHasTimer*/ ) - Q_PROPERTY(QString timerValue READ timerValue WRITE setTimerValue /*NOTIFY changed*/ /*changedTimerValue*/ ) - Q_PROPERTY(bool completed READ completed WRITE setCompleted /*NOTIFY changed*/ /*changedCompleted*/ ) - Q_PROPERTY(double interval READ interval WRITE setInterval /*NOTIFY changed*/ /*changedInterval*/ ) - Q_PROPERTY(QList reagents READ reagents /*WRITE*/ /*NOTIFY changed*/ /*changedReagents*/ ) - Q_PROPERTY(int instructionNumber READ instructionNumber /*WRITE*/ /*NOTIFY changed*/ STORED false ) - - // "set" methods. - void setDirections(const QString& dir); - void setHasTimer (bool has); - void setTimerValue(const QString& timerVal); - void setCompleted (bool comp); - void setInterval (double interval); - void addReagent (const QString& reagent); + //=================================================== PROPERTIES ==================================================== + Q_PROPERTY(QString directions READ directions WRITE setDirections) + Q_PROPERTY(bool hasTimer READ hasTimer WRITE setHasTimer ) + Q_PROPERTY(QString timerValue READ timerValue WRITE setTimerValue) + Q_PROPERTY(bool completed READ completed WRITE setCompleted ) + Q_PROPERTY(double interval READ interval WRITE setInterval ) + Q_PROPERTY(QList reagents READ reagents STORED false ) - // "get" methods. + //============================================ "GETTER" MEMBER FUNCTIONS ============================================ QString directions(); bool hasTimer(); QString timerValue(); @@ -99,21 +100,22 @@ class Instruction : public NamedEntity { QList reagents(); double interval(); - int instructionNumber() const; + //============================================ "SETTER" MEMBER FUNCTIONS ============================================ + void setDirections(const QString& dir); + void setHasTimer (bool has); + void setTimerValue(const QString& timerVal); + void setCompleted (bool comp); + void setInterval (double interval); + void addReagent (const QString& reagent); - virtual std::shared_ptr owningRecipe() const; +/// virtual std::shared_ptr owningRecipe() const override; signals: protected: - virtual bool isEqualTo(NamedEntity const & other) const; - virtual ObjectStore & getObjectStoreTypedInstance() const; + virtual bool isEqualTo(NamedEntity const & other) const override; private: - // Private implementation details - see https://herbsutter.com/gotw/_100/ - class impl; - std::unique_ptr pimpl; - QString m_directions; bool m_hasTimer; QString m_timerValue; @@ -126,9 +128,6 @@ class Instruction : public NamedEntity { BT_DECLARE_METATYPES(Instruction) //! \brief Compares Instruction pointers by Instruction::instructionNumber(). -inline bool insPtrLtByNumber( Instruction* lhs, Instruction* rhs) -{ - return lhs->instructionNumber() < rhs->instructionNumber(); -} +bool operator<(Instruction & lhs, Instruction & rhs); #endif diff --git a/src/model/Mash.cpp b/src/model/Mash.cpp index 16ef480f4..2e6cea414 100644 --- a/src/model/Mash.cpp +++ b/src/model/Mash.cpp @@ -42,8 +42,10 @@ bool Mash::isEqualTo(NamedEntity const & other) const { Utils::AutoCompare(this->m_spargeTemp_c , rhs.m_spargeTemp_c ) && Utils::AutoCompare(this->m_ph , rhs.m_ph ) && Utils::AutoCompare(this->m_mashTunWeight_kg , rhs.m_mashTunWeight_kg ) && - Utils::AutoCompare(this->m_mashTunSpecificHeat_calGC, rhs.m_mashTunSpecificHeat_calGC) - // .:TBD:. Should we check MashSteps too? + Utils::AutoCompare(this->m_mashTunSpecificHeat_calGC, rhs.m_mashTunSpecificHeat_calGC) && + // Parent classes have to be equal too + this->FolderBase::doIsEqualTo(rhs) && + this->StepOwnerBase::doIsEqualTo(rhs) ); } @@ -64,12 +66,11 @@ TypeLookup const Mash::typeLookup { PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Mash::mashTunSpecificHeat_calGC, Mash::m_mashTunSpecificHeat_calGC, Measurement::PhysicalQuantity::SpecificHeatCapacity), PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Mash::tunTemp_c , Mash::m_tunTemp_c , Measurement::PhysicalQuantity::Temperature ), PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::Mash::mashTunWeight_kg , Mash::m_mashTunWeight_kg , Measurement::PhysicalQuantity::Mass ), - - PROPERTY_TYPE_LOOKUP_ENTRY_NO_MV(PropertyNames::Mash::mashSteps , Mash::mashSteps ), }, // Parent classes lookup {&NamedEntity::typeLookup, - std::addressof(FolderBase::typeLookup)} + std::addressof(FolderBase::typeLookup), + std::addressof(StepOwnerBase::typeLookup)} }; static_assert(std::is_base_of, Mash>::value); @@ -216,19 +217,9 @@ bool Mash::hasSparge() const { return false; } -void Mash::acceptStepChange([[maybe_unused]] QMetaProperty prop, - [[maybe_unused]] QVariant val) { - MashStep * stepSender = qobject_cast(sender()); - if (!stepSender) { - return; - } - - // If one of our mash steps changed, our calculated properties may also change, so we need to emit some signals - if (stepSender->ownerId() == this->key()) { - emit changed(metaProperty(*PropertyNames::Mash::totalMashWater_l), QVariant()); - emit changed(metaProperty(*PropertyNames::Mash::totalTime ), QVariant()); - } - +void Mash::acceptStepChange(QMetaProperty prop, QVariant val) { + this->doAcceptStepChange(this->sender(), prop, val, {&PropertyNames::Mash::totalMashWater_l, + &PropertyNames::Mash::totalTime }); return; } diff --git a/src/model/Mash.h b/src/model/Mash.h index 295ffca03..dc2dcd407 100644 --- a/src/model/Mash.h +++ b/src/model/Mash.h @@ -34,6 +34,7 @@ #include "model/FolderBase.h" #include "model/NamedEntity.h" +#include "model/MashStep.h" #include "model/StepOwnerBase.h" //====================================================================================================================== @@ -42,7 +43,7 @@ #define AddPropertyName(property) namespace PropertyNames::Mash { BtStringConst const property{#property}; } AddPropertyName(equipAdjust ) AddPropertyName(grainTemp_c ) -AddPropertyName(mashSteps ) +///AddPropertyName(mashSteps ) AddPropertyName(mashTunSpecificHeat_calGC) AddPropertyName(mashTunWeight_kg ) AddPropertyName(notes ) @@ -55,10 +56,6 @@ AddPropertyName(tunTemp_c ) //=========================================== End of property name constants =========================================== //====================================================================================================================== - -// Forward declarations. -class MashStep; - /*! * \class Mash * @@ -73,6 +70,11 @@ class Mash : public NamedEntity, Q_OBJECT FOLDER_BASE_DECL(Mash) STEP_OWNER_COMMON_DECL(Mash, mash) + // See model/FolderBase.h for info, getters and setters for these properties + Q_PROPERTY(QString folder READ folder WRITE setFolder ) + // See model/SteppedOwnerBase.h for info, getters and setters for these properties + Q_PROPERTY(QList> steps READ steps WRITE setSteps STORED false) + Q_PROPERTY(unsigned int numSteps READ numSteps STORED false) public: /** @@ -94,8 +96,6 @@ class Mash : public NamedEntity, virtual ~Mash(); //=================================================== PROPERTIES ==================================================== - //! \brief Folder. See model/FolderBase for implementation of the getter & setter. - Q_PROPERTY(QString folder READ folder WRITE setFolder) //! \brief The initial grain temp in Celsius. Q_PROPERTY(double grainTemp_c READ grainTemp_c WRITE setGrainTemp_c ) //! \brief The notes. @@ -116,8 +116,6 @@ class Mash : public NamedEntity, Q_PROPERTY(double totalMashWater_l READ totalMashWater_l STORED false ) //! \brief The total mash time in minutes. Calculated. Q_PROPERTY(double totalTime READ totalTime STORED false ) - //! \brief The individual mash steps. - Q_PROPERTY(QList> mashSteps READ mashSteps WRITE setMashSteps STORED false ) // ⮜⮜⮜ BeerJSON support does not require any additional properties on this class! ⮞⮞⮞ @@ -142,7 +140,7 @@ class Mash : public NamedEntity, void setEquipAdjust (bool const val); // Calculated getters - unsigned int numMashSteps() const; +/// unsigned int numMashSteps() const; double totalMashWater_l() const; //! \brief all the infusion water, excluding sparge double totalInfusionAmount_l() const; @@ -160,8 +158,8 @@ public slots: void stepsChanged(); protected: - virtual bool isEqualTo(NamedEntity const & other) const; - virtual ObjectStore & getObjectStoreTypedInstance() const; + virtual bool isEqualTo(NamedEntity const & other) const override; + virtual ObjectStore & getObjectStoreTypedInstance() const override; private: double m_grainTemp_c ; diff --git a/src/model/MashStep.cpp b/src/model/MashStep.cpp index d03ce7340..461470d46 100644 --- a/src/model/MashStep.cpp +++ b/src/model/MashStep.cpp @@ -50,7 +50,7 @@ EnumStringMapping const MashStep::typeDisplayNames { bool MashStep::isEqualTo(NamedEntity const & other) const { - // Base class (NamedEntity) will have ensured this cast is valid + // NamedEntity::operator==() will have ensured this cast is valid MashStep const & rhs = static_cast(other); // Base class will already have ensured names are equal return ( @@ -58,7 +58,10 @@ bool MashStep::isEqualTo(NamedEntity const & other) const { this->m_amount_l == rhs.m_amount_l && this->m_infuseTemp_c == rhs.m_infuseTemp_c && // Parent classes have to be equal too - this->Step::isEqualTo(other) + this->Step::isEqualTo(rhs) && + this->StepBase::doIsEqualTo(rhs) ); } @@ -74,7 +77,8 @@ TypeLookup const MashStep::typeLookup { PROPERTY_TYPE_LOOKUP_ENTRY(PropertyNames::MashStep::liquorToGristRatio_lKg, MashStep::m_liquorToGristRatio_lKg, Measurement::PhysicalQuantity::SpecificVolume), }, // Parent class lookup. NB: Step not NamedEntity! - {&Step::typeLookup} + {&Step::typeLookup, + std::addressof(StepBase::typeLookup)} }; static_assert(std::is_base_of::value); diff --git a/src/model/MashStep.h b/src/model/MashStep.h index f61eaa265..a0cf96f20 100644 --- a/src/model/MashStep.h +++ b/src/model/MashStep.h @@ -25,7 +25,6 @@ #include #include -#include "model/Mash.h" #include "model/NamedEntity.h" #include "model/Step.h" #include "model/StepBase.h" @@ -41,10 +40,12 @@ AddPropertyName(infuseAmount_l ) // Should only be used for BeerXML AddPropertyName(infuseTemp_c ) AddPropertyName(liquorToGristRatio_lKg) AddPropertyName(type ) - #undef AddPropertyName //=========================================== End of property name constants =========================================== //====================================================================================================================== + +class Mash; + /** * On \c MashStep, \c stepTime_mins is required, \c startTemp_c is required */ @@ -58,6 +59,17 @@ class MashStep : public Step, public StepBase { Q_OBJECT STEP_COMMON_DECL(Mash, MashStepOptions) + // See model/SteppedBase.h for info, getters and setters for these properties + Q_PROPERTY(int ownerId READ ownerId WRITE setOwnerId ) + Q_PROPERTY(int stepNumber READ stepNumber WRITE setStepNumber) + // See model/StepBase.h for info, getters and setters for these properties + Q_PROPERTY(std::optional stepTime_mins READ stepTime_mins WRITE setStepTime_mins) + Q_PROPERTY(std::optional stepTime_days READ stepTime_days WRITE setStepTime_days) + Q_PROPERTY(std::optional startTemp_c READ startTemp_c WRITE setStartTemp_c ) + Q_PROPERTY(std::optional rampTime_mins READ rampTime_mins WRITE setRampTime_mins) + +// Q_PROPERTY(std::optional startTemp_c READ startTemp_c WRITE setStartTemp_c ) +// Q_PROPERTY(std::optional stepTime_mins READ stepTime_mins WRITE setStepTime_mins) public: /** diff --git a/src/model/Misc.h b/src/model/Misc.h index c47206d40..9d972dd8a 100644 --- a/src/model/Misc.h +++ b/src/model/Misc.h @@ -148,8 +148,8 @@ class Misc : public Ingredient, public IngredientBase { signals: protected: - virtual bool isEqualTo(NamedEntity const & other) const; - virtual ObjectStore & getObjectStoreTypedInstance() const; + virtual bool isEqualTo(NamedEntity const & other) const override; + virtual ObjectStore & getObjectStoreTypedInstance() const override; private: Type m_type ; diff --git a/src/model/NamedEntity.h b/src/model/NamedEntity.h index 53780bc7b..b3c4f29e2 100644 --- a/src/model/NamedEntity.h +++ b/src/model/NamedEntity.h @@ -354,6 +354,12 @@ class NamedEntity : public QObject { * object has an owning \c Recipe to make it easy to determine whether a change to a base class property * constitutes a change to a \c Recipe (and if so which one). Hence this function. * + * NOTE that we have a similar member function \b owner() on classes that have an owner (be that owner a + * \c Recipe, \c Boil, \c Mash or \c Fermentation). The difference is that \c owner is not virtual (as it is + * intended to be called by templated code where we know the concrete type at compile time) and it has + * covariant return types (which are hard to do properly with smart pointers). To avoid code duplication, we + * normally implement the virtual function (\c owningRecipe()) by calling the non-virtual one (\c owner). + * * \return \c nullptr if this object does not belong to a \c Recipe (ie it is an Independent Item or a * Semi-Independent Item that is either used in more than one \c Recipe or not used in any \c Recipe) */ @@ -446,8 +452,8 @@ class NamedEntity : public QObject { virtual bool isEqualTo(NamedEntity const & other) const = 0; /** - * \brief Subclasses need to override this function to return the appropriate instance of \c ObjectStoreTyped. - * This allows us in this base class to access \c ObjectStoreTyped for \c Hop, + * \brief Concrete subclasses need to override this functionto return the appropriate instance of + * \c ObjectStoreTyped. This allows us in this base class to access \c ObjectStoreTyped for \c Hop, * \c ObjectStoreTyped for \c Fermentable, etc. */ virtual ObjectStore & getObjectStoreTypedInstance() const = 0; diff --git a/src/model/OwnedByRecipe.cpp b/src/model/OwnedByRecipe.cpp index 0254c4aca..a5a742e67 100755 --- a/src/model/OwnedByRecipe.cpp +++ b/src/model/OwnedByRecipe.cpp @@ -73,13 +73,19 @@ std::shared_ptr OwnedByRecipe::recipe() const { return ObjectStoreWrapper::getById(this->m_recipeId); } -std::shared_ptr OwnedByRecipe::owningRecipe() const { +std::shared_ptr OwnedByRecipe::owner() const { if (ObjectStoreWrapper::contains(this->m_recipeId)) { return ObjectStoreWrapper::getById(this->m_recipeId); } return nullptr; } +std::shared_ptr OwnedByRecipe::owningRecipe() const { + // See comment in model/NamedEntity.h. This function is virtual (runtime polymorphic) but we implement it with by + // calling the compile-time polymorphic function whose signature we share with BoilStep, MashStep, FermentationStep. + return this->owner(); +} + void OwnedByRecipe::setRecipeId(int const val) { SET_AND_NOTIFY(PropertyNames::OwnedByRecipe::recipeId, this->m_recipeId, val); return; } void OwnedByRecipe::setRecipe(Recipe * recipe) { diff --git a/src/model/OwnedByRecipe.h b/src/model/OwnedByRecipe.h index c83efc221..5402bdfea 100755 --- a/src/model/OwnedByRecipe.h +++ b/src/model/OwnedByRecipe.h @@ -21,14 +21,14 @@ #include "model/NamedEntity.h" -//╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ +//====================================================================================================================== //========================================== Start of property name constants ========================================== // See comment in model/NamedEntity.h #define AddPropertyName(property) namespace PropertyNames::OwnedByRecipe { BtStringConst const property{#property}; } AddPropertyName(recipeId) #undef AddPropertyName //=========================================== End of property name constants =========================================== -//╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ +//====================================================================================================================== // Forward declarations; class Recipe; @@ -59,12 +59,17 @@ class OwnedByRecipe : public NamedEntity { void setRecipeId(int const val); void setRecipe(Recipe * recipe); - virtual std::shared_ptr owningRecipe() const; + /** + * \brief This is, amongst other things, needed by \c TreeModelBase + */ + std::shared_ptr owner() const; + + virtual std::shared_ptr owningRecipe() const override; int recipeId() const; std::shared_ptr recipe() const; protected: - virtual bool isEqualTo(NamedEntity const & other) const; + virtual bool isEqualTo(NamedEntity const & other) const override; protected: int m_recipeId; diff --git a/src/model/Recipe.cpp b/src/model/Recipe.cpp index 2e376b827..89f9b1329 100644 --- a/src/model/Recipe.cpp +++ b/src/model/Recipe.cpp @@ -204,7 +204,7 @@ namespace { template<> BtStringConst const & Recipe::propertyNameFor() { return PropertyNames::Recipe::boilId ; } template<> BtStringConst const & Recipe::propertyNameFor() { return PropertyNames::Recipe::equipmentId ; } template<> BtStringConst const & Recipe::propertyNameFor() { return PropertyNames::Recipe::fermentationId ; } -template<> BtStringConst const & Recipe::propertyNameFor() { return PropertyNames::Recipe::instructionIds ; } +///template<> BtStringConst const & Recipe::propertyNameFor() { return PropertyNames::Recipe::instructionIds ; } template<> BtStringConst const & Recipe::propertyNameFor() { return PropertyNames::Recipe::mashId ; } template<> BtStringConst const & Recipe::propertyNameFor