From b73641004475a95faef8de226368f9961b607488 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 31 Mar 2018 12:53:31 +0200 Subject: [PATCH 1/3] Finished edit functionality --- gui/mainwindow.cpp | 3 +- gui/newrecipedialog.ui | 25 ++++------ gui/openrecipedialog.cpp | 8 ++++ gui/openrecipedialog.h | 2 + gui/openrecipedialog.ui | 8 +--- model/database/database.cpp | 17 +++++-- model/database/database.h | 4 ++ model/database/recipedatabase.cpp | 79 ++++++++++++++++++++++++++++++- model/database/recipedatabase.h | 21 +++++--- model/database/resulttable.cpp | 5 ++ model/database/resulttable.h | 2 + 11 files changed, 141 insertions(+), 33 deletions(-) diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index d2de990..bc09378 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -95,11 +95,12 @@ void MainWindow::on_exitButton_clicked(){ void MainWindow::on_editButton_clicked(){ NewRecipeDialog d(this->recipeDB, this->currentRecipe, this); + string originalName = this->currentRecipe.getName(); d.show(); d.exec(); if (d.isAccepted()){ Recipe r = d.getRecipe(); - if (!this->recipeDB->storeRecipe(r)){ + if (!this->recipeDB->updateRecipe(r, originalName)){ QMessageBox::critical(this, QString("Unable to Save Recipe"), QString("The program was not able to successfully save the recipe. Make sure to give the recipe a name, instructions, and some ingredients!")); } else { this->loadFromRecipe(r); diff --git a/gui/newrecipedialog.ui b/gui/newrecipedialog.ui index 937ee33..a42a7a2 100644 --- a/gui/newrecipedialog.ui +++ b/gui/newrecipedialog.ui @@ -100,7 +100,6 @@ - 16 3 false false @@ -142,7 +141,7 @@ 0 1999 12 - 24 + 23 @@ -352,6 +351,9 @@ QAbstractItemView::MultiSelection + + QAbstractItemView::ScrollPerPixel + @@ -667,6 +669,9 @@ QAbstractItemView::MultiSelection + + QAbstractItemView::ScrollPerPixel + 100 @@ -746,16 +751,6 @@ background-color: rgb(250, 250, 255); - - - - Instructions - - - Qt::AlignCenter - - - @@ -769,7 +764,7 @@ - I + Italic true @@ -789,7 +784,7 @@ - B + Bold true @@ -827,7 +822,7 @@ Qt::LeftToRight - true + false background-color: rgb(255, 255, 255); diff --git a/gui/openrecipedialog.cpp b/gui/openrecipedialog.cpp index 184ea18..c0458b8 100644 --- a/gui/openrecipedialog.cpp +++ b/gui/openrecipedialog.cpp @@ -127,3 +127,11 @@ void OpenRecipeDialog::on_foodGroupsListWidget_itemSelectionChanged(){ } this->populateRecipesTable(this->recipeDB->retrieveRecipesWithFoodGroups(groups)); } + +void OpenRecipeDialog::on_clearSearchButton_clicked(){ + ui->nameEdit->clear(); + ui->foodGroupsListWidget->selectionModel()->clearSelection(); + ui->tagsListView->selectionModel()->clearSelection(); + ui->ingredientsListView->selectionModel()->clearSelection(); + this->populateRecipesTable(this->recipeDB->retrieveAllRecipes()); +} diff --git a/gui/openrecipedialog.h b/gui/openrecipedialog.h index 8e63496..f0989da 100644 --- a/gui/openrecipedialog.h +++ b/gui/openrecipedialog.h @@ -38,6 +38,8 @@ class OpenRecipeDialog : public QDialog void on_foodGroupsListWidget_itemSelectionChanged(); + void on_clearSearchButton_clicked(); + private: Ui::OpenRecipeDialog *ui; RecipeDatabase *recipeDB; diff --git a/gui/openrecipedialog.ui b/gui/openrecipedialog.ui index 93cb94b..460e738 100644 --- a/gui/openrecipedialog.ui +++ b/gui/openrecipedialog.ui @@ -147,13 +147,9 @@ - + - - - - - :/images/images/search_icon.png:/images/images/search_icon.png + Clear search criteria diff --git a/model/database/database.cpp b/model/database/database.cpp index db53c3f..6e914b0 100644 --- a/model/database/database.cpp +++ b/model/database/database.cpp @@ -14,12 +14,11 @@ ResultTable Database::executeSQL(string statement){ sqlite3_stmt* stmt; this->sql = statement; this->returnCode = sqlite3_prepare_v2(this->db, statement.c_str(), -1, &stmt, NULL); - ResultTable t(statement); if (this->returnCode != SQLITE_OK){ fprintf(stderr, "Unable to successfully prepare SQL statement. Error code: %d\n\tError Message: %s\n", this->returnCode, sqlite3_errmsg(this->db)); - return t; + return ResultTable(this->returnCode); } - + ResultTable t(statement); t.extractData(stmt); this->returnCode = sqlite3_finalize(stmt); @@ -78,6 +77,18 @@ void Database::closeConnection(){ this->dbIsOpen = false; } +void Database::beginTransaction(){ + this->executeSQL("BEGIN;"); +} + +void Database::commitTransaction(){ + this->executeSQL("COMMIT;"); +} + +void Database::rollbackTransaction(){ + this->executeSQL("ROLLBACK;"); +} + string Database::combineVector(std::vector strings, string mid){ if (strings.empty()){ return ""; diff --git a/model/database/database.h b/model/database/database.h index d0bd435..0ccc9fd 100644 --- a/model/database/database.h +++ b/model/database/database.h @@ -35,6 +35,10 @@ public: void closeConnection(); + void beginTransaction(); + void commitTransaction(); + void rollbackTransaction(); + protected: string surroundString(string s, string surround); diff --git a/model/database/recipedatabase.cpp b/model/database/recipedatabase.cpp index 1a9f83a..0ebd528 100644 --- a/model/database/recipedatabase.cpp +++ b/model/database/recipedatabase.cpp @@ -253,6 +253,18 @@ vector RecipeDatabase::retrieveRecipeIngredients(int recipeId) return ings; } +int RecipeDatabase::retrieveIngredientId(string ingredientName){ + return std::stoi(this->selectFrom("ingredient", "ingredientId", "WHERE name = '"+ingredientName+"'").at(0, 0)); +} + +bool RecipeDatabase::deleteRecipeTags(int recipeId){ + return this->deleteFrom("recipeTag", "WHERE recipeId = "+std::to_string(recipeId)); +} + +bool RecipeDatabase::deleteRecipeIngredients(int recipeId){ + return this->deleteFrom("recipeIngredient", "WHERE recipeId = "+std::to_string(recipeId)); +} + vector RecipeDatabase::retrieveAllIngredients(){ ResultTable t = this->selectFrom("ingredient", "name, foodGroup", "ORDER BY name"); vector ings; @@ -356,8 +368,71 @@ bool RecipeDatabase::deleteTag(RecipeTag tag){ return this->deleteFrom("recipeTag", "WHERE tagName='"+tag.getValue()+"'"); } -bool RecipeDatabase::updateRecipe(Recipe recipe){ - +bool RecipeDatabase::updateRecipe(Recipe recipe, string originalName) { + string idS = this->selectFrom("recipe", "recipeId", "WHERE name="+surroundString(originalName, "'")).at(0, 0); + int id = std::stoi(idS); + this->beginTransaction(); + ResultTable t = this->executeSQL("UPDATE recipe " + "SET name = '"+recipe.getName()+"', " + "createdDate = '"+recipe.getCreatedDate().toString().toStdString()+"', " + "prepTime = '"+recipe.getPrepTime().toString().toStdString()+"', " + "cookTime = '"+recipe.getCookTime().toString().toStdString()+"', " + "servingCount = "+std::to_string(recipe.getServings())+" " + "WHERE recipeId = "+idS+";"); + bool recipeSuccess = t.getReturnCode() == SQLITE_DONE; + if (!recipeSuccess){ + this->rollbackTransaction(); + return false; + } + bool tagsSuccess = this->deleteRecipeTags(id); + for (RecipeTag tag : recipe.getTags()){ + tagsSuccess = tagsSuccess && this->insertInto( + "recipeTag", + vector({ + "recipeId", + "tagName" + }), + vector({ + idS, + tag.getValue() + })); + } + if (!tagsSuccess){ + this->rollbackTransaction(); + return false; + } + bool ingredientsSuccess = this->deleteRecipeIngredients(id); + for (RecipeIngredient ri : recipe.getIngredients()){ + ingredientsSuccess = ingredientsSuccess && this->insertInto( + "recipeIngredient", + vector({ + "recipeId", + "ingredientId", + "unitName", + "quantity", + "comment" + }), + vector({ + idS, + std::to_string(this->retrieveIngredientId(ri.getName())), + ri.getUnit().getName(), + std::to_string(ri.getQuantity()), + ri.getComment() + })); + } + if (!ingredientsSuccess){ + this->rollbackTransaction(); + return false; + } + bool instructionSuccess = FileUtils::saveInstruction(id, recipe.getInstruction()); + bool imageSuccess = FileUtils::saveImage(id, recipe.getImage()); + if (!(instructionSuccess && imageSuccess)){ + this->rollbackTransaction(); + return false; + } else { + this->commitTransaction(); + return true; + } } void RecipeDatabase::ensureTablesExist(){ diff --git a/model/database/recipedatabase.h b/model/database/recipedatabase.h index c68b0c9..2b80d95 100644 --- a/model/database/recipedatabase.h +++ b/model/database/recipedatabase.h @@ -26,9 +26,6 @@ class RecipeDatabase : public Database bool storeRecipeIngredient(RecipeIngredient ri, int recipeId); 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); @@ -39,10 +36,8 @@ class RecipeDatabase : public Database vector retrieveRecipesWithSubstring(string s); vector retrieveRecipesWithFoodGroups(vector groups); vector retrieveAllFoodGroups(); - vector retrieveRecipeIngredients(int recipeId); vector retrieveAllIngredients(); vector retrieveAllUnitsOfMeasure(); - vector retrieveTags(int recipeId); vector retrieveAllTags(); //Deletion. @@ -53,7 +48,7 @@ class RecipeDatabase : public Database bool deleteTag(RecipeTag tag); //Updating. - bool updateRecipe(Recipe recipe); + bool updateRecipe(Recipe recipe, string originalName); private: //Utility methods. @@ -61,6 +56,20 @@ class RecipeDatabase : public Database //Read a recipe from a row of a result table. Recipe readFromResultTable(ResultTable t, int row=0); vector readRecipesFromTable(ResultTable t); + + //Storage + bool storeInstruction(Instruction instruction, int recipeId); + bool storeImage(QImage image, int recipeId); + bool storeTags(vector tags, int recipeId); + + //Retrieval + vector retrieveTags(int recipeId); + vector retrieveRecipeIngredients(int recipeId); + int retrieveIngredientId(string ingredientName); + + //Deletion + bool deleteRecipeTags(int recipeId); + bool deleteRecipeIngredients(int recipeId); }; #endif // RECIPEDATABASE_H diff --git a/model/database/resulttable.cpp b/model/database/resulttable.cpp index ac96d7b..a8a3ac0 100644 --- a/model/database/resulttable.cpp +++ b/model/database/resulttable.cpp @@ -8,6 +8,10 @@ ResultTable::ResultTable(string query){ this->originalQuery = query; } +ResultTable::ResultTable(int resultCode){ + this->queryCode = resultCode; +} + void ResultTable::extractData(sqlite3_stmt *stmt){ this->values.clear(); int res = sqlite3_step(stmt); @@ -30,6 +34,7 @@ void ResultTable::processRow(sqlite3_stmt *stmt){ } void ResultTable::printData(){ + printf("--> Result Code: [%d] <--\n", this->getReturnCode()); if (this->isEmpty()){ printf("Result table is empty.\n"); return; diff --git a/model/database/resulttable.h b/model/database/resulttable.h index a4dcd84..361f75c 100644 --- a/model/database/resulttable.h +++ b/model/database/resulttable.h @@ -21,6 +21,8 @@ class ResultTable ResultTable(); //Constructs a table with the original query saved. ResultTable(string query); + //Constructs an empty table with a result code. + ResultTable(int resultCode); //Gets all the data from the result set and stores it internally as strings. void extractData(sqlite3_stmt* stmt); -- 2.34.1 From 9e3c59e415f1858826c6ce7d4135285716963b73 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 31 Mar 2018 14:01:57 +0200 Subject: [PATCH 2/3] Added icons for search criteria, fixed deleting image bug when editing. --- gui/newrecipedialog.cpp | 3 +- gui/newrecipedialog.h | 1 - gui/openrecipedialog.cpp | 6 +- gui/openrecipedialog.h | 2 + gui/openrecipedialog.ui | 257 +++++++++++++++++++++++++++--- images.qrc | 3 + images/foodPyramid.png | Bin 0 -> 7405 bytes images/ingredients.png | Bin 0 -> 18033 bytes images/tag.png | Bin 0 -> 1408 bytes main.cpp | 17 +- model/database/recipedatabase.cpp | 2 +- model/recipe/recipe.cpp | 2 +- 12 files changed, 249 insertions(+), 44 deletions(-) create mode 100644 images/foodPyramid.png create mode 100644 images/ingredients.png create mode 100644 images/tag.png diff --git a/gui/newrecipedialog.cpp b/gui/newrecipedialog.cpp index 3825ad1..ac3258c 100644 --- a/gui/newrecipedialog.cpp +++ b/gui/newrecipedialog.cpp @@ -40,7 +40,7 @@ Recipe NewRecipeDialog::getRecipe(){ Recipe r(ui->recipeNameEdit->text().toStdString(), this->ingredientListModel.getIngredients(), ui->instructionsTextEdit->toHtml().toStdString(), - this->img,//Image + ui->imageDisplayLabel->pixmap()->toImage(),//Image this->tagsListModel.getTags(),//Tags QDate::currentDate(), ui->prepTimeEdit->time(), @@ -126,7 +126,6 @@ void NewRecipeDialog::on_deleteTagButton_clicked(){ void NewRecipeDialog::on_selectImageButton_clicked(){ QString filename = QFileDialog::getOpenFileName(this, "Open Image", QString(), "Image Files (*.png *.jpg *.bmp)"); if (!filename.isEmpty()){ - this->img = QImage(filename); ui->imageDisplayLabel->setPixmap(QPixmap(filename)); } } diff --git a/gui/newrecipedialog.h b/gui/newrecipedialog.h index 105cd37..5198dd9 100644 --- a/gui/newrecipedialog.h +++ b/gui/newrecipedialog.h @@ -70,7 +70,6 @@ class NewRecipeDialog : public QDialog vector tags; RecipeIngredientListModel ingredientListModel; TagListModel tagsListModel; - QImage img; bool accepted = false; //Helper functions to fill fields. diff --git a/gui/openrecipedialog.cpp b/gui/openrecipedialog.cpp index c0458b8..8f936da 100644 --- a/gui/openrecipedialog.cpp +++ b/gui/openrecipedialog.cpp @@ -72,7 +72,7 @@ void OpenRecipeDialog::on_deleteRecipeButton_clicked(){ } string recipePlural = (rows.size() == 1) ? "recipe" : "recipes"; QString title = QString::fromStdString("Delete " + recipePlural); - QString content = QString::fromStdString("Are you sure you wish to delete the selected "+recipePlural+"?"); + QString content = QString::fromStdString("Are you sure you wish to delete the selected "+recipePlural+"?\nAll deleted recipes are permanently deleted."); QMessageBox::StandardButton reply = QMessageBox::question(this, title, content); if (reply == QMessageBox::Yes){ for (int row : rows){ @@ -135,3 +135,7 @@ void OpenRecipeDialog::on_clearSearchButton_clicked(){ ui->ingredientsListView->selectionModel()->clearSelection(); this->populateRecipesTable(this->recipeDB->retrieveAllRecipes()); } + +void OpenRecipeDialog::on_exitButton_clicked(){ + this->close(); +} diff --git a/gui/openrecipedialog.h b/gui/openrecipedialog.h index f0989da..c6cf23c 100644 --- a/gui/openrecipedialog.h +++ b/gui/openrecipedialog.h @@ -40,6 +40,8 @@ class OpenRecipeDialog : public QDialog void on_clearSearchButton_clicked(); + void on_exitButton_clicked(); + private: Ui::OpenRecipeDialog *ui; RecipeDatabase *recipeDB; diff --git a/gui/openrecipedialog.ui b/gui/openrecipedialog.ui index 460e738..06db1f0 100644 --- a/gui/openrecipedialog.ui +++ b/gui/openrecipedialog.ui @@ -17,13 +17,37 @@ :/images/images/icon.png:/images/images/icon.png + + font: 25 "Noto Sans CJK KR Light"; + true - + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + background-color: rgb(245, 245, 255); + + + 0 + @@ -39,13 +63,35 @@ QTabWidget::Rounded - 2 + 1 - + + + + :/images/images/tag.png:/images/images/tag.png + + + + Tags + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + @@ -74,10 +120,32 @@ + + + :/images/images/ingredients.png:/images/images/ingredients.png + + + + Ingredients + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + @@ -105,11 +173,33 @@ - + + + + :/images/images/foodPyramid.png:/images/images/foodPyramid.png + + + + Food Groups + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + @@ -132,6 +222,9 @@ + + background-color: rgb(240, 240, 255); + @@ -141,16 +234,115 @@ - + + + + 12 + 3 + false + false + + + + background-color: rgb(255, 255, 255); + + + false + + + Qt::AlignCenter + + + Recipe Name + + + + + 0 + 40 + + + + false + + + QPushButton#clearSearchButton { + background-color: rgb(235, 235, 255); + border: 0px; +} +QPushButton#clearSearchButton:hover{ + background-color: rgb(245, 245, 255); +} +QPushButton#clearSearchButton:pressed{ + background-color: rgb(255, 255, 255); +} + Clear search criteria + + false + + + + + + + + 0 + 40 + + + + QPushButton#deleteRecipeButton { + background-color: rgb(225, 225, 255); + border: 0px; +} +QPushButton#deleteRecipeButton:hover{ + background-color: rgb(235, 235, 255); +} +QPushButton#deleteRecipeButton:pressed{ + background-color: rgb(245, 245, 255); +} + + + + + + + :/images/images/trash.png:/images/images/trash.png + + + + + + + + 0 + 40 + + + + QPushButton#exitButton { + background-color: rgb(215, 215, 255); + border: 0px; +} +QPushButton#exitButton:hover{ + background-color: rgb(225, 225, 255); +} +QPushButton#exitButton:pressed{ + background-color: rgb(235, 235, 255); +} + + + Close + @@ -159,34 +351,55 @@ - - - - - - - - - - - :/images/images/trash.png:/images/images/trash.png - - - - - - + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + 14 + 3 + false + false + + + + background-color: rgb(250, 250, 255); + QFrame::NoFrame + + true + QAbstractItemView::SelectRows + + QAbstractItemView::ScrollPerPixel + + + Qt::NoPen + true + + false + false diff --git a/images.qrc b/images.qrc index a5261a4..de44dca 100644 --- a/images.qrc +++ b/images.qrc @@ -6,5 +6,8 @@ images/minus_icon.png images/search_icon.png images/trash.png + images/tag.png + images/foodPyramid.png + images/ingredients.png diff --git a/images/foodPyramid.png b/images/foodPyramid.png new file mode 100644 index 0000000000000000000000000000000000000000..10ecfa6f08448d12d41ab8e262c0c97f5865fbda GIT binary patch literal 7405 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 v*TfF@r_MClx literal 0 HcmV?d00001 diff --git a/images/ingredients.png b/images/ingredients.png new file mode 100644 index 0000000000000000000000000000000000000000..5d8e207ba482506901d4820fe009212a3cd0fc4d GIT binary patch literal 18033 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 literal 0 HcmV?d00001 diff --git a/images/tag.png b/images/tag.png new file mode 100644 index 0000000000000000000000000000000000000000..544ec9a7daaa21cbfe40d6678b06416a7852dff4 GIT binary patch literal 1408 zcmc&!e@s+rLOI&JbtnkL{FwmIQdo3Ihwt1su)+8WaUOY}WWvkB|QVtd(DI(JroIHNziclUhn z_w)UH&-r|}@Aq_WF5O^DcsKz7Y(*QTEdYr9A|PgF%IiLTof-41#Tz78Fy5Zg<6R7i zuif}^J%H6|dCGHSe?D?RqO|_!>*U|9N8FfF8bbIm6!F^2_Mh-sq~@6X0sf7hKTl zF>`UU)=sM-QE~A$8eNKyA+PSHAaED39J?1Gq5bExsvXrwPobQ0AF6>Vjt7Z;^N{*V zUAk(fC4fW&SzxV8)QdAhrq5VgN`uG{hgkwow< z`>-m#(7`v4Je6KxDHlwtwg;FXznUUIM+L_yJaX{xY>*JZk6;$!5dwzPxZMH>7)MU- zVWA)e%1l!R%H*L<1ET!aVZqZSsu`k7uV~($k@zVP z^tr>y^y|U0mg!BYn`>CRvnffG$g$VTpbUr56sO@Y*s7Xkr&@es6tQlHGW1vlDmQ5d zlEEP;z1!RURGZ(*n){|^rG#G|Ks-7V_JCV#h?zu=*cEnQlqEWZTpbIz?Sl$N*8W%W zxt(dppBoAneiW{93Tc&w;vWvO&OdFh2y-Hrs0<=%mj@qqAk!Pe;yObC=};q9=ykao?!U^I aPY2(FKW>Yce;-mtjc}2_R2nFdTmA%q&$m(l literal 0 HcmV?d00001 diff --git a/main.cpp b/main.cpp index 940cd9d..42982af 100644 --- a/main.cpp +++ b/main.cpp @@ -16,7 +16,7 @@ int main(int argc, char *argv[]) w.show(); //TESTING CODE - test(&recipeDB); + //test(&recipeDB); //END TESTING CODE. @@ -24,7 +24,6 @@ int main(int argc, char *argv[]) a.exec(); recipeDB.closeConnection(); - printf("Total queries: %lu\n", recipeDB.getQueryCount()); return 0; } @@ -48,18 +47,4 @@ void test(RecipeDatabase *recipeDB){ bool success = recipeDB->storeRecipe(rec); printf("Storage successful: %d\n", success); -// vector foodGroups = recipeDB->retrieveAllFoodGroups(); -// printf("Food Groups:\n"); -// for (string s : foodGroups){ -// printf("\t%s\n", s.c_str()); -// } - - //Get food groups from recipe. -// Recipe r = recipeDB->retrieveRecipe("Pannenkoeken"); -// vector foodGroupsR = r.getFoodGroups(); -// printf("Pannenkoeken Food Groups:\n"); -// for (string s : foodGroupsR){ -// printf("\t%s\n", s.c_str()); -// } - } diff --git a/model/database/recipedatabase.cpp b/model/database/recipedatabase.cpp index 0ebd528..42bf4ff 100644 --- a/model/database/recipedatabase.cpp +++ b/model/database/recipedatabase.cpp @@ -204,7 +204,7 @@ vector RecipeDatabase::retrieveRecipesWithTags(vector tags){ } vector RecipeDatabase::retrieveRecipesWithSubstring(string s){ - ResultTable t = this->executeSQL("SELECT * FROM recipe WHERE name LIKE '%"+s+"%' COLLATE NOCASE;"); + ResultTable t = this->executeSQL("SELECT * FROM recipe WHERE name LIKE '%"+s+"%' COLLATE NOCASE ORDER BY name;"); return this->readRecipesFromTable(t); } diff --git a/model/recipe/recipe.cpp b/model/recipe/recipe.cpp index a899c1f..fb1e545 100644 --- a/model/recipe/recipe.cpp +++ b/model/recipe/recipe.cpp @@ -12,7 +12,7 @@ Recipe::Recipe(string name, vector ingredients, Instruction in setServings(servings); } -Recipe::Recipe() : Recipe::Recipe("", vector(), Instruction(), QImage(), vector(), QDate::currentDate(), QTime(1, 0), QTime(0, 30), 10.0f){ +Recipe::Recipe() : Recipe::Recipe("", vector(), Instruction(), QImage(), vector(), QDate::currentDate(), QTime(), QTime(), 1.0f){ //Set default values when none are specified. } -- 2.34.1 From c84370be1180f97d8bf45d5803171530d03d52e7 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 31 Mar 2018 15:02:35 +0200 Subject: [PATCH 3/3] Updated UI, added default ingredients. --- gui/mainwindow.cpp | 31 +++--- gui/mainwindow.h | 1 + gui/mainwindow.ui | 2 +- gui/newrecipedialog.cpp | 2 + gui/newrecipedialog.ui | 150 +++++++++++++++++++++++++++++- main.cpp | 24 +++-- model/database/recipedatabase.cpp | 79 +++++++++++++--- model/database/recipedatabase.h | 4 + model/recipe/recipe.cpp | 20 +++- model/recipe/recipe.h | 5 +- 10 files changed, 272 insertions(+), 46 deletions(-) diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index bc09378..c552432 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -19,19 +19,24 @@ MainWindow::~MainWindow(){ } void MainWindow::loadFromRecipe(Recipe recipe){ - setRecipeName(recipe.getName()); - setInstruction(recipe.getInstruction()); - setIngredients(recipe.getIngredients()); - if (recipe.getImage().isNull()){ - setImage(QImage(QString(":/images/images/no_image.png"))); + if (recipe.isEmpty()){ + setRecipeName("No recipes found."); + setAuthorName("Click 'New' to get started."); } else { - setImage(recipe.getImage()); + setRecipeName(recipe.getName()); + setInstruction(recipe.getInstruction()); + setIngredients(recipe.getIngredients()); + if (recipe.getImage().isNull()){ + setImage(QImage(QString(":/images/images/no_image.png"))); + } else { + setImage(recipe.getImage()); + } + setPrepTime(recipe.getPrepTime()); + setCookTime(recipe.getCookTime()); + setServings(recipe.getServings()); + setTags(recipe.getTags()); + this->currentRecipe = recipe; } - setPrepTime(recipe.getPrepTime()); - setCookTime(recipe.getCookTime()); - setServings(recipe.getServings()); - setTags(recipe.getTags()); - this->currentRecipe = recipe; } void MainWindow::setRecipeName(string name){ @@ -66,6 +71,10 @@ void MainWindow::setTags(vector tags){ this->tagsListModel.setTags(tags); } +void MainWindow::setAuthorName(string name){ + ui->authorLabel->setText(QString::fromStdString(name)); +} + void MainWindow::on_newButton_clicked(){ NewRecipeDialog d(this->recipeDB, this); d.show(); diff --git a/gui/mainwindow.h b/gui/mainwindow.h index 3ce5cf4..70ab7be 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -54,6 +54,7 @@ public: void setCookTime(QTime cookTime); void setServings(float servings); void setTags(vector tags); + void setAuthorName(string name); }; #endif // MAINWINDOW_H diff --git a/gui/mainwindow.ui b/gui/mainwindow.ui index 23922da..69bddb9 100644 --- a/gui/mainwindow.ui +++ b/gui/mainwindow.ui @@ -364,7 +364,7 @@ font: "Noto Sans CJK KR"; - false + true diff --git a/gui/newrecipedialog.cpp b/gui/newrecipedialog.cpp index ac3258c..fc8657f 100644 --- a/gui/newrecipedialog.cpp +++ b/gui/newrecipedialog.cpp @@ -23,6 +23,7 @@ NewRecipeDialog::NewRecipeDialog(RecipeDatabase *db, QWidget *parent) : NewRecip NewRecipeDialog::NewRecipeDialog(RecipeDatabase *db, Recipe recipe, QWidget *parent) : NewRecipeDialog(db, parent){ ui->recipeNameEdit->setText(QString::fromStdString(recipe.getName())); + ui->authorNameEdit->setText(QString::fromStdString(recipe.getAuthor())); ui->prepTimeEdit->setTime(recipe.getPrepTime()); ui->cookTimeEdit->setTime(recipe.getCookTime()); ui->servingsSpinBox->setValue((double)recipe.getServings()); @@ -38,6 +39,7 @@ NewRecipeDialog::~NewRecipeDialog(){ Recipe NewRecipeDialog::getRecipe(){ Recipe r(ui->recipeNameEdit->text().toStdString(), + ui->authorNameEdit->text().toStdString(), this->ingredientListModel.getIngredients(), ui->instructionsTextEdit->toHtml().toStdString(), ui->imageDisplayLabel->pixmap()->toImage(),//Image diff --git a/gui/newrecipedialog.ui b/gui/newrecipedialog.ui index a42a7a2..50bee64 100644 --- a/gui/newrecipedialog.ui +++ b/gui/newrecipedialog.ui @@ -96,15 +96,25 @@ + + 0 + + 14 3 false false + + background-color: rgb(255, 255, 255); + + + false + Qt::AlignCenter @@ -113,6 +123,30 @@ + + + + + 12 + 3 + false + false + + + + background-color: rgb(245, 245, 255); + + + false + + + Qt::AlignCenter + + + Author + + + @@ -141,7 +175,7 @@ 0 1999 12 - 23 + 22 @@ -252,6 +286,9 @@ + + 2 + 0 @@ -285,6 +322,9 @@ Create a new tag + + background-color: rgb(255, 255, 255); + :/images/images/plus_icon.png:/images/images/plus_icon.png @@ -296,6 +336,9 @@ Permanently delete this tag + + background-color: rgb(255, 255, 255); + :/images/images/minus_icon.png:/images/images/minus_icon.png @@ -308,6 +351,9 @@ + + 0 + 0 @@ -322,6 +368,27 @@ + + + 0 + 20 + + + + Add the above tag to the recipe + + + QPushButton#addTagButton { + background-color: rgb(235, 235, 255); + border: 0px; +} +QPushButton#addTagButton:hover{ + background-color: rgb(245, 245, 255); +} +QPushButton#addTagButton:pressed{ + background-color: rgb(255, 255, 255); +} + Add @@ -329,8 +396,29 @@ + + + 0 + 20 + + + + Remove this tag from the recipe + + + QPushButton#deleteTagButton { + background-color: rgb(225, 225, 255); + border: 0px; +} +QPushButton#deleteTagButton:hover{ + background-color: rgb(235, 235, 255); +} +QPushButton#deleteTagButton:pressed{ + background-color: rgb(245, 245, 255); +} + - Delete + Remove @@ -434,7 +522,7 @@ - Add Ingredient + Ingredients Qt::AlignCenter @@ -497,6 +585,9 @@ + + Delete this ingredient + @@ -581,6 +672,9 @@ + + Create a new unit of measure + :/images/images/plus_icon.png:/images/images/plus_icon.png @@ -589,6 +683,9 @@ + + Delete this unit of measure + @@ -626,6 +723,9 @@ + + 0 + 0 @@ -640,6 +740,27 @@ + + + 0 + 30 + + + + Add the above ingredient to the recipe + + + QPushButton#addIngredientButton { + background-color: rgb(235, 235, 255); + border: 0px; +} +QPushButton#addIngredientButton:hover{ + background-color: rgb(245, 245, 255); +} +QPushButton#addIngredientButton:pressed{ + background-color: rgb(255, 255, 255); +} + Add @@ -647,8 +768,29 @@ + + + 0 + 30 + + + + Remove this ingredient from the recipe + + + QPushButton#removeIngredientButton { + background-color: rgb(225, 225, 255); + border: 0px; +} +QPushButton#removeIngredientButton:hover{ + background-color: rgb(235, 235, 255); +} +QPushButton#removeIngredientButton:pressed{ + background-color: rgb(245, 245, 255); +} + - Delete + Remove diff --git a/main.cpp b/main.cpp index 42982af..cdd2938 100644 --- a/main.cpp +++ b/main.cpp @@ -8,19 +8,24 @@ void test(RecipeDatabase *recipeDB); +Recipe checkForFirstRun(RecipeDatabase *recipeDB){ + Recipe r = recipeDB->retrieveRandomRecipe(); + if (r.isEmpty()){//There are no recipes in the database. + //Add some basic units to the units, and some basic ingredients. + recipeDB->addBasicUnits(); + recipeDB->addBasicIngredients(); + } + return r; +} + int main(int argc, char *argv[]) { RecipeDatabase recipeDB(QString(FileUtils::appDataPath+"recipes.db").toStdString()); - QApplication a(argc, argv); + + QApplication a(argc, argv); MainWindow w(&recipeDB); - w.show(); - - //TESTING CODE - //test(&recipeDB); - - //END TESTING CODE. - - w.loadFromRecipe(recipeDB.retrieveRandomRecipe()); + w.loadFromRecipe(checkForFirstRun(&recipeDB)); + w.show(); a.exec(); recipeDB.closeConnection(); @@ -34,6 +39,7 @@ void test(RecipeDatabase *recipeDB){ ri.push_back(RecipeIngredient("baking powder", "additives", 1.0f, UnitOfMeasure("teaspoon", "teaspoons", "tsp", UnitOfMeasure::VOLUME, 1.0), "")); Recipe rec("Example", + "Andrew Lalis", ri, Instruction("Placeholder Text"), QImage(), diff --git a/model/database/recipedatabase.cpp b/model/database/recipedatabase.cpp index 42bf4ff..bae0eae 100644 --- a/model/database/recipedatabase.cpp +++ b/model/database/recipedatabase.cpp @@ -12,7 +12,7 @@ bool RecipeDatabase::storeRecipe(Recipe recipe){ return false; } //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;"); + this->beginTransaction(); 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()); @@ -20,6 +20,7 @@ bool RecipeDatabase::storeRecipe(Recipe recipe){ bool success = this->insertInto("recipe", vector({ "name", + "authorName", "createdDate", "cookTime", "prepTime", @@ -27,6 +28,7 @@ bool RecipeDatabase::storeRecipe(Recipe recipe){ }), vector({ recipe.getName(), + recipe.getAuthor(), recipe.getCreatedDate().toString().toStdString(), recipe.getCookTime().toString().toStdString(), recipe.getPrepTime().toString().toStdString(), @@ -46,12 +48,12 @@ bool RecipeDatabase::storeRecipe(Recipe recipe){ this->storeInstruction(recipe.getInstruction(), recipeId) && this->storeImage(recipe.getImage(), recipeId) && this->storeTags(recipe.getTags(), recipeId)){ - this->executeSQL("COMMIT;"); + this->commitTransaction(); return true; } } } - this->executeSQL("ROLLBACK;"); + this->rollbackTransaction(); return false; } @@ -158,7 +160,7 @@ Recipe RecipeDatabase::retrieveRandomRecipe(){ } return this->readFromResultTable(t); } -//TODO: Change this to be more efficient! One query per recipe is not good! + vector RecipeDatabase::retrieveAllRecipes(){ ResultTable t = this->executeSQL("SELECT * FROM recipe ORDER BY name;"); return this->readRecipesFromTable(t); @@ -326,7 +328,7 @@ bool RecipeDatabase::deleteRecipe(int recipeId){ printf("Cannot delete. No recipe with ID %d exists.\n", recipeId); return false; } - this->executeSQL("BEGIN;"); + this->beginTransaction(); bool tagsDeleted = this->deleteFrom("recipeTag", "WHERE recipeId="+idString); bool recipeIngredientDeleted = this->deleteFrom("recipeIngredient", "WHERE recipeId="+idString); bool recipeDeleted = this->deleteFrom("recipe", "WHERE recipeId="+idString); @@ -335,10 +337,10 @@ bool RecipeDatabase::deleteRecipe(int recipeId){ Q_UNUSED(instructionDeleted); Q_UNUSED(imageDeleted); if (tagsDeleted && recipeIngredientDeleted && recipeDeleted){ - this->executeSQL("COMMIT;"); + this->commitTransaction(); return true; } else { - this->executeSQL("ROLLBACK;"); + this->rollbackTransaction(); return false; } } @@ -374,6 +376,7 @@ bool RecipeDatabase::updateRecipe(Recipe recipe, string originalName) { this->beginTransaction(); ResultTable t = this->executeSQL("UPDATE recipe " "SET name = '"+recipe.getName()+"', " + "authorName = '"+recipe.getAuthor()+"', " "createdDate = '"+recipe.getCreatedDate().toString().toStdString()+"', " "prepTime = '"+recipe.getPrepTime().toString().toStdString()+"', " "cookTime = '"+recipe.getCookTime().toString().toStdString()+"', " @@ -435,11 +438,55 @@ bool RecipeDatabase::updateRecipe(Recipe recipe, string originalName) { } } +bool RecipeDatabase::addBasicUnits(){ + this->beginTransaction(); + //Volume + this->storeUnitOfMeasure(UnitOfMeasure("Teaspoon", "Teaspoons", "tsp", UnitOfMeasure::VOLUME, 5.0)); + this->storeUnitOfMeasure(UnitOfMeasure("Tablespoon", "Tablespoons", "tbsp", UnitOfMeasure::VOLUME, 15.0)); + this->storeUnitOfMeasure(UnitOfMeasure("Fluid Ounce", "Fluid Ounces", "fl oz", UnitOfMeasure::VOLUME, 30.0)); + this->storeUnitOfMeasure(UnitOfMeasure("Cup", "Cups", "c", UnitOfMeasure::VOLUME, 250.0)); + this->storeUnitOfMeasure(UnitOfMeasure("Milliliter", "Milliliters", "mL", UnitOfMeasure::VOLUME, 1.0)); + this->storeUnitOfMeasure(UnitOfMeasure("Liter", "Liters", "L", UnitOfMeasure::VOLUME, 1000.0)); + this->storeUnitOfMeasure(UnitOfMeasure("Gallon", "Gallons", "gal", UnitOfMeasure::VOLUME, 3800.0)); + //Mass/Weight + this->storeUnitOfMeasure(UnitOfMeasure("Ounce", "Ounces", "oz", UnitOfMeasure::MASS, 28.0)); + this->storeUnitOfMeasure(UnitOfMeasure("Pound", "Pounds", "lb", UnitOfMeasure::MASS, 454.0)); + this->storeUnitOfMeasure(UnitOfMeasure("Gram", "Grams", "g", UnitOfMeasure::MASS, 1.0)); + this->storeUnitOfMeasure(UnitOfMeasure("Milligram", "Milligrams", "mg", UnitOfMeasure::MASS, 0.001)); + this->storeUnitOfMeasure(UnitOfMeasure("Kilogram", "Kilograms", "kg", UnitOfMeasure::MASS, 1000.0)); + //Length + this->storeUnitOfMeasure(UnitOfMeasure("Inch", "Inches", "in", UnitOfMeasure::LENGTH, 2.54)); + this->storeUnitOfMeasure(UnitOfMeasure("Centimeter", "Centimeters", "cm", UnitOfMeasure::LENGTH, 1.0)); + //MISC + this->storeUnitOfMeasure(UnitOfMeasure("Piece", "Pieces", "pc", UnitOfMeasure::MISC, 1.0)); + this->storeUnitOfMeasure(UnitOfMeasure("Item", "Items", "", UnitOfMeasure::MISC, 1.0)); + this->commitTransaction(); + return true; +} + +bool RecipeDatabase::addBasicIngredients(){ + this->beginTransaction(); + this->storeIngredient(Ingredient("Flour", "grains")); + this->storeIngredient(Ingredient("Eggs", "eggs")); + this->storeIngredient(Ingredient("Milk", "dairy")); + this->storeIngredient(Ingredient("Cheese", "dairy")); + this->storeIngredient(Ingredient("Salt", "spices")); + this->storeIngredient(Ingredient("Sugar", "sugars")); + this->storeIngredient(Ingredient("Vegetable Oil", "oils")); + this->storeIngredient(Ingredient("Olive Oil", "oils")); + this->storeIngredient(Ingredient("Water", "water")); + this->storeIngredient(Ingredient("Bell Pepper", "vegetables")); + this->storeIngredient(Ingredient("Onion", "vegetables")); + this->storeIngredient(Ingredient("Garlic", "spices")); + this->commitTransaction(); + return true; +} + void RecipeDatabase::ensureTablesExist(){ //Make sure that foreign keys are enabled. this->executeSQL("PRAGMA foreign_keys = ON;"); - this->executeSQL("BEGIN;"); + this->beginTransaction(); //Ingredients table. this->executeSQL("CREATE TABLE IF NOT EXISTS ingredient(" "ingredientId INTEGER PRIMARY KEY," @@ -456,6 +503,7 @@ void RecipeDatabase::ensureTablesExist(){ this->executeSQL("CREATE TABLE IF NOT EXISTS recipe(" "recipeId INTEGER PRIMARY KEY," "name varchar UNIQUE," + "authorName varchar," "createdDate date," "prepTime time," "cookTime time," @@ -475,18 +523,19 @@ void RecipeDatabase::ensureTablesExist(){ "FOREIGN KEY (ingredientId) REFERENCES ingredient(ingredientId)," "FOREIGN KEY (recipeId) REFERENCES recipe(recipeId)," "FOREIGN KEY (unitName) REFERENCES unitOfMeasure(name));"); - this->executeSQL("COMMIT;"); + this->commitTransaction(); } Recipe RecipeDatabase::readFromResultTable(ResultTable t, int tRow){ Recipe r; TableRow row = t.rows().at(tRow); - int id = std::stoi(row.at(0)); - r.setName(row.at(1)); - r.setCreatedDate(QDate::fromString(QString::fromStdString(row.at(2)))); - r.setPrepTime(QTime::fromString(QString::fromStdString(row.at(3)))); - r.setCookTime(QTime::fromString(QString::fromStdString(row.at(4)))); - r.setServings(std::stof(row.at(5))); + int id = std::stoi(row.at(0)); //id + r.setName(row.at(1)); //Name + r.setAuthor(row.at(2)); //author + r.setCreatedDate(QDate::fromString(QString::fromStdString(row.at(3)))); //createdDate + r.setPrepTime(QTime::fromString(QString::fromStdString(row.at(4)))); //prepTime + r.setCookTime(QTime::fromString(QString::fromStdString(row.at(5)))); //cookTime + r.setServings(std::stof(row.at(6))); //servings r.setInstruction(FileUtils::loadInstruction(id)); r.setImage(FileUtils::loadImage(id)); r.setIngredients(this->retrieveRecipeIngredients(id)); diff --git a/model/database/recipedatabase.h b/model/database/recipedatabase.h index 2b80d95..2452e42 100644 --- a/model/database/recipedatabase.h +++ b/model/database/recipedatabase.h @@ -49,6 +49,10 @@ class RecipeDatabase : public Database //Updating. bool updateRecipe(Recipe recipe, string originalName); + + //Adding basic information at start. + bool addBasicUnits(); + bool addBasicIngredients(); private: //Utility methods. diff --git a/model/recipe/recipe.cpp b/model/recipe/recipe.cpp index fb1e545..8d123e7 100644 --- a/model/recipe/recipe.cpp +++ b/model/recipe/recipe.cpp @@ -1,7 +1,8 @@ #include "model/recipe/recipe.h" -Recipe::Recipe(string name, vector ingredients, Instruction instruction, QImage image, vector tags, QDate createdDate, QTime prepTime, QTime cookTime, float servings){ +Recipe::Recipe(string name, string author, vector ingredients, Instruction instruction, QImage image, vector tags, QDate createdDate, QTime prepTime, QTime cookTime, float servings){ setName(name); + setAuthor(author); setIngredients(ingredients); setInstruction(instruction); setImage(image); @@ -12,12 +13,16 @@ Recipe::Recipe(string name, vector ingredients, Instruction in setServings(servings); } -Recipe::Recipe() : Recipe::Recipe("", vector(), Instruction(), QImage(), vector(), QDate::currentDate(), QTime(), QTime(), 1.0f){ +Recipe::Recipe() : Recipe::Recipe("", "", vector(), Instruction(), QImage(), vector(), QDate::currentDate(), QTime(), QTime(), 1.0f){ //Set default values when none are specified. } string Recipe::getName() const{ - return this->name; + return this->name; +} + +string Recipe::getAuthor() const{ + return this->authorName; } vector Recipe::getIngredients() const{ @@ -71,7 +76,11 @@ bool Recipe::isEmpty() const{ } void Recipe::setName(string newName){ - this->name = newName; + this->name = newName; +} + +void Recipe::setAuthor(string newName){ + this->authorName = newName; } void Recipe::setIngredients(vector ingredients){ @@ -111,8 +120,9 @@ void Recipe::setServings(float newServingsCount){ } void Recipe::print(){ - printf("Recipe: %s, Created on: %s, Prep time: %s, Cook time: %s, Serves: %f\n", + printf("Recipe: %s, Created on: %s, by %s, Prep time: %s, Cook time: %s, Serves: %f\n", this->name.c_str(), + this->authorName.c_str(), this->createdDate.toString().toStdString().c_str(), this->prepTime.toString().toStdString().c_str(), this->cookTime.toString().toStdString().c_str(), diff --git a/model/recipe/recipe.h b/model/recipe/recipe.h index 3bdb92d..4d8dca3 100644 --- a/model/recipe/recipe.h +++ b/model/recipe/recipe.h @@ -32,12 +32,13 @@ class Recipe { public: //Full constructor - Recipe(string name, vector ingredients, Instruction instruction, QImage image, vector tags, QDate createdDate, QTime prepTime, QTime cookTime, float servings); + Recipe(string name, string author, vector ingredients, Instruction instruction, QImage image, vector tags, QDate createdDate, QTime prepTime, QTime cookTime, float servings); //Constructor with default values. Recipe(); //Getters string getName() const; + string getAuthor() const; vector getIngredients() const; vector getFoodGroups() const; Instruction getInstruction() const; @@ -52,6 +53,7 @@ public: //Setters void setName(string newName); + void setAuthor(string newName); void setIngredients(vector ingredients); void setTags(vector tags); void addIngredient(RecipeIngredient newIngredient); @@ -66,6 +68,7 @@ public: private: //Main information. string name; //The name of the recipe. + string authorName; //The name of the author of this recipe. vector ingredients; //The list of ingredients in the recipe. Instruction instruction; //The instruction HTML document. QImage image; //An image displayed alongside the recipe. -- 2.34.1