Merge pull request #9 from andrewlalis/development

Almost at full release.
This commit is contained in:
Andrew Lalis 2018-03-30 23:30:20 +02:00 committed by GitHub
commit e60fe413db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 294 additions and 103 deletions

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -180,9 +180,9 @@ QPushButton#openButton:pressed{
</widget>
</item>
<item alignment="Qt::AlignTop">
<widget class="QPushButton" name="browseButton">
<widget class="QPushButton" name="editButton">
<property name="enabled">
<bool>false</bool>
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
@ -201,19 +201,19 @@ QPushButton#openButton:pressed{
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">QPushButton#browseButton {
<string notr="true">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);
}</string>
</property>
<property name="text">
<string>Browse</string>
<string>Edit</string>
</property>
<property name="flat">
<bool>false</bool>
@ -363,6 +363,9 @@ font: &quot;Noto Sans CJK KR&quot;;</string>
</item>
<item alignment="Qt::AlignLeft|Qt::AlignBottom">
<widget class="QLabel" name="authorLabel">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>

View File

@ -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;
}

View File

@ -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();

View File

@ -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(on_ingredientsListView_selectionChanged(QItemSelection)));
SLOT(onIngredientsListViewSelectionChanged(QItemSelection)));
QObject::connect(ui->tagsListView->selectionModel(),
SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
this,
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<Recipe> recipes){
this->recipeTableModel.clear();
vector<Recipe> 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<Ingredient> 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<RecipeTag> 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<string> 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));
}

View File

@ -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<Recipe> recipes);
void populateIngredientsList();
void populateTagsList();
void populateFoodGroupsList();
};
#endif // OPENRECIPEDIALOG_H

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>1000</width>
<width>1064</width>
<height>480</height>
</rect>
</property>
@ -25,15 +25,27 @@
<widget class="QWidget" name="searchPanel" native="true">
<layout class="QVBoxLayout" name="verticalLayout">
<item alignment="Qt::AlignLeft">
<widget class="QWidget" name="tagsSearchpanel" native="true">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="tagLabel">
<property name="text">
<string>Tag</string>
<widget class="QTabWidget" name="tabWidget">
<property name="minimumSize">
<size>
<width>290</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<property name="tabPosition">
<enum>QTabWidget::South</enum>
</property>
<property name="tabShape">
<enum>QTabWidget::Rounded</enum>
</property>
<property name="currentIndex">
<number>2</number>
</property>
<widget class="QWidget" name="tagsTab">
<attribute name="title">
<string>Tags</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QListView" name="tagsListView">
<property name="sizePolicy">
@ -48,27 +60,24 @@
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>false</bool>
</property>
<property name="wordWrap">
<bool>false</bool>
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item alignment="Qt::AlignLeft">
<widget class="QWidget" name="ingredientSearchPanel" native="true">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="ingredientLabel">
<property name="text">
<string>Ingredient</string>
</property>
</widget>
</item>
<widget class="QWidget" name="ingredientsTab">
<attribute name="title">
<string>Ingredients</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QListView" name="ingredientsListView">
<property name="sizePolicy">
@ -83,6 +92,9 @@
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>false</bool>
</property>
@ -93,6 +105,30 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Food Groups</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QListWidget" name="foodGroupsListWidget">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QWidget" name="nameSearchPanel" native="true">

View File

@ -48,18 +48,18 @@ void test(RecipeDatabase *recipeDB){
bool success = recipeDB->storeRecipe(rec);
printf("Storage successful: %d\n", success);
vector<string> foodGroups = recipeDB->retrieveAllFoodGroups();
printf("Food Groups:\n");
for (string s : foodGroups){
printf("\t%s\n", s.c_str());
}
// vector<string> 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<string> 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<string> foodGroupsR = r.getFoodGroups();
// printf("Pannenkoeken Food Groups:\n");
// for (string s : foodGroupsR){
// printf("\t%s\n", s.c_str());
// }
}

View File

@ -158,15 +158,69 @@ Recipe RecipeDatabase::retrieveRandomRecipe(){
}
return this->readFromResultTable(t);
}
//TODO: Change this to be more efficient! One query per recipe is not good!
vector<Recipe> RecipeDatabase::retrieveAllRecipes(){
ResultTable t = this->selectFrom("recipe", "name", "ORDER BY name");
vector<Recipe> recipes;
for (TableRow row : t.rows()){
recipes.push_back(this->retrieveRecipe(row.at(0)));
ResultTable t = this->executeSQL("SELECT * FROM recipe ORDER BY name;");
return this->readRecipesFromTable(t);
}
vector<Recipe> RecipeDatabase::retrieveRecipesWithIngredients(vector<Ingredient> ingredients){
vector<Recipe> recipes;
if (ingredients.empty()){
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<Recipe> RecipeDatabase::retrieveRecipesWithTags(vector<RecipeTag> tags){
vector<Recipe> 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<Recipe> RecipeDatabase::retrieveRecipesWithSubstring(string s){
ResultTable t = this->executeSQL("SELECT * FROM recipe WHERE name LIKE '%"+s+"%' COLLATE NOCASE;");
return this->readRecipesFromTable(t);
}
vector<Recipe> RecipeDatabase::retrieveRecipesWithFoodGroups(vector<string> groups){
vector<Recipe> 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<string> RecipeDatabase::retrieveAllFoodGroups(){
ResultTable t = this->executeSQL("SELECT DISTINCT foodGroup FROM ingredient ORDER BY foodGroup;");
@ -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<Recipe> RecipeDatabase::readRecipesFromTable(ResultTable t){
vector<Recipe> recipes;
for (unsigned int row = 0; row < t.rowCount(); row++){
recipes.push_back(readFromResultTable(t, row));
}
return recipes;
}

View File

@ -34,6 +34,10 @@ class RecipeDatabase : public Database
Recipe retrieveRecipe(string name);
Recipe retrieveRandomRecipe();
vector<Recipe> retrieveAllRecipes();
vector<Recipe> retrieveRecipesWithIngredients(vector<Ingredient> ingredients);
vector<Recipe> retrieveRecipesWithTags(vector<RecipeTag> tags);
vector<Recipe> retrieveRecipesWithSubstring(string s);
vector<Recipe> retrieveRecipesWithFoodGroups(vector<string> groups);
vector<string> retrieveAllFoodGroups();
vector<RecipeIngredient> retrieveRecipeIngredients(int recipeId);
vector<Ingredient> 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<Recipe> readRecipesFromTable(ResultTable t);
};
#endif // RECIPEDATABASE_H