From ceab72177a88a38c8f48725a1a9e01e5e0c8d572 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 10 Mar 2018 08:51:17 +0100 Subject: [PATCH] Added units as a table, populate ingredient and unit boxes in new recipe dialog. --- gui/newrecipedialog.cpp | 36 +++++- gui/newrecipedialog.h | 14 +++ gui/newrecipedialog.ui | 112 ++++++++++++------ main.cpp | 14 +-- model/database/database.cpp | 5 +- model/database/recipedatabase.cpp | 104 ++++++++++++---- model/database/recipedatabase.h | 7 +- model/recipe/ingredients/recipeingredient.cpp | 6 +- model/recipe/ingredients/recipeingredient.h | 4 +- model/recipe/ingredients/unitofmeasure.cpp | 10 +- model/recipe/ingredients/unitofmeasure.h | 3 +- 11 files changed, 234 insertions(+), 81 deletions(-) diff --git a/gui/newrecipedialog.cpp b/gui/newrecipedialog.cpp index 818f180..15364f0 100644 --- a/gui/newrecipedialog.cpp +++ b/gui/newrecipedialog.cpp @@ -3,12 +3,40 @@ NewRecipeDialog::NewRecipeDialog(QWidget *parent) : QDialog(parent), - ui(new Ui::NewRecipeDialog) -{ + ui(new Ui::NewRecipeDialog){ ui->setupUi(this); } -NewRecipeDialog::~NewRecipeDialog() -{ +NewRecipeDialog::NewRecipeDialog(RecipeDatabase *db, QWidget *parent) : NewRecipeDialog(parent){ + this->recipeDB = db; + + + this->populateIngredientsBox(); + this->populateUnitsBox(); +} + +NewRecipeDialog::~NewRecipeDialog(){ delete ui; } + +void NewRecipeDialog::on_toolButton_clicked(){ + ui->instructionsTextEdit->setFontItalic(!ui->instructionsTextEdit->fontItalic()); +} + +void NewRecipeDialog::populateIngredientsBox(){ + this->ingredients = this->recipeDB->retrieveAllIngredients(); + ui->ingredientNameBox->clear(); + for (unsigned int i = 0; i < this->ingredients.size(); i++){ + QString s = QString::fromStdString(this->ingredients[i].getName()); + ui->ingredientNameBox->insertItem(i, s); + } +} + +void NewRecipeDialog::populateUnitsBox(){ + this->units = this->recipeDB->retrieveAllUnitsOfMeasure(); + ui->unitComboBox->clear(); + for (unsigned int i = 0; i < this->units.size(); i++){ + QString s = QString::fromStdString(this->units[i].getName()); + ui->unitComboBox->insertItem(i, s); + } +} diff --git a/gui/newrecipedialog.h b/gui/newrecipedialog.h index 30c34a2..81272e4 100644 --- a/gui/newrecipedialog.h +++ b/gui/newrecipedialog.h @@ -2,6 +2,9 @@ #define NEWRECIPEDIALOG_H #include +#include + +#include "model/database/recipedatabase.h" namespace Ui { class NewRecipeDialog; @@ -13,10 +16,21 @@ class NewRecipeDialog : public QDialog public: explicit NewRecipeDialog(QWidget *parent = 0); + NewRecipeDialog(RecipeDatabase *db, QWidget *parent = 0); ~NewRecipeDialog(); + private slots: + void on_toolButton_clicked(); + private: Ui::NewRecipeDialog *ui; + RecipeDatabase *recipeDB; + vector ingredients; + vector units; + + //Helper functions to fill fields. + void populateIngredientsBox(); + void populateUnitsBox(); }; #endif // NEWRECIPEDIALOG_H diff --git a/gui/newrecipedialog.ui b/gui/newrecipedialog.ui index 0482bfe..9be836e 100644 --- a/gui/newrecipedialog.ui +++ b/gui/newrecipedialog.ui @@ -10,7 +10,7 @@ 0 0 640 - 640 + 689 @@ -22,40 +22,43 @@ New Recipe - - font: 25 14pt "Noto Sans CJK KR"; - - + - - - - Noto Sans CJK KR - 14 - 3 - false - false - - - - New Recipe Name - - - Qt::AlignCenter - - - - - - - Qt::AlignCenter - - - Recipe Name - + + + + + + + Noto Sans CJK KR + 14 + 50 + false + false + + + + Edit Recipe + + + Qt::AlignCenter + + + + + + + Qt::AlignCenter + + + Recipe Name + + + + @@ -83,7 +86,7 @@ 0 1999 12 - 29 + 28 @@ -185,7 +188,7 @@ QLayout::SetMaximumSize - + Ingredients @@ -198,7 +201,7 @@ - + 100 @@ -303,7 +306,7 @@ Noto Sans CJK KR 14 - 3 + 50 false false @@ -329,12 +332,12 @@ + + + - - - @@ -373,6 +376,39 @@ + + + + + + + Instructions + + + Qt::AlignCenter + + + + + + + italics + + + Qt::ToolButtonIconOnly + + + + + + + Enter instructions here. + + + + + + diff --git a/main.cpp b/main.cpp index 38b8ee6..3f700e5 100644 --- a/main.cpp +++ b/main.cpp @@ -16,21 +16,21 @@ int main(int argc, char *argv[]) RecipeDatabase recipeDB("recipes"); //TESTING CODE -// vector ri; -// ri.push_back(RecipeIngredient("flour", "grains", 3.0f, UnitOfMeasure("cup", "cups", "c"))); -// ri.push_back(RecipeIngredient("Baking Powder", "Additives", 1.0f, UnitOfMeasure("Teaspoon", "Teaspoons", "Tsp"))); + vector ri; + ri.push_back(RecipeIngredient("flour", "grains", 3.0f, UnitOfMeasure("cup", "cups", "c", UnitOfMeasure::VOLUME, 1.0), "")); + ri.push_back(RecipeIngredient("Baking Powder", "Additives", 1.0f, UnitOfMeasure("Teaspoon", "Teaspoons", "Tsp", UnitOfMeasure::VOLUME, 1.0), "")); -// Recipe rec("Example", ri, Instruction("BOLDiTaLiCs"), QImage(), vector(), QDate::currentDate(), QTime(0, 30), QTime(0, 25), 10.0f); + Recipe rec("Example", ri, Instruction("BOLDiTaLiCs"), QImage(), vector(), QDate::currentDate(), QTime(0, 30), QTime(0, 25), 10.0f); -// bool success = recipeDB.storeRecipe(rec); -// printf("Storage successful: %d\n", success); + bool success = recipeDB.storeRecipe(rec); + printf("Storage successful: %d\n", success); Recipe reloadRec = recipeDB.retrieveRecipe("Example"); reloadRec.print(); w.loadFromRecipe(reloadRec); - NewRecipeDialog d; + NewRecipeDialog d(&recipeDB); d.show(); d.exec(); diff --git a/model/database/database.cpp b/model/database/database.cpp index 6fb4394..636a137 100644 --- a/model/database/database.cpp +++ b/model/database/database.cpp @@ -42,7 +42,7 @@ ResultTable Database::selectFrom(string tableName, string columnNames, string co if (columnNames.size() == 0 || tableName.empty()){ return ResultTable(); } - string query = "SELECT " + columnNames + " FROM " + tableName + " WHERE " + conditions + ";"; + string query = "SELECT " + columnNames + " FROM " + tableName + " " + conditions + ";"; return this->executeSQL(query); } @@ -81,8 +81,7 @@ bool Database::tableExists(string tableName){ if (tableName.empty() || this->db == NULL || !this->dbIsOpen){ return false; } - ResultTable t = this->selectFrom("sqlite_master", "name", "type='table' AND name='"+tableName+"'"); - //ResultTable t = executeSQL("SELECT name FROM sqlite_master WHERE type='table' AND name='"+tableName+"';"); + ResultTable t = this->selectFrom("sqlite_master", "name", "WHERE type='table' AND name='"+tableName+"'"); return !t.isEmpty(); } diff --git a/model/database/recipedatabase.cpp b/model/database/recipedatabase.cpp index bc23000..37f23ce 100644 --- a/model/database/recipedatabase.cpp +++ b/model/database/recipedatabase.cpp @@ -5,9 +5,9 @@ RecipeDatabase::RecipeDatabase(string filename) : Database(filename){ } bool RecipeDatabase::storeRecipe(Recipe recipe){ - ///TODO: Implement this in a smart way using transaction. + //Store a recipe, if it doesn't already exist. This first tries to create the recipe entry, then all subsequent supporting table entries. this->executeSQL("BEGIN;"); - ResultTable t = this->selectFrom("recipe", "*", "name="+surroundString(recipe.getName(), "'")); + ResultTable t = this->selectFrom("recipe", "*", "WHERE name="+surroundString(recipe.getName(), "'")); if (!t.isEmpty()){ fprintf(stderr, "Error storing recipe: Recipe with name %s already exists.\n", recipe.getName().c_str()); } else { @@ -50,17 +50,11 @@ bool RecipeDatabase::storeRecipe(Recipe recipe){ } bool RecipeDatabase::storeRecipeIngredient(RecipeIngredient ri, int recipeId){ - //First check if the base ingredient has been added to the database. This is done within storeIngredient(). - ResultTable t = this->selectFrom("ingredient", "ingredientId", "name="+surroundString(ri.getName(), "'")); - int ingId = 0; - if (t.isEmpty()){ - if (!this->insertInto("ingredient", vector({"foodGroup", "name"}), vector({ri.getFoodGroup(), ri.getName()}))){ - return false; - } - ingId = this->getLastInsertedRowId(); - } else { - ingId = std::stoi(t.valueAt(0, 0)); - } + int ingId = this->storeIngredient(ri); + if (ingId < 0) return false; + + if (!this->storeUnitOfMeasure(ri.getUnit())) return false; + return this->insertInto("recipeIngredient", vector({ "ingredientId", @@ -78,13 +72,43 @@ bool RecipeDatabase::storeRecipeIngredient(RecipeIngredient ri, int recipeId){ })); } -void RecipeDatabase::storeIngredient(Ingredient ingredient){ - ResultTable t = this->selectFrom("ingredient", "*", "name="+surroundString(ingredient.getName(), "'")); +int RecipeDatabase::storeIngredient(Ingredient ingredient){ + ResultTable t = this->selectFrom("ingredient", "*", "WHERE name="+surroundString(ingredient.getName(), "'")); if (t.isEmpty()){ - this->insertInto("ingredient", vector({"foodGroup", "name"}), vector({ingredient.getFoodGroup(), ingredient.getName()})); + bool success = this->insertInto("ingredient", vector({"foodGroup", "name"}), vector({ingredient.getFoodGroup(), ingredient.getName()})); + if (success){ + return this->getLastInsertedRowId(); + } else { + return -1; + } + } else { + return std::stoi(t.valueAt(0, 0)); } } +bool RecipeDatabase::storeUnitOfMeasure(UnitOfMeasure u){ + ResultTable t = this->selectFrom("unitOfMeasure", "name", "WHERE name="+surroundString(u.getName(), "'")); + if (!t.isEmpty()){ + return true; + } + bool success = this->insertInto("unitOfMeasure", + vector({ + "name", + "plural", + "abbreviation", + "type", + "metricCoefficient" + }), + vector({ + u.getName(), + u.getNamePlural(), + u.getAbbreviation(), + std::to_string(u.getType()), + std::to_string(u.getMetricCoefficient()) + })); + return success; +} + bool RecipeDatabase::storeInstruction(Instruction instruction, int recipeId){ return FileUtils::saveInstruction(recipeId, instruction); } @@ -112,7 +136,7 @@ bool RecipeDatabase::storeTags(vector tags, int recipeId){ } Recipe RecipeDatabase::retrieveRecipe(string name){ - ResultTable t = this->selectFrom("recipe", "*", "name="+surroundString(name, "'")); + ResultTable t = this->selectFrom("recipe", "*", "WHERE name="+surroundString(name, "'")); if (t.isEmpty()){ fprintf(stderr, "Error: No recipe with name %s found!\n", name.c_str()); return Recipe(); @@ -131,19 +155,49 @@ Recipe RecipeDatabase::retrieveRecipe(string name){ } vector RecipeDatabase::retrieveRecipeIngredients(int recipeId){ - ResultTable t = this->executeSQL("SELECT ingredient.name, ingredient.foodGroup, recipeIngredient.quantity, recipeIngredient.unitName, recipeIngredient.comment " + ResultTable t = this->executeSQL("SELECT ingredient.name, ingredient.foodGroup, "//0, 1 + "recipeIngredient.quantity, recipeIngredient.unitName, recipeIngredient.comment,"//2, 3, 4 + "unitOfMeasure.name, unitOfMeasure.plural, unitOfMeasure.abbreviation, unitOfMeasure.type, unitOfMeasure.metricCoefficient "//5, 6, 7, 8, 9 "FROM ingredient " "INNER JOIN recipeIngredient " "ON ingredient.ingredientId = recipeIngredient.ingredientId " - "AND recipeIngredient.recipeId = "+std::to_string(recipeId)+";"); + "INNER JOIN unitOfMeasure " + "ON recipeIngredient.unitName = unitOfMeasure.name " + "WHERE recipeIngredient.recipeId = "+std::to_string(recipeId)+";"); vector ings; for (unsigned int row = 0; row < t.rowCount(); row++){ - RecipeIngredient r(t.valueAt(row, 0), t.valueAt(row, 1), std::stof(t.valueAt(row, 2)), UnitOfMeasure(t.valueAt(row, 3))); + RecipeIngredient r(t.valueAt(row, 0), + t.valueAt(row, 1), + std::stof(t.valueAt(row, 2)), + UnitOfMeasure(t.valueAt(row, 5), t.valueAt(row, 6), t.valueAt(row, 7), std::stoi(t.valueAt(row, 8)), std::stod(t.valueAt(row, 9))), + t.valueAt(row, 4)); ings.push_back(r); } return ings; } +vector RecipeDatabase::retrieveAllIngredients(){ + ResultTable t = this->selectFrom("ingredient", "*", "ORDER BY name"); + vector ings; + for (unsigned int row = 0; row < t.rowCount(); row++){ + Ingredient i(t.valueAt(row, 2), t.valueAt(row, 1)); + ings.push_back(i); + } + return ings; +} + +vector RecipeDatabase::retrieveAllUnitsOfMeasure(){ + ResultTable t = this->selectFrom("unitOfMeasure", "*", "ORDER BY name"); + vector units; + if (!t.isEmpty()){ + for (unsigned int row = 0; row < t.rowCount(); row++){ + UnitOfMeasure u(t.valueAt(row, 0), t.valueAt(row, 1), t.valueAt(row, 2), std::stoi(t.valueAt(row, 3)), std::stod(t.valueAt(row, 4))); + units.push_back(u); + } + } + return units; +} + void RecipeDatabase::ensureTablesExist(){ //Make sure that foreign keys are enabled. this->executeSQL("PRAGMA foreign_keys = ON;"); @@ -154,6 +208,13 @@ void RecipeDatabase::ensureTablesExist(){ "ingredientId INTEGER PRIMARY KEY," "foodGroup varchar," "name varchar UNIQUE);"); + //Unit of Measure table. + this->executeSQL("CREATE TABLE IF NOT EXISTS unitOfMeasure(" + "name varchar UNIQUE PRIMARY KEY," + "plural varchar," + "abbreviation varchar," + "type int," + "metricCoefficient real);"); //Recipe table. Each recipe can have at most one instruction, and one image. this->executeSQL("CREATE TABLE IF NOT EXISTS recipe(" "recipeId INTEGER PRIMARY KEY," @@ -175,6 +236,7 @@ void RecipeDatabase::ensureTablesExist(){ "unitName varchar," "comment varchar," "FOREIGN KEY (ingredientId) REFERENCES ingredient(ingredientId)," - "FOREIGN KEY (recipeId) REFERENCES recipe(recipeId));"); + "FOREIGN KEY (recipeId) REFERENCES recipe(recipeId)," + "FOREIGN KEY (unitName) REFERENCES unitOfMeasure(name));"); this->executeSQL("COMMIT;"); } diff --git a/model/database/recipedatabase.h b/model/database/recipedatabase.h index e91b45d..446536c 100644 --- a/model/database/recipedatabase.h +++ b/model/database/recipedatabase.h @@ -22,14 +22,19 @@ class RecipeDatabase : public Database bool storeRecipe(Recipe recipe); //SQL Helper methods. + //Storage. bool storeRecipeIngredient(RecipeIngredient ri, int recipeId); - void storeIngredient(Ingredient ingredient); + int storeIngredient(Ingredient ingredient); + bool storeUnitOfMeasure(UnitOfMeasure u); bool storeInstruction(Instruction instruction, int recipeId); bool storeImage(QImage image, int recipeId); bool storeTags(vector tags, int recipeId); + //Retrieval. Recipe retrieveRecipe(string name); vector retrieveRecipeIngredients(int recipeId); + vector retrieveAllIngredients(); + vector retrieveAllUnitsOfMeasure(); private: //Utility methods. diff --git a/model/recipe/ingredients/recipeingredient.cpp b/model/recipe/ingredients/recipeingredient.cpp index c48155c..7a3b75a 100644 --- a/model/recipe/ingredients/recipeingredient.cpp +++ b/model/recipe/ingredients/recipeingredient.cpp @@ -1,15 +1,17 @@ #include "model/recipe/ingredients/recipeingredient.h" -RecipeIngredient::RecipeIngredient(string name, string foodGroup, float quantity, UnitOfMeasure unit) : Ingredient(name, foodGroup){ +RecipeIngredient::RecipeIngredient(string name, string foodGroup, float quantity, UnitOfMeasure unit, string comment) : Ingredient(name, foodGroup){ setQuantity(quantity); setUnit(unit); + setComment(comment); } -RecipeIngredient::RecipeIngredient(Ingredient i, float quantity, UnitOfMeasure unit){ +RecipeIngredient::RecipeIngredient(Ingredient i, float quantity, UnitOfMeasure unit, string comment){ setName(i.getName()); setFoodGroup(i.getFoodGroup()); setQuantity(quantity); setUnit(unit); + setComment(comment); } RecipeIngredient::RecipeIngredient(){ diff --git a/model/recipe/ingredients/recipeingredient.h b/model/recipe/ingredients/recipeingredient.h index 70c20f6..ae5fef9 100644 --- a/model/recipe/ingredients/recipeingredient.h +++ b/model/recipe/ingredients/recipeingredient.h @@ -16,9 +16,9 @@ class RecipeIngredient : public Ingredient { public: //Constructor for new RecipeIngredient without starting child ingredient. - RecipeIngredient(string name, string foodGroup, float quantity, UnitOfMeasure unit); + RecipeIngredient(string name, string foodGroup, float quantity, UnitOfMeasure unit, string comment); //Constructor using data from a child ingredient. - RecipeIngredient(Ingredient i, float quantity, UnitOfMeasure unit); + RecipeIngredient(Ingredient i, float quantity, UnitOfMeasure unit, string comment); RecipeIngredient(); //Getters diff --git a/model/recipe/ingredients/unitofmeasure.cpp b/model/recipe/ingredients/unitofmeasure.cpp index 7b4a0f8..dedcb75 100644 --- a/model/recipe/ingredients/unitofmeasure.cpp +++ b/model/recipe/ingredients/unitofmeasure.cpp @@ -1,10 +1,11 @@ #include "unitofmeasure.h" -UnitOfMeasure::UnitOfMeasure(string name, string plural, string abbreviation, int type){ +UnitOfMeasure::UnitOfMeasure(string name, string plural, string abbreviation, int type, double coef){ this->name = name; this->plural = plural; this->abbreviation = abbreviation; this->type = type; + this->metricCoefficient = coef; } UnitOfMeasure::UnitOfMeasure(string name){ @@ -12,10 +13,11 @@ UnitOfMeasure::UnitOfMeasure(string name){ this->plural = name + "s"; this->abbreviation = "NULL"; this->type = MISC; + this->metricCoefficient = 1; ///TODO: Make actual guessing of this stuff. } -UnitOfMeasure::UnitOfMeasure() : UnitOfMeasure::UnitOfMeasure("", "", "", MISC){ +UnitOfMeasure::UnitOfMeasure() : UnitOfMeasure::UnitOfMeasure("", "", "", MISC, 1.0){ //Default constructor initializes all fields to empty strings. } @@ -34,3 +36,7 @@ string UnitOfMeasure::getAbbreviation() const{ int UnitOfMeasure::getType() const{ return this->type; } + +double UnitOfMeasure::getMetricCoefficient() const{ + return this->metricCoefficient; +} diff --git a/model/recipe/ingredients/unitofmeasure.h b/model/recipe/ingredients/unitofmeasure.h index 1197089..b446e2e 100644 --- a/model/recipe/ingredients/unitofmeasure.h +++ b/model/recipe/ingredients/unitofmeasure.h @@ -19,7 +19,7 @@ public: static const int MISC = 4; //Full constructor. - UnitOfMeasure(string name, string plural, string abbreviation, int type); + UnitOfMeasure(string name, string plural, string abbreviation, int type, double coef); //Attempt to guess unit from just a string. UnitOfMeasure(string name); //Constructor with default values. @@ -30,6 +30,7 @@ public: string getNamePlural() const; string getAbbreviation() const; int getType() const; + double getMetricCoefficient() const; private: string name; //The name of the unit of measure. string plural; //The plural name.