diff --git a/RecipeDB.pro b/RecipeDB.pro index 791ba66..92ac7d6 100644 --- a/RecipeDB.pro +++ b/RecipeDB.pro @@ -14,7 +14,6 @@ TEMPLATE = app SOURCES += model/recipe/instruction.cpp \ model/recipe/recipe.cpp \ - userInterface/mainwindow.cpp \ main.cpp \ model/database/database.cpp \ model/recipe/ingredients/unitofmeasure.cpp \ @@ -33,11 +32,12 @@ SOURCES += model/recipe/instruction.cpp \ gui/newDialogs/newunitdialog.cpp \ utils/aspectratiopixmaplabel.cpp \ utils/stringutils.cpp \ - openrecipedialog.cpp + gui/openrecipedialog.cpp \ + model/recipe/recipetablemodel.cpp \ + gui/mainwindow.cpp HEADERS += model/recipe/instruction.h \ model/recipe/recipe.h \ - userInterface/mainwindow.h \ model/database/database.h \ model/recipe/ingredients/unitofmeasure.h \ model/recipe/ingredients/ingredient.h \ @@ -56,7 +56,9 @@ HEADERS += model/recipe/instruction.h \ gui/newDialogs/newunitdialog.h \ utils/aspectratiopixmaplabel.h \ utils/stringutils.h \ - openrecipedialog.h + gui/openrecipedialog.h \ + model/recipe/recipetablemodel.h \ + gui/mainwindow.h LIBS += -ldl \ @@ -65,7 +67,8 @@ FORMS += gui/mainwindow.ui \ gui/newDialogs/newingredientdialog.ui \ gui/newDialogs/newtagdialog.ui \ gui/newDialogs/newunitdialog.ui \ - openrecipedialog.ui + gui/openrecipedialog.ui \ + gui/mainwindow.ui DISTFILES += \ .gitignore diff --git a/userInterface/mainwindow.cpp b/gui/mainwindow.cpp similarity index 79% rename from userInterface/mainwindow.cpp rename to gui/mainwindow.cpp index d2825d3..7db6bbe 100644 --- a/userInterface/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -1,4 +1,4 @@ -#include "userInterface/mainwindow.h" +#include "gui/mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : @@ -58,7 +58,7 @@ void MainWindow::setCookTime(QTime cookTime){ } void MainWindow::setServings(float servings){ - ui->servingsLabel->setText(QString("Servings: ")+QString::fromStdString(StringUtils::toString(servings))); + ui->servingsLabel->setText(QString(QString::fromStdString(StringUtils::toString(servings) + " Serving" + ((servings != 1.0f) ? "s" : "")))); } void MainWindow::setTags(vector tags){ @@ -72,8 +72,22 @@ void MainWindow::on_newButton_clicked(){ 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.")); + 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); } - this->loadFromRecipe(r); } } + +void MainWindow::on_openButton_clicked(){ + OpenRecipeDialog d(this->recipeDB, this); + d.show(); + d.exec(); + if (!d.getSelectedRecipe().isEmpty()){ + this->loadFromRecipe(d.getSelectedRecipe()); + } +} + +void MainWindow::on_exitButton_clicked(){ + this->close(); +} diff --git a/userInterface/mainwindow.h b/gui/mainwindow.h similarity index 92% rename from userInterface/mainwindow.h rename to gui/mainwindow.h index b5bcdb5..2b62cc3 100644 --- a/userInterface/mainwindow.h +++ b/gui/mainwindow.h @@ -8,6 +8,7 @@ #include "model/recipe/recipe.h" #include "model/recipe/ingredients/ingredientlistmodel.h" #include "gui/newrecipedialog.h" +#include "gui/openrecipedialog.h" #include "utils/stringutils.h" using namespace std; @@ -27,9 +28,14 @@ public: //Loads all data from a recipe into the GUI components. void loadFromRecipe(Recipe recipe); + private slots: void on_newButton_clicked(); + void on_openButton_clicked(); + + void on_exitButton_clicked(); + private: Ui::MainWindow *ui; RecipeDatabase *recipeDB; diff --git a/gui/mainwindow.ui b/gui/mainwindow.ui index 430cb1e..0925f75 100644 --- a/gui/mainwindow.ui +++ b/gui/mainwindow.ui @@ -143,6 +143,12 @@ QPushButton#newButton:pressed{ + + + 0 + 0 + + 150 @@ -175,6 +181,9 @@ QPushButton#openButton:pressed{ + + false + 150 @@ -211,6 +220,56 @@ QPushButton#browseButton:pressed{ + + + + + 150 + 80 + + + + + 150 + 80 + + + + + Noto Sans CJK KR Light + 20 + PreferAntialias + + + + false + + + QPushButton#exitButton { + background-color: rgb(255, 216, 216); + border: 0px; +} +QPushButton#exitButton:hover{ + background-color: rgb(255, 191, 191); +} +QPushButton#exitButton:pressed{ + background-color: rgb(255, 147, 147); +} + + + Exit + + + false + + + false + + + false + + + @@ -367,6 +426,9 @@ font: "Noto Sans CJK KR"; Prep Time: + + Qt::AlignCenter + @@ -383,6 +445,9 @@ font: "Noto Sans CJK KR"; Cook Time: + + Qt::AlignCenter + @@ -396,6 +461,9 @@ font: "Noto Sans CJK KR"; Servings: + + Qt::AlignCenter + diff --git a/gui/newDialogs/newunitdialog.cpp b/gui/newDialogs/newunitdialog.cpp index d89acaf..e7be030 100644 --- a/gui/newDialogs/newunitdialog.cpp +++ b/gui/newDialogs/newunitdialog.cpp @@ -8,10 +8,8 @@ NewUnitDialog::NewUnitDialog(QWidget *parent) : ui->setupUi(this); ui->typeComboBox->clear(); - ui->typeComboBox->setItemData(0, "Mass"); - ui->typeComboBox->setItemData(1, "Volume"); - ui->typeComboBox->setItemData(2, "Length"); - ui->typeComboBox->setItemData(3, "Misc"); + QStringList list({"Mass", "Volume", "Length", "Misc"}); + ui->typeComboBox->insertItems(0, list); } @@ -23,7 +21,7 @@ NewUnitDialog::~NewUnitDialog() UnitOfMeasure NewUnitDialog::getUnit(){ return UnitOfMeasure(ui->unitNameEdit->text().toLower().toStdString(), ui->pluralNameEdit->text().toLower().toStdString(), - ui->abbreviationEdit->text().toLower().toStdString(), + ui->abbreviationEdit->text().toStdString(), this->getSelectedType(), ui->coefficientSpinBox->value()); } diff --git a/gui/newrecipedialog.cpp b/gui/newrecipedialog.cpp index 93e3ffa..a0681f2 100644 --- a/gui/newrecipedialog.cpp +++ b/gui/newrecipedialog.cpp @@ -119,7 +119,7 @@ void NewRecipeDialog::on_selectImageButton_clicked(){ } } -void NewRecipeDialog::on_deleteIngredientButton_clicked(){ +void NewRecipeDialog::on_removeIngredientButton_clicked(){ QModelIndexList indexList = ui->ingredientsListView->selectionModel()->selectedIndexes(); for (QModelIndexList::iterator it = indexList.begin(); it != indexList.end(); ++it){ QModelIndex i = *it; @@ -127,6 +127,22 @@ void NewRecipeDialog::on_deleteIngredientButton_clicked(){ } } +void NewRecipeDialog::on_deleteIngredientButton_clicked(){ + int index = ui->ingredientNameBox->currentIndex(); + Ingredient ing = this->ingredients.at(index); + QMessageBox::StandardButton reply = QMessageBox::question(this, + QString::fromStdString("Delete Ingredient"), + QString::fromStdString("Are you sure you want to delete the ingredient " + ing.getName() + "?")); + if (reply == QMessageBox::Yes){ + bool success = this->recipeDB->deleteIngredient(ing.getName()); + if (!success){ + QMessageBox::critical(this, QString::fromStdString("Error"), QString::fromStdString("Unable to delete ingredient: " + ing.getName() + ", some recipes use it!")); + } else { + this->populateIngredientsBox(); + } + } +} + void NewRecipeDialog::on_newIngredientButton_clicked(){ NewIngredientDialog d(this); d.show(); @@ -155,12 +171,12 @@ void NewRecipeDialog::on_newTagButton_clicked(){ } void NewRecipeDialog::on_removeTagButton_clicked(){ - int index = ui->tagsComboBox->currentIndex(); - if (index < 0 || index >= this->tags.size()){ + unsigned int index = ui->tagsComboBox->currentIndex(); + if (index >= this->tags.size()){ return; } RecipeTag tag = this->tags[ui->tagsComboBox->currentIndex()]; - string content = "Are you sure you wish to delete the following tag:\n"+tag.getValue(); + string content = "Are you sure you wish to delete the following tag:\n"+tag.getValue()+"\nThis will delete the tag for all recipes that use it."; QMessageBox::StandardButton reply = QMessageBox::question(this, QString("Delete Tag"), QString(content.c_str())); if (reply == QMessageBox::Yes){ this->recipeDB->deleteTag(tag); @@ -173,10 +189,25 @@ void NewRecipeDialog::on_newUnitButton_clicked(){ d.show(); if (d.exec() == QDialog::Accepted){ UnitOfMeasure u = d.getUnit(); - if (!this->recipeDB->storeUnitOfMeasure(u)){ + if (!this->recipeDB->storeUnitOfMeasure(u) || u.getName().empty() || u.getNamePlural().empty() || u.getAbbreviation().empty()){ QMessageBox::critical(this, "Error", "Unable to store new unit."); } else { this->populateUnitsBox(); } } } + +void NewRecipeDialog::on_deleteUnitButton_clicked(){ + int index = ui->unitComboBox->currentIndex(); + UnitOfMeasure unit = this->units[index]; + QMessageBox::StandardButton reply = QMessageBox::question(this, + QString::fromStdString("Delete Unit Of Measure"), + QString::fromStdString("Are you sure you want to delete the unit " + unit.getName() + "?")); + if (reply == QMessageBox::Yes){ + if (!this->recipeDB->deleteUnitOfMeasure(unit.getName())){ + QMessageBox::critical(this, "Error", "Unable to delete unit. There may be recipes which still use it!"); + } else { + this->populateUnitsBox(); + } + } +} diff --git a/gui/newrecipedialog.h b/gui/newrecipedialog.h index d272f3e..bfeae1f 100644 --- a/gui/newrecipedialog.h +++ b/gui/newrecipedialog.h @@ -47,6 +47,8 @@ class NewRecipeDialog : public QDialog void on_selectImageButton_clicked(); + void on_removeIngredientButton_clicked(); + void on_deleteIngredientButton_clicked(); void on_newIngredientButton_clicked(); @@ -57,6 +59,8 @@ class NewRecipeDialog : public QDialog void on_newUnitButton_clicked(); + void on_deleteUnitButton_clicked(); + private: Ui::NewRecipeDialog *ui; RecipeDatabase *recipeDB; diff --git a/gui/newrecipedialog.ui b/gui/newrecipedialog.ui index e7ef5e9..7a57690 100644 --- a/gui/newrecipedialog.ui +++ b/gui/newrecipedialog.ui @@ -26,6 +26,9 @@ :/images/images/icon.png:/images/images/icon.png + + font: 25 "Noto Sans CJK KR"; + true @@ -97,9 +100,7 @@ - Noto Sans CJK KR - 14 - 50 + 3 false false @@ -116,7 +117,9 @@ - PreferAntialias + 3 + false + false @@ -155,7 +158,7 @@ 0 1999 12 - 26 + 25 @@ -501,6 +504,17 @@ + + + + + + + + :/images/images/minus_icon.png:/images/images/minus_icon.png + + + @@ -532,9 +546,7 @@ - Noto Sans CJK KR - 14 - 50 + 3 false false @@ -578,6 +590,17 @@ + + + + + + + + :/images/images/minus_icon.png:/images/images/minus_icon.png + + + @@ -626,7 +649,7 @@ - + Delete @@ -745,9 +768,9 @@ - Liberation Serif - 12 - true + 3 + false + false @@ -765,10 +788,9 @@ - Liberation Serif - 12 - 75 - true + 3 + false + false diff --git a/gui/openrecipedialog.cpp b/gui/openrecipedialog.cpp new file mode 100644 index 0000000..fb34703 --- /dev/null +++ b/gui/openrecipedialog.cpp @@ -0,0 +1,64 @@ +#include "openrecipedialog.h" +#include "ui_openrecipedialog.h" + +OpenRecipeDialog::OpenRecipeDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::OpenRecipeDialog) +{ + ui->setupUi(this); + + ui->recipeTableView->setModel(&this->recipeTableModel); +} + +OpenRecipeDialog::OpenRecipeDialog(RecipeDatabase *recipeDB, QWidget *parent) : OpenRecipeDialog(parent){ + this->recipeDB = recipeDB; + this->populateRecipesTable(); +} + +OpenRecipeDialog::~OpenRecipeDialog() +{ + delete ui; +} + +Recipe OpenRecipeDialog::getSelectedRecipe(){ + return this->selectedRecipe; +} + +void OpenRecipeDialog::populateRecipesTable(){ + this->recipeTableModel.clear(); + vector recipes = this->recipeDB->retrieveAllRecipes(); + this->recipeTableModel.setRecipes(recipes); + ui->recipeTableView->resizeColumnsToContents(); + ui->recipeTableView->show(); +} + +void OpenRecipeDialog::on_deleteRecipeButton_clicked(){ + QItemSelectionModel *selectModel = ui->recipeTableView->selectionModel(); + if (!selectModel->hasSelection()){ + return; + } + vector rows; + QModelIndexList indexes = selectModel->selectedIndexes(); + for (int i = 0; i < indexes.count(); i++){ + rows.push_back(indexes.at(i).row()); + } + 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+"?"); + QMessageBox::StandardButton reply = QMessageBox::question(this, title, content); + if (reply == QMessageBox::Yes){ + for (int row : rows){ + Recipe r = this->recipeTableModel.getRecipeAt(row); + 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())); + } + } + this->populateRecipesTable(); + } +} + +void OpenRecipeDialog::on_recipeTableView_doubleClicked(const QModelIndex &index){ + this->selectedRecipe = this->recipeTableModel.getRecipeAt(index.row()); + this->close(); +} diff --git a/gui/openrecipedialog.h b/gui/openrecipedialog.h new file mode 100644 index 0000000..974b6cf --- /dev/null +++ b/gui/openrecipedialog.h @@ -0,0 +1,39 @@ +#ifndef OPENRECIPEDIALOG_H +#define OPENRECIPEDIALOG_H + +#include +#include + +#include "model/database/recipedatabase.h" +#include "model/recipe/recipetablemodel.h" + +namespace Ui { +class OpenRecipeDialog; +} + +class OpenRecipeDialog : public QDialog +{ + Q_OBJECT + + public: + explicit OpenRecipeDialog(QWidget *parent = 0); + OpenRecipeDialog(RecipeDatabase *recipeDB, QWidget *parent = 0); + ~OpenRecipeDialog(); + + Recipe getSelectedRecipe(); + + private slots: + void on_deleteRecipeButton_clicked(); + + void on_recipeTableView_doubleClicked(const QModelIndex &index); + + private: + Ui::OpenRecipeDialog *ui; + RecipeDatabase *recipeDB; + RecipeTableModel recipeTableModel; + Recipe selectedRecipe; + + void populateRecipesTable(); +}; + +#endif // OPENRECIPEDIALOG_H diff --git a/gui/openrecipedialog.ui b/gui/openrecipedialog.ui new file mode 100644 index 0000000..d689a16 --- /dev/null +++ b/gui/openrecipedialog.ui @@ -0,0 +1,134 @@ + + + OpenRecipeDialog + + + + 0 + 0 + 640 + 480 + + + + Open Recipe + + + + :/images/images/icon.png:/images/images/icon.png + + + true + + + + + + + + + + + + Name + + + + + + + + + + + + + + + + Tag + + + + + + + + + + + + + + + + Ingredient + + + + + + + + + + + + + + + + + :/images/images/search_icon.png:/images/images/search_icon.png + + + + + + + + + + + + + + + + + :/images/images/trash.png:/images/images/trash.png + + + + + + + + + + + + + QFrame::NoFrame + + + QAbstractItemView::SelectRows + + + true + + + false + + + + + + + + + + + + + diff --git a/images.qrc b/images.qrc index 2391914..a5261a4 100644 --- a/images.qrc +++ b/images.qrc @@ -4,5 +4,7 @@ images/icon.png images/plus_icon.png images/minus_icon.png + images/search_icon.png + images/trash.png diff --git a/images/search_icon.png b/images/search_icon.png new file mode 100644 index 0000000..9cac3e7 Binary files /dev/null and b/images/search_icon.png differ diff --git a/images/trash.png b/images/trash.png new file mode 100644 index 0000000..529896a Binary files /dev/null and b/images/trash.png differ diff --git a/main.cpp b/main.cpp index 4e49c1e..3d4bfdb 100644 --- a/main.cpp +++ b/main.cpp @@ -1,4 +1,4 @@ -#include "userInterface/mainwindow.h" +#include "gui/mainwindow.h" #include "gui/newrecipedialog.h" #include @@ -13,42 +13,28 @@ int main(int argc, char *argv[]) w.show(); //TESTING CODE -// 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), "")); + 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({RecipeTag("testing"), -// RecipeTag("fake")}), -// QDate::currentDate(), -// QTime(0, 30), -// QTime(0, 25), -// 10.0f); + Recipe rec("Example", + ri, + Instruction("BOLDiTaLiCs"), + QImage(), + vector({RecipeTag("testing"), + RecipeTag("fake")}), + 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); -// recipeDB.storeUnitOfMeasure(UnitOfMeasure("tablespoon", "tablespoons", "tbsp", UnitOfMeasure::VOLUME, 1.0)); -// recipeDB.storeUnitOfMeasure(UnitOfMeasure("pinch", "pinches", "pch", UnitOfMeasure::VOLUME, 1.0)); -// recipeDB.storeUnitOfMeasure(UnitOfMeasure("gram", "grams", "g", UnitOfMeasure::MASS, 1.0)); + //recipeDB.selectFrom("recipe", "recipeId, name", "").printData(); + w.loadFromRecipe(recipeDB.retrieveRandomRecipe()); -// Recipe reloadRec = recipeDB.retrieveRecipe("Example"); -// reloadRec.print(); - -// w.loadFromRecipe(reloadRec); - -// NewRecipeDialog d(&recipeDB); -// d.show(); -// d.exec(); - -// if (d.isAccepted()){ -// printf("Accepted the dialog.\n"); -// } - - w.loadFromRecipe(recipeDB.retrieveRecipe("Generic Bread")); - - return a.exec(); + a.exec(); + recipeDB.closeConnection(); + return 0; } diff --git a/model/database/database.cpp b/model/database/database.cpp index 636a137..0d3b9a4 100644 --- a/model/database/database.cpp +++ b/model/database/database.cpp @@ -46,6 +46,20 @@ ResultTable Database::selectFrom(string tableName, string columnNames, string co return this->executeSQL(query); } +bool Database::deleteFrom(string tableName, string conditions){ + if (tableName.empty()){ + return false; + } + string query = "DELETE FROM " + tableName + " " + conditions + ";"; + ResultTable t = this->executeSQL(query); + if (t.getReturnCode() != SQLITE_DONE){ + fprintf(stderr, "Can't delete from table %s.Return code: %d\n%s\n", tableName.c_str(), t.getReturnCode(), sqlite3_errmsg(this->db)); + exit(EXIT_FAILURE); + } else { + return true; + } +} + void Database::openConnection(){ this->returnCode = sqlite3_open(this->filename.c_str(), &this->db); if (this->returnCode || this->db == NULL){ diff --git a/model/database/database.h b/model/database/database.h index 4a60447..ab149d5 100644 --- a/model/database/database.h +++ b/model/database/database.h @@ -26,10 +26,13 @@ public: ResultTable executeSQL(string statement); bool insertInto(string tableName, vector columnNames, vector values); ResultTable selectFrom(string tableName, string columnNames, string conditions); + bool deleteFrom(string tableName, string conditions); bool tableExists(string tableName); int getLastInsertedRowId(); + void closeConnection(); + protected: string surroundString(string s, string surround); @@ -43,7 +46,6 @@ private: char* errorMsg; void openConnection(); - void closeConnection(); std::string combineVector(std::vector strings, std::string mid); }; diff --git a/model/database/recipedatabase.cpp b/model/database/recipedatabase.cpp index 5ab05fd..1650579 100644 --- a/model/database/recipedatabase.cpp +++ b/model/database/recipedatabase.cpp @@ -5,6 +5,12 @@ RecipeDatabase::RecipeDatabase(string filename) : Database(filename){ } bool RecipeDatabase::storeRecipe(Recipe recipe){ + //Some primary checks to avoid garbage in the database. + if (recipe.getName().empty() || + recipe.getInstruction().getHTML().empty() || + recipe.getIngredients().empty()){ + 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;"); ResultTable t = this->selectFrom("recipe", "*", "WHERE name="+surroundString(recipe.getName(), "'")); @@ -141,18 +147,25 @@ Recipe RecipeDatabase::retrieveRecipe(string name){ fprintf(stderr, "Error: No recipe with name %s found!\n", name.c_str()); return Recipe(); } - Recipe r; - int id = std::stoi(t.valueAt(0, 0)); - r.setName(t.valueAt(0, 1)); - r.setCreatedDate(QDate::fromString(QString::fromStdString(t.valueAt(0, 2)))); - r.setPrepTime(QTime::fromString(QString::fromStdString(t.valueAt(0, 3)))); - r.setCookTime(QTime::fromString(QString::fromStdString(t.valueAt(0, 4)))); - r.setServings(std::stof(t.valueAt(0, 5))); - r.setInstruction(FileUtils::loadInstruction(id)); - r.setImage(FileUtils::loadImage(id)); - r.setIngredients(this->retrieveRecipeIngredients(id)); - r.setTags(this->retrieveTags(id)); - return r; + return this->readFromResultTable(t); +} + +Recipe RecipeDatabase::retrieveRandomRecipe(){ + ResultTable t = this->selectFrom("recipe", "*", "ORDER BY RANDOM() LIMIT 1"); + if (t.isEmpty()){ + fprintf(stderr, "Unable to find a random recipe.\n"); + return Recipe(); + } + return this->readFromResultTable(t); +} + +vector RecipeDatabase::retrieveAllRecipes(){ + ResultTable t = this->selectFrom("recipe", "name", "ORDER BY name"); + vector recipes; + for (unsigned int row = 0; row < t.rowCount(); row++){ + recipes.push_back(this->retrieveRecipe(t.valueAt(row, 0))); + } + return recipes; } vector RecipeDatabase::retrieveRecipeIngredients(int recipeId){ @@ -223,8 +236,57 @@ vector RecipeDatabase::retrieveAllTags(){ return tags; } -void RecipeDatabase::deleteTag(RecipeTag tag){ - ResultTable t = this->executeSQL("DELETE FROM recipeTag WHERE tagName="+surroundString(tag.getValue(), "'")); +bool RecipeDatabase::deleteRecipe(string name){ + ResultTable t = this->selectFrom("recipe", "recipeId", "WHERE name='"+name+"'"); + if (t.rowCount() != 1){ + return false; + } + string recipeId = t.valueAt(0, 0); + return this->deleteRecipe(std::stoi(recipeId)); +} + +bool RecipeDatabase::deleteRecipe(int recipeId){ + string idString = std::to_string(recipeId); + if (this->selectFrom("recipe", "recipeId", "WHERE recipeId="+idString).isEmpty()){ + printf("Cannot delete. No recipe with ID %d exists.\n", recipeId); + return false; + } + this->executeSQL("BEGIN;"); + bool tagsDeleted = this->deleteFrom("recipeTag", "WHERE recipeId="+idString); + bool recipeIngredientDeleted = this->deleteFrom("recipeIngredient", "WHERE recipeId="+idString); + bool recipeDeleted = this->deleteFrom("recipe", "WHERE recipeId="+idString); + if (tagsDeleted && recipeIngredientDeleted && recipeDeleted){ + this->executeSQL("COMMIT;"); + return true; + } else { + this->executeSQL("ROLLBACK;"); + return false; + } +} + +bool RecipeDatabase::deleteIngredient(string name){ + ResultTable t = this->executeSQL("SELECT recipeId " + "FROM recipeIngredient " + "INNER JOIN ingredient " + "ON recipeIngredient.ingredientId = ingredient.ingredientId " + "WHERE ingredient.name='"+name+"';"); + if (!t.isEmpty()){ + //There is at least one recipe dependent on the ingredient. + return false; + } + return this->deleteFrom("ingredient", "WHERE name='"+name+"'"); +} + +bool RecipeDatabase::deleteUnitOfMeasure(string name){ + ResultTable t = this->selectFrom("recipeIngredient", "recipeId", "WHERE unitName='"+name+"'"); + if (!t.isEmpty()){ + return false; + } + return this->deleteFrom("unitOfMeasure", "WHERE name='"+name+"'"); +} + +bool RecipeDatabase::deleteTag(RecipeTag tag){ + return this->deleteFrom("recipeTag", "WHERE tagName='"+tag.getValue()+"'"); } void RecipeDatabase::ensureTablesExist(){ @@ -269,3 +331,18 @@ void RecipeDatabase::ensureTablesExist(){ "FOREIGN KEY (unitName) REFERENCES unitOfMeasure(name));"); this->executeSQL("COMMIT;"); } + +Recipe RecipeDatabase::readFromResultTable(ResultTable t, int row){ + Recipe r; + int id = std::stoi(t.valueAt(row, 0)); + r.setName(t.valueAt(row, 1)); + r.setCreatedDate(QDate::fromString(QString::fromStdString(t.valueAt(row, 2)))); + r.setPrepTime(QTime::fromString(QString::fromStdString(t.valueAt(row, 3)))); + r.setCookTime(QTime::fromString(QString::fromStdString(t.valueAt(row, 4)))); + r.setServings(std::stof(t.valueAt(row, 5))); + r.setInstruction(FileUtils::loadInstruction(id)); + r.setImage(FileUtils::loadImage(id)); + r.setIngredients(this->retrieveRecipeIngredients(id)); + r.setTags(this->retrieveTags(id)); + return r; +} diff --git a/model/database/recipedatabase.h b/model/database/recipedatabase.h index 099c233..c5466b7 100644 --- a/model/database/recipedatabase.h +++ b/model/database/recipedatabase.h @@ -32,6 +32,8 @@ class RecipeDatabase : public Database //Retrieval. Recipe retrieveRecipe(string name); + Recipe retrieveRandomRecipe(); + vector retrieveAllRecipes(); vector retrieveRecipeIngredients(int recipeId); vector retrieveAllIngredients(); vector retrieveAllUnitsOfMeasure(); @@ -39,11 +41,17 @@ class RecipeDatabase : public Database vector retrieveAllTags(); //Deletion. - void deleteTag(RecipeTag tag); + bool deleteRecipe(string name); + bool deleteRecipe(int recipeId); + bool deleteIngredient(string name); + bool deleteUnitOfMeasure(string name); + bool deleteTag(RecipeTag tag); private: //Utility methods. void ensureTablesExist(); + //Read a recipe from a row of a result table. + Recipe readFromResultTable(ResultTable t, int row=0); }; #endif // RECIPEDATABASE_H diff --git a/model/recipe/ingredients/recipeingredient.cpp b/model/recipe/ingredients/recipeingredient.cpp index d8f115b..34de9d1 100644 --- a/model/recipe/ingredients/recipeingredient.cpp +++ b/model/recipe/ingredients/recipeingredient.cpp @@ -50,7 +50,7 @@ string RecipeIngredient::toString(){ result += StringUtils::toString(this->getQuantity()); } result += " " + this->getUnit().getAbbreviation() + " " + this->getName(); - if (!this->getComment().empty()) result += " ~" + this->getComment(); + if (!this->getComment().empty()) result += " (" + this->getComment() + ")"; return result; } diff --git a/model/recipe/recipe.cpp b/model/recipe/recipe.cpp index f99e1a2..0859bd8 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("Unnamed 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(1, 0), QTime(0, 30), 10.0f){ //Set default values when none are specified. } @@ -53,7 +53,11 @@ QTime Recipe::getTotalTime() const{ } float Recipe::getServings() const{ - return this->servings; + return this->servings; +} + +bool Recipe::isEmpty() const{ + return this->name.empty(); } void Recipe::setName(string newName){ diff --git a/model/recipe/recipe.h b/model/recipe/recipe.h index d013659..048b09a 100644 --- a/model/recipe/recipe.h +++ b/model/recipe/recipe.h @@ -46,6 +46,7 @@ public: QTime getCookTime() const; QTime getTotalTime() const; //Derived method to add prep and cook times. float getServings() const; + bool isEmpty() const; //Setters void setName(string newName); diff --git a/model/recipe/recipetablemodel.cpp b/model/recipe/recipetablemodel.cpp new file mode 100644 index 0000000..521fa51 --- /dev/null +++ b/model/recipe/recipetablemodel.cpp @@ -0,0 +1,86 @@ +#include "recipetablemodel.h" + +RecipeTableModel::RecipeTableModel() +{ + +} + +RecipeTableModel::RecipeTableModel(vector recipes){ + this->setRecipes(recipes); +} + +int RecipeTableModel::rowCount(const QModelIndex &parent) const{ + Q_UNUSED(parent); + return this->recipes.size(); +} + +int RecipeTableModel::columnCount(const QModelIndex &parent) const{ + Q_UNUSED(parent); + return 5;//FIX THIS TO BE MORE ADAPTIVE EVENTUALLY. +} + +QVariant RecipeTableModel::data(const QModelIndex &index, int role) const{ + int row = index.row(); + int col = index.column(); + Recipe r = this->recipes[row]; + + if (role == Qt::DisplayRole){ + switch(col){ + case 0: + return QString::fromStdString(r.getName()); + case 1: + return QString::fromStdString(r.getCreatedDate().toString().toStdString()); + case 2: + return QString::fromStdString(StringUtils::toString(r.getServings())); + case 3: + return r.getPrepTime().toString("hh:mm:ss"); + case 4: + return r.getCookTime().toString("hh:mm:ss"); + } + } + return QVariant(); +} + +QVariant RecipeTableModel::headerData(int section, Qt::Orientation orientation, int role) const{ + if (role != Qt::DisplayRole){ + return QVariant(); + } + if (orientation == Qt::Horizontal){ + switch (section){ + case 0: + return "Name"; + case 1: + return "Created On"; + case 2: + return "Servings"; + case 3: + return "Prep Time"; + case 4: + return "Cook Time"; + default: + return QVariant(); + } + } else if (orientation == Qt::Vertical){ + return QString::fromStdString(std::to_string(section)); + } + return QVariant(); +} + +void RecipeTableModel::setRecipes(vector recipes){ + beginInsertRows({}, 0, recipes.size()-1); + this->recipes = recipes; + endInsertRows(); +} + +Recipe RecipeTableModel::getRecipeAt(int index){ + if (index < 0 || index >= this->recipes.size()){ + return Recipe(); + } + return this->recipes[index]; +} + +void RecipeTableModel::clear(){ + beginResetModel(); + this->recipes.clear(); + endResetModel(); +} diff --git a/model/recipe/recipetablemodel.h b/model/recipe/recipetablemodel.h new file mode 100644 index 0000000..7449619 --- /dev/null +++ b/model/recipe/recipetablemodel.h @@ -0,0 +1,29 @@ +#ifndef RECIPETABLEMODEL_H +#define RECIPETABLEMODEL_H + +#include + +#include "model/recipe/recipe.h" +#include "utils/stringutils.h" + +class RecipeTableModel : public QAbstractTableModel +{ + public: + RecipeTableModel(); + RecipeTableModel(vector recipes); + + //Overridden methods. + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + //Normal methods. + void setRecipes(vector recipes); + Recipe getRecipeAt(int index); + void clear(); + private: + vector recipes; +}; + +#endif // RECIPETABLEMODEL_H diff --git a/openrecipedialog.cpp b/openrecipedialog.cpp deleted file mode 100644 index 950a06c..0000000 --- a/openrecipedialog.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "openrecipedialog.h" -#include "ui_openrecipedialog.h" - -OpenRecipeDialog::OpenRecipeDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::OpenRecipeDialog) -{ - ui->setupUi(this); -} - -OpenRecipeDialog::~OpenRecipeDialog() -{ - delete ui; -} diff --git a/openrecipedialog.h b/openrecipedialog.h deleted file mode 100644 index ad95ad6..0000000 --- a/openrecipedialog.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef OPENRECIPEDIALOG_H -#define OPENRECIPEDIALOG_H - -#include - -namespace Ui { -class OpenRecipeDialog; -} - -class OpenRecipeDialog : public QDialog -{ - Q_OBJECT - - public: - explicit OpenRecipeDialog(QWidget *parent = 0); - ~OpenRecipeDialog(); - - private: - Ui::OpenRecipeDialog *ui; -}; - -#endif // OPENRECIPEDIALOG_H diff --git a/openrecipedialog.ui b/openrecipedialog.ui deleted file mode 100644 index 31f318f..0000000 --- a/openrecipedialog.ui +++ /dev/null @@ -1,19 +0,0 @@ - - - OpenRecipeDialog - - - - 0 - 0 - 640 - 480 - - - - Dialog - - - - - diff --git a/utils/stringutils.cpp b/utils/stringutils.cpp index 3ed578b..f6115e8 100644 --- a/utils/stringutils.cpp +++ b/utils/stringutils.cpp @@ -2,6 +2,14 @@ namespace StringUtils{ +bool stringEndsWith(std::string const &fullString, std::string const &ending){ + if (fullString.length() >= ending.length()) { + return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending)); + } else { + return false; + } +} + std::string toString(float val){ float decimal = std::fmod(val, 1.0f); int places = 1; @@ -13,6 +21,11 @@ std::string toString(float val){ std::string arg = "%."+std::to_string(places)+"f"; sprintf(buffer, arg.c_str(), val); std::string s = buffer; + if (stringEndsWith(s, ".0")){ + while (s.find('.') != std::string::npos){ + s.pop_back(); + } + } return s; }