diff --git a/README.md b/README.md index b84335d..e46c739 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Recipe DB +![screenshot](http://i.imgur.com/FbltFit.png) + Recipe DB is a simple, lightweight database powered by Qt and SQLite to allow you to save, retrieve, and search through many recipes without needing to be connected to the internet. Meant as a tool for those who enjoy making recipes their own, this tool lets you edit recipes, search by ingredients or food groups, and filter out recipes that meet certain criteria. Recipe DB was created initially out of a desire to organize my many recipes in one uniform way, as simply as possible, while still making sure that retrieving any given recipe is as effortless as possible. I wanted something where I could save recipes offline, change them if I need to, convert units on-the-fly, and add a few other small utilities that would save a few precious minutes in the kitchen. ### How it works diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 7db6bbe..d2de990 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -31,6 +31,7 @@ void MainWindow::loadFromRecipe(Recipe recipe){ setCookTime(recipe.getCookTime()); setServings(recipe.getServings()); setTags(recipe.getTags()); + this->currentRecipe = recipe; } void MainWindow::setRecipeName(string name){ @@ -91,3 +92,17 @@ void MainWindow::on_openButton_clicked(){ void MainWindow::on_exitButton_clicked(){ this->close(); } + +void MainWindow::on_editButton_clicked(){ + NewRecipeDialog d(this->recipeDB, this->currentRecipe, this); + d.show(); + d.exec(); + if (d.isAccepted()){ + Recipe r = d.getRecipe(); + if (!this->recipeDB->storeRecipe(r)){ + 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/mainwindow.h b/gui/mainwindow.h index 03e802a..3ce5cf4 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -36,11 +36,14 @@ public: void on_exitButton_clicked(); + void on_editButton_clicked(); + private: Ui::MainWindow *ui; RecipeDatabase *recipeDB; RecipeIngredientListModel ingredientModel; TagListModel tagsListModel; + Recipe currentRecipe; //Hidden manipulation methods. void setRecipeName(string name); diff --git a/gui/mainwindow.ui b/gui/mainwindow.ui index 4ac1f88..23922da 100644 --- a/gui/mainwindow.ui +++ b/gui/mainwindow.ui @@ -180,9 +180,9 @@ QPushButton#openButton:pressed{ - + - false + true @@ -201,19 +201,19 @@ QPushButton#openButton:pressed{ false - QPushButton#browseButton { + QPushButton#editButton { background-color: rgb(215, 215, 255); border: 0px; } -QPushButton#browseButton:hover{ +QPushButton#editButton:hover{ background-color: rgb(225, 225, 255); } -QPushButton#browseButton:pressed{ +QPushButton#editButton:pressed{ background-color: rgb(255, 255, 255); } - Browse + Edit false @@ -363,6 +363,9 @@ font: "Noto Sans CJK KR"; + + false + 0 diff --git a/gui/newrecipedialog.cpp b/gui/newrecipedialog.cpp index c105333..3825ad1 100644 --- a/gui/newrecipedialog.cpp +++ b/gui/newrecipedialog.cpp @@ -21,6 +21,17 @@ NewRecipeDialog::NewRecipeDialog(RecipeDatabase *db, QWidget *parent) : NewRecip this->populateTagsBox(); } +NewRecipeDialog::NewRecipeDialog(RecipeDatabase *db, Recipe recipe, QWidget *parent) : NewRecipeDialog(db, parent){ + ui->recipeNameEdit->setText(QString::fromStdString(recipe.getName())); + ui->prepTimeEdit->setTime(recipe.getPrepTime()); + ui->cookTimeEdit->setTime(recipe.getCookTime()); + ui->servingsSpinBox->setValue((double)recipe.getServings()); + ui->instructionsTextEdit->setHtml(QString::fromStdString(recipe.getInstruction().getHTML())); + ui->imageDisplayLabel->setPixmap(QPixmap::fromImage(recipe.getImage())); + this->tagsListModel.setTags(recipe.getTags()); + this->ingredientListModel.setIngredients(recipe.getIngredients()); +} + NewRecipeDialog::~NewRecipeDialog(){ delete ui; } diff --git a/gui/newrecipedialog.h b/gui/newrecipedialog.h index 181cea2..105cd37 100644 --- a/gui/newrecipedialog.h +++ b/gui/newrecipedialog.h @@ -26,6 +26,7 @@ class NewRecipeDialog : public QDialog public: explicit NewRecipeDialog(QWidget *parent = 0); NewRecipeDialog(RecipeDatabase *db, QWidget *parent = 0); + NewRecipeDialog(RecipeDatabase *db, Recipe recipe, QWidget *parent = 0); ~NewRecipeDialog(); Recipe getRecipe(); diff --git a/gui/openrecipedialog.cpp b/gui/openrecipedialog.cpp index 648beb9..184ea18 100644 --- a/gui/openrecipedialog.cpp +++ b/gui/openrecipedialog.cpp @@ -11,17 +11,22 @@ OpenRecipeDialog::OpenRecipeDialog(QWidget *parent) : ui->ingredientsListView->setModel(&this->ingredientsModel); ui->tagsListView->setModel(&this->tagsModel); - connect(ui->ingredientsListView->selectionModel(), + QObject::connect(ui->ingredientsListView->selectionModel(), + SIGNAL(selectionChanged(QItemSelection, QItemSelection)), + this, + SLOT(onIngredientsListViewSelectionChanged(QItemSelection))); + QObject::connect(ui->tagsListView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, - SLOT(on_ingredientsListView_selectionChanged(QItemSelection))); + SLOT(onTagsListViewSelectionChanged(QItemSelection))); } OpenRecipeDialog::OpenRecipeDialog(RecipeDatabase *recipeDB, QWidget *parent) : OpenRecipeDialog(parent){ this->recipeDB = recipeDB; this->populateIngredientsList(); this->populateTagsList(); - this->populateRecipesTable(); + this->populateFoodGroupsList(); + this->populateRecipesTable(this->recipeDB->retrieveAllRecipes()); } OpenRecipeDialog::~OpenRecipeDialog() @@ -33,9 +38,8 @@ Recipe OpenRecipeDialog::getSelectedRecipe(){ return this->selectedRecipe; } -void OpenRecipeDialog::populateRecipesTable(){ +void OpenRecipeDialog::populateRecipesTable(vector recipes){ this->recipeTableModel.clear(); - vector recipes = this->recipeDB->retrieveAllRecipes(); this->recipeTableModel.setRecipes(recipes); ui->recipeTableView->resizeColumnsToContents(); ui->recipeTableView->show(); @@ -49,6 +53,13 @@ void OpenRecipeDialog::populateTagsList(){ this->tagsModel.setTags(this->recipeDB->retrieveAllTags()); } +void OpenRecipeDialog::populateFoodGroupsList(){ + for (string s : this->recipeDB->retrieveAllFoodGroups()){ + ui->foodGroupsListWidget->addItem(QString::fromStdString(s)); + } + //ui->foodGroupsListWidget->show(); +} + void OpenRecipeDialog::on_deleteRecipeButton_clicked(){ QItemSelectionModel *selectModel = ui->recipeTableView->selectionModel(); if (!selectModel->hasSelection()){ @@ -69,9 +80,10 @@ void OpenRecipeDialog::on_deleteRecipeButton_clicked(){ bool success = this->recipeDB->deleteRecipe(r.getName()); if (!success){ QMessageBox::critical(this, QString::fromStdString("Unable to Delete"), QString::fromStdString("Could not delete recipe "+r.getName())); + } else { + this->populateRecipesTable(this->recipeDB->retrieveAllRecipes()); } } - this->populateRecipesTable(); } } @@ -80,13 +92,38 @@ void OpenRecipeDialog::on_recipeTableView_doubleClicked(const QModelIndex &index this->close(); } -void OpenRecipeDialog::on_ingredientsListView_selectionChanged(const QItemSelection &selection){ - printf("Selection changed!\n"); +void OpenRecipeDialog::onIngredientsListViewSelectionChanged(const QItemSelection &selection){ + Q_UNUSED(selection); vector ingredients; - for (QModelIndex index : selection.indexes()){ + QModelIndexList indexes = ui->ingredientsListView->selectionModel()->selectedRows(); + for (QModelIndex index : indexes){ Ingredient i = this->ingredientsModel.getIngredients().at(index.row()); ingredients.push_back(i); - printf("Selected: %s\n", i.getName().c_str()); } - + this->populateRecipesTable(this->recipeDB->retrieveRecipesWithIngredients(ingredients)); +} + +void OpenRecipeDialog::onTagsListViewSelectionChanged(const QItemSelection &selection){ + Q_UNUSED(selection); + vector tags; + QModelIndexList indexes = ui->tagsListView->selectionModel()->selectedRows(); + for (QModelIndex index : indexes){ + RecipeTag t = this->tagsModel.getTags().at(index.row()); + tags.push_back(t); + } + this->populateRecipesTable(this->recipeDB->retrieveRecipesWithTags(tags)); +} + +void OpenRecipeDialog::on_nameEdit_textChanged(const QString &arg1){ + Q_UNUSED(arg1); + this->populateRecipesTable(this->recipeDB->retrieveRecipesWithSubstring(ui->nameEdit->text().toStdString())); +} + +void OpenRecipeDialog::on_foodGroupsListWidget_itemSelectionChanged(){ + vector groups; + for (QModelIndex index : ui->foodGroupsListWidget->selectionModel()->selectedRows()){ + QListWidgetItem *item = ui->foodGroupsListWidget->item(index.row()); + groups.push_back(item->text().toStdString()); + } + this->populateRecipesTable(this->recipeDB->retrieveRecipesWithFoodGroups(groups)); } diff --git a/gui/openrecipedialog.h b/gui/openrecipedialog.h index 44c56f9..8e63496 100644 --- a/gui/openrecipedialog.h +++ b/gui/openrecipedialog.h @@ -30,7 +30,13 @@ class OpenRecipeDialog : public QDialog void on_recipeTableView_doubleClicked(const QModelIndex &index); - void on_ingredientsListView_selectionChanged(const QItemSelection &selection); + void onIngredientsListViewSelectionChanged(const QItemSelection &selection); + + void onTagsListViewSelectionChanged(const QItemSelection &selection); + + void on_nameEdit_textChanged(const QString &arg1); + + void on_foodGroupsListWidget_itemSelectionChanged(); private: Ui::OpenRecipeDialog *ui; @@ -41,9 +47,10 @@ class OpenRecipeDialog : public QDialog IngredientListModel ingredientsModel; TagListModel tagsModel; - void populateRecipesTable(); + void populateRecipesTable(vector recipes); void populateIngredientsList(); void populateTagsList(); + void populateFoodGroupsList(); }; #endif // OPENRECIPEDIALOG_H diff --git a/gui/openrecipedialog.ui b/gui/openrecipedialog.ui index 0e51715..93cb94b 100644 --- a/gui/openrecipedialog.ui +++ b/gui/openrecipedialog.ui @@ -6,7 +6,7 @@ 0 0 - 1000 + 1064 480 @@ -25,73 +25,109 @@ - - - - - - Tag - - - - - - - - 0 - 0 - - - - QFrame::NoFrame - - - QAbstractItemView::ExtendedSelection - - - false - - - false - - - - - - - - - - - - - Ingredient - - - - - - - - 0 - 0 - - - - QFrame::NoFrame - - - QAbstractItemView::ExtendedSelection - - - false - - - true - - - - + + + + 290 + 0 + + + + QTabWidget::South + + + QTabWidget::Rounded + + + 2 + + + + Tags + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::ScrollPerPixel + + + false + + + true + + + + + + + + Ingredients + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::ScrollPerPixel + + + false + + + true + + + + + + + + Food Groups + + + + + + QFrame::NoFrame + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::ScrollPerPixel + + + true + + + + + diff --git a/main.cpp b/main.cpp index 2a70cec..940cd9d 100644 --- a/main.cpp +++ b/main.cpp @@ -48,18 +48,18 @@ 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()); - } +// 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()); - } +// 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 1eeebd3..1a9f83a 100644 --- a/model/database/recipedatabase.cpp +++ b/model/database/recipedatabase.cpp @@ -158,14 +158,68 @@ 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->selectFrom("recipe", "name", "ORDER BY name"); + ResultTable t = this->executeSQL("SELECT * FROM recipe ORDER BY name;"); + return this->readRecipesFromTable(t); +} + +vector RecipeDatabase::retrieveRecipesWithIngredients(vector ingredients){ vector recipes; - for (TableRow row : t.rows()){ - recipes.push_back(this->retrieveRecipe(row.at(0))); + if (ingredients.empty()){ + return recipes; } - return recipes; + string filterList = surroundString(ingredients.at(0).getName(), "'"); + for (unsigned int i = 1; i < ingredients.size(); i++){ + filterList += ", " + surroundString(ingredients[i].getName(), "'"); + } + filterList = '(' + filterList + ')'; + ResultTable t = this->executeSQL("SELECT * " + "FROM recipe " + "WHERE recipeId IN (" + " SELECT recipeIngredient.recipeId " + " FROM recipeIngredient " + " INNER JOIN (" + " SELECT ingredientId " + " FROM ingredient " + " WHERE name IN "+filterList+"" + " ) filteredIngredients " + " ON recipeIngredient.ingredientId = filteredIngredients.ingredientId" + ") ORDER BY name;"); + return this->readRecipesFromTable(t); +} + +vector RecipeDatabase::retrieveRecipesWithTags(vector tags){ + vector recipes; + if (tags.empty()){ + return recipes; + } + string filterList = surroundString(tags.at(0).getValue(), "'"); + for (unsigned int i = 1; i < tags.size(); i++){ + filterList += ", " + surroundString(tags[i].getValue(), "'"); + } + filterList = '(' + filterList + ')'; + ResultTable t = this->executeSQL("SELECT * FROM recipe WHERE recipeId IN (SELECT recipeId FROM recipeTag WHERE tagName IN "+filterList+" );"); + return this->readRecipesFromTable(t); +} + +vector RecipeDatabase::retrieveRecipesWithSubstring(string s){ + ResultTable t = this->executeSQL("SELECT * FROM recipe WHERE name LIKE '%"+s+"%' COLLATE NOCASE;"); + return this->readRecipesFromTable(t); +} + +vector RecipeDatabase::retrieveRecipesWithFoodGroups(vector groups){ + vector recipes; + if (groups.empty()){ + return recipes; + } + string filterList = surroundString(groups.at(0), "'"); + for (unsigned int i = 1; i < groups.size(); i++){ + filterList += ", " + surroundString(groups.at(i), "'"); + } + filterList = '(' + filterList + ')'; + ResultTable t = this->executeSQL("SELECT * FROM recipe WHERE recipeId IN (SELECT recipeId FROM recipeIngredient WHERE ingredientId IN (SELECT ingredientId FROM ingredient WHERE foodGroup IN "+filterList+" ) ) ORDER BY name;"); + return this->readRecipesFromTable(t); } vector RecipeDatabase::retrieveAllFoodGroups(){ @@ -302,6 +356,10 @@ bool RecipeDatabase::deleteTag(RecipeTag tag){ return this->deleteFrom("recipeTag", "WHERE tagName='"+tag.getValue()+"'"); } +bool RecipeDatabase::updateRecipe(Recipe recipe){ + +} + void RecipeDatabase::ensureTablesExist(){ //Make sure that foreign keys are enabled. this->executeSQL("PRAGMA foreign_keys = ON;"); @@ -360,3 +418,13 @@ Recipe RecipeDatabase::readFromResultTable(ResultTable t, int tRow){ r.setTags(this->retrieveTags(id)); return r; } + +//Retrieves recipes from a table with the following format: +// id, name, createdDate, prepTime, cookTime, servings +vector RecipeDatabase::readRecipesFromTable(ResultTable t){ + vector recipes; + for (unsigned int row = 0; row < t.rowCount(); row++){ + recipes.push_back(readFromResultTable(t, row)); + } + return recipes; +} diff --git a/model/database/recipedatabase.h b/model/database/recipedatabase.h index 79ff7bd..c68b0c9 100644 --- a/model/database/recipedatabase.h +++ b/model/database/recipedatabase.h @@ -34,6 +34,10 @@ class RecipeDatabase : public Database Recipe retrieveRecipe(string name); Recipe retrieveRandomRecipe(); vector retrieveAllRecipes(); + vector retrieveRecipesWithIngredients(vector ingredients); + vector retrieveRecipesWithTags(vector tags); + vector retrieveRecipesWithSubstring(string s); + vector retrieveRecipesWithFoodGroups(vector groups); vector retrieveAllFoodGroups(); vector retrieveRecipeIngredients(int recipeId); vector retrieveAllIngredients(); @@ -47,12 +51,16 @@ class RecipeDatabase : public Database bool deleteIngredient(string name); bool deleteUnitOfMeasure(string name); bool deleteTag(RecipeTag tag); + + //Updating. + bool updateRecipe(Recipe recipe); private: //Utility methods. void ensureTablesExist(); //Read a recipe from a row of a result table. Recipe readFromResultTable(ResultTable t, int row=0); + vector readRecipesFromTable(ResultTable t); }; #endif // RECIPEDATABASE_H