Merge pull request #7 from andrewlalis/development

Actually Usable Version
This commit is contained in:
Andrew Lalis 2018-03-29 16:11:26 +02:00 committed by GitHub
commit 626fe91bb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 694 additions and 144 deletions

View File

@ -14,7 +14,6 @@ TEMPLATE = app
SOURCES += model/recipe/instruction.cpp \ SOURCES += model/recipe/instruction.cpp \
model/recipe/recipe.cpp \ model/recipe/recipe.cpp \
userInterface/mainwindow.cpp \
main.cpp \ main.cpp \
model/database/database.cpp \ model/database/database.cpp \
model/recipe/ingredients/unitofmeasure.cpp \ model/recipe/ingredients/unitofmeasure.cpp \
@ -33,11 +32,12 @@ SOURCES += model/recipe/instruction.cpp \
gui/newDialogs/newunitdialog.cpp \ gui/newDialogs/newunitdialog.cpp \
utils/aspectratiopixmaplabel.cpp \ utils/aspectratiopixmaplabel.cpp \
utils/stringutils.cpp \ utils/stringutils.cpp \
openrecipedialog.cpp gui/openrecipedialog.cpp \
model/recipe/recipetablemodel.cpp \
gui/mainwindow.cpp
HEADERS += model/recipe/instruction.h \ HEADERS += model/recipe/instruction.h \
model/recipe/recipe.h \ model/recipe/recipe.h \
userInterface/mainwindow.h \
model/database/database.h \ model/database/database.h \
model/recipe/ingredients/unitofmeasure.h \ model/recipe/ingredients/unitofmeasure.h \
model/recipe/ingredients/ingredient.h \ model/recipe/ingredients/ingredient.h \
@ -56,7 +56,9 @@ HEADERS += model/recipe/instruction.h \
gui/newDialogs/newunitdialog.h \ gui/newDialogs/newunitdialog.h \
utils/aspectratiopixmaplabel.h \ utils/aspectratiopixmaplabel.h \
utils/stringutils.h \ utils/stringutils.h \
openrecipedialog.h gui/openrecipedialog.h \
model/recipe/recipetablemodel.h \
gui/mainwindow.h
LIBS += -ldl \ LIBS += -ldl \
@ -65,7 +67,8 @@ FORMS += gui/mainwindow.ui \
gui/newDialogs/newingredientdialog.ui \ gui/newDialogs/newingredientdialog.ui \
gui/newDialogs/newtagdialog.ui \ gui/newDialogs/newtagdialog.ui \
gui/newDialogs/newunitdialog.ui \ gui/newDialogs/newunitdialog.ui \
openrecipedialog.ui gui/openrecipedialog.ui \
gui/mainwindow.ui
DISTFILES += \ DISTFILES += \
.gitignore .gitignore

View File

@ -1,4 +1,4 @@
#include "userInterface/mainwindow.h" #include "gui/mainwindow.h"
#include "ui_mainwindow.h" #include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) : MainWindow::MainWindow(QWidget *parent) :
@ -58,7 +58,7 @@ void MainWindow::setCookTime(QTime cookTime){
} }
void MainWindow::setServings(float servings){ 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<RecipeTag> tags){ void MainWindow::setTags(vector<RecipeTag> tags){
@ -72,8 +72,22 @@ void MainWindow::on_newButton_clicked(){
if (d.isAccepted()){ if (d.isAccepted()){
Recipe r = d.getRecipe(); Recipe r = d.getRecipe();
if (!this->recipeDB->storeRecipe(r)){ 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();
} }

View File

@ -8,6 +8,7 @@
#include "model/recipe/recipe.h" #include "model/recipe/recipe.h"
#include "model/recipe/ingredients/ingredientlistmodel.h" #include "model/recipe/ingredients/ingredientlistmodel.h"
#include "gui/newrecipedialog.h" #include "gui/newrecipedialog.h"
#include "gui/openrecipedialog.h"
#include "utils/stringutils.h" #include "utils/stringutils.h"
using namespace std; using namespace std;
@ -27,9 +28,14 @@ public:
//Loads all data from a recipe into the GUI components. //Loads all data from a recipe into the GUI components.
void loadFromRecipe(Recipe recipe); void loadFromRecipe(Recipe recipe);
private slots: private slots:
void on_newButton_clicked(); void on_newButton_clicked();
void on_openButton_clicked();
void on_exitButton_clicked();
private: private:
Ui::MainWindow *ui; Ui::MainWindow *ui;
RecipeDatabase *recipeDB; RecipeDatabase *recipeDB;

View File

@ -143,6 +143,12 @@ QPushButton#newButton:pressed{
</item> </item>
<item alignment="Qt::AlignTop"> <item alignment="Qt::AlignTop">
<widget class="QPushButton" name="openButton"> <widget class="QPushButton" name="openButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>150</width> <width>150</width>
@ -175,6 +181,9 @@ QPushButton#openButton:pressed{
</item> </item>
<item alignment="Qt::AlignTop"> <item alignment="Qt::AlignTop">
<widget class="QPushButton" name="browseButton"> <widget class="QPushButton" name="browseButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>150</width> <width>150</width>
@ -211,6 +220,56 @@ QPushButton#browseButton:pressed{
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="exitButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>150</horstretch>
<verstretch>80</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>150</width>
<height>80</height>
</size>
</property>
<property name="font">
<font>
<family>Noto Sans CJK KR Light</family>
<pointsize>20</pointsize>
<stylestrategy>PreferAntialias</stylestrategy>
</font>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">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);
}</string>
</property>
<property name="text">
<string>Exit</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="default">
<bool>false</bool>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -367,6 +426,9 @@ font: &quot;Noto Sans CJK KR&quot;;</string>
<property name="text"> <property name="text">
<string>Prep Time:</string> <string>Prep Time:</string>
</property> </property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -383,6 +445,9 @@ font: &quot;Noto Sans CJK KR&quot;;</string>
<property name="text"> <property name="text">
<string>Cook Time:</string> <string>Cook Time:</string>
</property> </property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -396,6 +461,9 @@ font: &quot;Noto Sans CJK KR&quot;;</string>
<property name="text"> <property name="text">
<string>Servings:</string> <string>Servings:</string>
</property> </property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget> </widget>
</item> </item>
</layout> </layout>

View File

@ -8,10 +8,8 @@ NewUnitDialog::NewUnitDialog(QWidget *parent) :
ui->setupUi(this); ui->setupUi(this);
ui->typeComboBox->clear(); ui->typeComboBox->clear();
ui->typeComboBox->setItemData(0, "Mass"); QStringList list({"Mass", "Volume", "Length", "Misc"});
ui->typeComboBox->setItemData(1, "Volume"); ui->typeComboBox->insertItems(0, list);
ui->typeComboBox->setItemData(2, "Length");
ui->typeComboBox->setItemData(3, "Misc");
} }
@ -23,7 +21,7 @@ NewUnitDialog::~NewUnitDialog()
UnitOfMeasure NewUnitDialog::getUnit(){ UnitOfMeasure NewUnitDialog::getUnit(){
return UnitOfMeasure(ui->unitNameEdit->text().toLower().toStdString(), return UnitOfMeasure(ui->unitNameEdit->text().toLower().toStdString(),
ui->pluralNameEdit->text().toLower().toStdString(), ui->pluralNameEdit->text().toLower().toStdString(),
ui->abbreviationEdit->text().toLower().toStdString(), ui->abbreviationEdit->text().toStdString(),
this->getSelectedType(), this->getSelectedType(),
ui->coefficientSpinBox->value()); ui->coefficientSpinBox->value());
} }

View File

@ -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(); QModelIndexList indexList = ui->ingredientsListView->selectionModel()->selectedIndexes();
for (QModelIndexList::iterator it = indexList.begin(); it != indexList.end(); ++it){ for (QModelIndexList::iterator it = indexList.begin(); it != indexList.end(); ++it){
QModelIndex i = *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(){ void NewRecipeDialog::on_newIngredientButton_clicked(){
NewIngredientDialog d(this); NewIngredientDialog d(this);
d.show(); d.show();
@ -155,12 +171,12 @@ void NewRecipeDialog::on_newTagButton_clicked(){
} }
void NewRecipeDialog::on_removeTagButton_clicked(){ void NewRecipeDialog::on_removeTagButton_clicked(){
int index = ui->tagsComboBox->currentIndex(); unsigned int index = ui->tagsComboBox->currentIndex();
if (index < 0 || index >= this->tags.size()){ if (index >= this->tags.size()){
return; return;
} }
RecipeTag tag = this->tags[ui->tagsComboBox->currentIndex()]; 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())); QMessageBox::StandardButton reply = QMessageBox::question(this, QString("Delete Tag"), QString(content.c_str()));
if (reply == QMessageBox::Yes){ if (reply == QMessageBox::Yes){
this->recipeDB->deleteTag(tag); this->recipeDB->deleteTag(tag);
@ -173,10 +189,25 @@ void NewRecipeDialog::on_newUnitButton_clicked(){
d.show(); d.show();
if (d.exec() == QDialog::Accepted){ if (d.exec() == QDialog::Accepted){
UnitOfMeasure u = d.getUnit(); 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."); QMessageBox::critical(this, "Error", "Unable to store new unit.");
} else { } else {
this->populateUnitsBox(); 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();
}
}
}

View File

@ -47,6 +47,8 @@ class NewRecipeDialog : public QDialog
void on_selectImageButton_clicked(); void on_selectImageButton_clicked();
void on_removeIngredientButton_clicked();
void on_deleteIngredientButton_clicked(); void on_deleteIngredientButton_clicked();
void on_newIngredientButton_clicked(); void on_newIngredientButton_clicked();
@ -57,6 +59,8 @@ class NewRecipeDialog : public QDialog
void on_newUnitButton_clicked(); void on_newUnitButton_clicked();
void on_deleteUnitButton_clicked();
private: private:
Ui::NewRecipeDialog *ui; Ui::NewRecipeDialog *ui;
RecipeDatabase *recipeDB; RecipeDatabase *recipeDB;

View File

@ -26,6 +26,9 @@
<iconset resource="../images.qrc"> <iconset resource="../images.qrc">
<normaloff>:/images/images/icon.png</normaloff>:/images/images/icon.png</iconset> <normaloff>:/images/images/icon.png</normaloff>:/images/images/icon.png</iconset>
</property> </property>
<property name="styleSheet">
<string notr="true">font: 25 &quot;Noto Sans CJK KR&quot;;</string>
</property>
<property name="modal"> <property name="modal">
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -97,9 +100,7 @@
<widget class="QLabel" name="recipeNameLabel"> <widget class="QLabel" name="recipeNameLabel">
<property name="font"> <property name="font">
<font> <font>
<family>Noto Sans CJK KR</family> <weight>3</weight>
<pointsize>14</pointsize>
<weight>50</weight>
<italic>false</italic> <italic>false</italic>
<bold>false</bold> <bold>false</bold>
</font> </font>
@ -116,7 +117,9 @@
<widget class="QLineEdit" name="recipeNameEdit"> <widget class="QLineEdit" name="recipeNameEdit">
<property name="font"> <property name="font">
<font> <font>
<stylestrategy>PreferAntialias</stylestrategy> <weight>3</weight>
<italic>false</italic>
<bold>false</bold>
</font> </font>
</property> </property>
<property name="alignment"> <property name="alignment">
@ -155,7 +158,7 @@
<second>0</second> <second>0</second>
<year>1999</year> <year>1999</year>
<month>12</month> <month>12</month>
<day>26</day> <day>25</day>
</datetime> </datetime>
</property> </property>
<property name="currentSection"> <property name="currentSection">
@ -501,6 +504,17 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="deleteIngredientButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../images.qrc">
<normaloff>:/images/images/minus_icon.png</normaloff>:/images/images/minus_icon.png</iconset>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -532,9 +546,7 @@
<widget class="QLabel" name="amountLabel"> <widget class="QLabel" name="amountLabel">
<property name="font"> <property name="font">
<font> <font>
<family>Noto Sans CJK KR</family> <weight>3</weight>
<pointsize>14</pointsize>
<weight>50</weight>
<italic>false</italic> <italic>false</italic>
<bold>false</bold> <bold>false</bold>
</font> </font>
@ -578,6 +590,17 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="deleteUnitButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../images.qrc">
<normaloff>:/images/images/minus_icon.png</normaloff>:/images/images/minus_icon.png</iconset>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -626,7 +649,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="deleteIngredientButton"> <widget class="QPushButton" name="removeIngredientButton">
<property name="text"> <property name="text">
<string>Delete</string> <string>Delete</string>
</property> </property>
@ -745,9 +768,9 @@
<widget class="QToolButton" name="italicsButton"> <widget class="QToolButton" name="italicsButton">
<property name="font"> <property name="font">
<font> <font>
<family>Liberation Serif</family> <weight>3</weight>
<pointsize>12</pointsize> <italic>false</italic>
<italic>true</italic> <bold>false</bold>
</font> </font>
</property> </property>
<property name="text"> <property name="text">
@ -765,10 +788,9 @@
<widget class="QToolButton" name="boldButton"> <widget class="QToolButton" name="boldButton">
<property name="font"> <property name="font">
<font> <font>
<family>Liberation Serif</family> <weight>3</weight>
<pointsize>12</pointsize> <italic>false</italic>
<weight>75</weight> <bold>false</bold>
<bold>true</bold>
</font> </font>
</property> </property>
<property name="text"> <property name="text">

64
gui/openrecipedialog.cpp Normal file
View File

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

39
gui/openrecipedialog.h Normal file
View File

@ -0,0 +1,39 @@
#ifndef OPENRECIPEDIALOG_H
#define OPENRECIPEDIALOG_H
#include <QDialog>
#include <QMessageBox>
#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

134
gui/openrecipedialog.ui Normal file
View File

@ -0,0 +1,134 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OpenRecipeDialog</class>
<widget class="QDialog" name="OpenRecipeDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>640</width>
<height>480</height>
</rect>
</property>
<property name="windowTitle">
<string>Open Recipe</string>
</property>
<property name="windowIcon">
<iconset resource="../images.qrc">
<normaloff>:/images/images/icon.png</normaloff>:/images/images/icon.png</iconset>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item alignment="Qt::AlignTop">
<widget class="QWidget" name="searchPanel" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QWidget" name="nameSearchPanel" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="nameLabel">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="nameEdit"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="tagsSearchpanel" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="tagLabel">
<property name="text">
<string>Tag</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="tagEdit"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="ingredientSearchPanel" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="ingredientLabel">
<property name="text">
<string>Ingredient</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="ingredientEdit"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QPushButton" name="searchButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../images.qrc">
<normaloff>:/images/images/search_icon.png</normaloff>:/images/images/search_icon.png</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item alignment="Qt::AlignLeft|Qt::AlignTop">
<widget class="QWidget" name="interactionPanel" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QPushButton" name="deleteRecipeButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../images.qrc">
<normaloff>:/images/images/trash.png</normaloff>:/images/images/trash.png</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="contentPanel" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTableView" name="recipeTableView">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../images.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -4,5 +4,7 @@
<file>images/icon.png</file> <file>images/icon.png</file>
<file>images/plus_icon.png</file> <file>images/plus_icon.png</file>
<file>images/minus_icon.png</file> <file>images/minus_icon.png</file>
<file>images/search_icon.png</file>
<file>images/trash.png</file>
</qresource> </qresource>
</RCC> </RCC>

BIN
images/search_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

BIN
images/trash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 B

View File

@ -1,4 +1,4 @@
#include "userInterface/mainwindow.h" #include "gui/mainwindow.h"
#include "gui/newrecipedialog.h" #include "gui/newrecipedialog.h"
#include <QApplication> #include <QApplication>
@ -13,42 +13,28 @@ int main(int argc, char *argv[])
w.show(); w.show();
//TESTING CODE //TESTING CODE
// vector<RecipeIngredient> ri; vector<RecipeIngredient> ri;
// ri.push_back(RecipeIngredient("flour", "grains", 3.0f, UnitOfMeasure("cup", "cups", "c", UnitOfMeasure::VOLUME, 1.0), "")); 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), "")); ri.push_back(RecipeIngredient("baking powder", "additives", 1.0f, UnitOfMeasure("teaspoon", "teaspoons", "tsp", UnitOfMeasure::VOLUME, 1.0), ""));
// Recipe rec("Example", Recipe rec("Example",
// ri, ri,
// Instruction("<b>BOLD</b><i>iTaLiCs</i>"), Instruction("<b>BOLD</b><i>iTaLiCs</i>"),
// QImage(), QImage(),
// vector<RecipeTag>({RecipeTag("testing"), vector<RecipeTag>({RecipeTag("testing"),
// RecipeTag("fake")}), RecipeTag("fake")}),
// QDate::currentDate(), QDate::currentDate(),
// QTime(0, 30), QTime(0, 30),
// QTime(0, 25), QTime(0, 25),
// 10.0f); 10.0f);
// bool success = recipeDB.storeRecipe(rec); bool success = recipeDB.storeRecipe(rec);
// printf("Storage successful: %d\n", success); printf("Storage successful: %d\n", success);
// recipeDB.storeUnitOfMeasure(UnitOfMeasure("tablespoon", "tablespoons", "tbsp", UnitOfMeasure::VOLUME, 1.0)); //recipeDB.selectFrom("recipe", "recipeId, name", "").printData();
// recipeDB.storeUnitOfMeasure(UnitOfMeasure("pinch", "pinches", "pch", UnitOfMeasure::VOLUME, 1.0)); w.loadFromRecipe(recipeDB.retrieveRandomRecipe());
// recipeDB.storeUnitOfMeasure(UnitOfMeasure("gram", "grams", "g", UnitOfMeasure::MASS, 1.0));
// Recipe reloadRec = recipeDB.retrieveRecipe("Example"); a.exec();
// reloadRec.print(); recipeDB.closeConnection();
return 0;
// 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();
} }

View File

@ -46,6 +46,20 @@ ResultTable Database::selectFrom(string tableName, string columnNames, string co
return this->executeSQL(query); 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(){ void Database::openConnection(){
this->returnCode = sqlite3_open(this->filename.c_str(), &this->db); this->returnCode = sqlite3_open(this->filename.c_str(), &this->db);
if (this->returnCode || this->db == NULL){ if (this->returnCode || this->db == NULL){

View File

@ -26,10 +26,13 @@ public:
ResultTable executeSQL(string statement); ResultTable executeSQL(string statement);
bool insertInto(string tableName, vector<string> columnNames, vector<string> values); bool insertInto(string tableName, vector<string> columnNames, vector<string> values);
ResultTable selectFrom(string tableName, string columnNames, string conditions); ResultTable selectFrom(string tableName, string columnNames, string conditions);
bool deleteFrom(string tableName, string conditions);
bool tableExists(string tableName); bool tableExists(string tableName);
int getLastInsertedRowId(); int getLastInsertedRowId();
void closeConnection();
protected: protected:
string surroundString(string s, string surround); string surroundString(string s, string surround);
@ -43,7 +46,6 @@ private:
char* errorMsg; char* errorMsg;
void openConnection(); void openConnection();
void closeConnection();
std::string combineVector(std::vector<std::string> strings, std::string mid); std::string combineVector(std::vector<std::string> strings, std::string mid);
}; };

View File

@ -5,6 +5,12 @@ RecipeDatabase::RecipeDatabase(string filename) : Database(filename){
} }
bool RecipeDatabase::storeRecipe(Recipe recipe){ 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. //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->executeSQL("BEGIN;");
ResultTable t = this->selectFrom("recipe", "*", "WHERE name="+surroundString(recipe.getName(), "'")); 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()); fprintf(stderr, "Error: No recipe with name %s found!\n", name.c_str());
return Recipe(); return Recipe();
} }
Recipe r; return this->readFromResultTable(t);
int id = std::stoi(t.valueAt(0, 0)); }
r.setName(t.valueAt(0, 1));
r.setCreatedDate(QDate::fromString(QString::fromStdString(t.valueAt(0, 2)))); Recipe RecipeDatabase::retrieveRandomRecipe(){
r.setPrepTime(QTime::fromString(QString::fromStdString(t.valueAt(0, 3)))); ResultTable t = this->selectFrom("recipe", "*", "ORDER BY RANDOM() LIMIT 1");
r.setCookTime(QTime::fromString(QString::fromStdString(t.valueAt(0, 4)))); if (t.isEmpty()){
r.setServings(std::stof(t.valueAt(0, 5))); fprintf(stderr, "Unable to find a random recipe.\n");
r.setInstruction(FileUtils::loadInstruction(id)); return Recipe();
r.setImage(FileUtils::loadImage(id)); }
r.setIngredients(this->retrieveRecipeIngredients(id)); return this->readFromResultTable(t);
r.setTags(this->retrieveTags(id)); }
return r;
vector<Recipe> RecipeDatabase::retrieveAllRecipes(){
ResultTable t = this->selectFrom("recipe", "name", "ORDER BY name");
vector<Recipe> recipes;
for (unsigned int row = 0; row < t.rowCount(); row++){
recipes.push_back(this->retrieveRecipe(t.valueAt(row, 0)));
}
return recipes;
} }
vector<RecipeIngredient> RecipeDatabase::retrieveRecipeIngredients(int recipeId){ vector<RecipeIngredient> RecipeDatabase::retrieveRecipeIngredients(int recipeId){
@ -223,8 +236,57 @@ vector<RecipeTag> RecipeDatabase::retrieveAllTags(){
return tags; return tags;
} }
void RecipeDatabase::deleteTag(RecipeTag tag){ bool RecipeDatabase::deleteRecipe(string name){
ResultTable t = this->executeSQL("DELETE FROM recipeTag WHERE tagName="+surroundString(tag.getValue(), "'")); 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(){ void RecipeDatabase::ensureTablesExist(){
@ -269,3 +331,18 @@ void RecipeDatabase::ensureTablesExist(){
"FOREIGN KEY (unitName) REFERENCES unitOfMeasure(name));"); "FOREIGN KEY (unitName) REFERENCES unitOfMeasure(name));");
this->executeSQL("COMMIT;"); 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;
}

View File

@ -32,6 +32,8 @@ class RecipeDatabase : public Database
//Retrieval. //Retrieval.
Recipe retrieveRecipe(string name); Recipe retrieveRecipe(string name);
Recipe retrieveRandomRecipe();
vector<Recipe> retrieveAllRecipes();
vector<RecipeIngredient> retrieveRecipeIngredients(int recipeId); vector<RecipeIngredient> retrieveRecipeIngredients(int recipeId);
vector<Ingredient> retrieveAllIngredients(); vector<Ingredient> retrieveAllIngredients();
vector<UnitOfMeasure> retrieveAllUnitsOfMeasure(); vector<UnitOfMeasure> retrieveAllUnitsOfMeasure();
@ -39,11 +41,17 @@ class RecipeDatabase : public Database
vector<RecipeTag> retrieveAllTags(); vector<RecipeTag> retrieveAllTags();
//Deletion. //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: private:
//Utility methods. //Utility methods.
void ensureTablesExist(); void ensureTablesExist();
//Read a recipe from a row of a result table.
Recipe readFromResultTable(ResultTable t, int row=0);
}; };
#endif // RECIPEDATABASE_H #endif // RECIPEDATABASE_H

View File

@ -50,7 +50,7 @@ string RecipeIngredient::toString(){
result += StringUtils::toString(this->getQuantity()); result += StringUtils::toString(this->getQuantity());
} }
result += " " + this->getUnit().getAbbreviation() + " " + this->getName(); result += " " + this->getUnit().getAbbreviation() + " " + this->getName();
if (!this->getComment().empty()) result += " ~" + this->getComment(); if (!this->getComment().empty()) result += " (" + this->getComment() + ")";
return result; return result;
} }

View File

@ -12,7 +12,7 @@ Recipe::Recipe(string name, vector<RecipeIngredient> ingredients, Instruction in
setServings(servings); setServings(servings);
} }
Recipe::Recipe() : Recipe::Recipe("Unnamed Recipe", vector<RecipeIngredient>(), Instruction(), QImage(), vector<RecipeTag>(), QDate::currentDate(), QTime(1, 0), QTime(0, 30), 10.0f){ Recipe::Recipe() : Recipe::Recipe("", vector<RecipeIngredient>(), Instruction(), QImage(), vector<RecipeTag>(), QDate::currentDate(), QTime(1, 0), QTime(0, 30), 10.0f){
//Set default values when none are specified. //Set default values when none are specified.
} }
@ -56,6 +56,10 @@ float Recipe::getServings() const{
return this->servings; return this->servings;
} }
bool Recipe::isEmpty() const{
return this->name.empty();
}
void Recipe::setName(string newName){ void Recipe::setName(string newName){
this->name = newName; this->name = newName;
} }

View File

@ -46,6 +46,7 @@ public:
QTime getCookTime() const; QTime getCookTime() const;
QTime getTotalTime() const; //Derived method to add prep and cook times. QTime getTotalTime() const; //Derived method to add prep and cook times.
float getServings() const; float getServings() const;
bool isEmpty() const;
//Setters //Setters
void setName(string newName); void setName(string newName);

View File

@ -0,0 +1,86 @@
#include "recipetablemodel.h"
RecipeTableModel::RecipeTableModel()
{
}
RecipeTableModel::RecipeTableModel(vector<Recipe> 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<Recipe> 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();
}

View File

@ -0,0 +1,29 @@
#ifndef RECIPETABLEMODEL_H
#define RECIPETABLEMODEL_H
#include <QAbstractTableModel>
#include "model/recipe/recipe.h"
#include "utils/stringutils.h"
class RecipeTableModel : public QAbstractTableModel
{
public:
RecipeTableModel();
RecipeTableModel(vector<Recipe> 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<Recipe> recipes);
Recipe getRecipeAt(int index);
void clear();
private:
vector<Recipe> recipes;
};
#endif // RECIPETABLEMODEL_H

View File

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

View File

@ -1,22 +0,0 @@
#ifndef OPENRECIPEDIALOG_H
#define OPENRECIPEDIALOG_H
#include <QDialog>
namespace Ui {
class OpenRecipeDialog;
}
class OpenRecipeDialog : public QDialog
{
Q_OBJECT
public:
explicit OpenRecipeDialog(QWidget *parent = 0);
~OpenRecipeDialog();
private:
Ui::OpenRecipeDialog *ui;
};
#endif // OPENRECIPEDIALOG_H

View File

@ -1,19 +0,0 @@
<?xml version='1.0'?>
<ui version="4.0">
<class>OpenRecipeDialog</class>
<widget name="OpenRecipeDialog" class="QDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>640</width>
<height>480</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -2,6 +2,14 @@
namespace StringUtils{ 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){ std::string toString(float val){
float decimal = std::fmod(val, 1.0f); float decimal = std::fmod(val, 1.0f);
int places = 1; int places = 1;
@ -13,6 +21,11 @@ std::string toString(float val){
std::string arg = "%."+std::to_string(places)+"f"; std::string arg = "%."+std::to_string(places)+"f";
sprintf(buffer, arg.c_str(), val); sprintf(buffer, arg.c_str(), val);
std::string s = buffer; std::string s = buffer;
if (stringEndsWith(s, ".0")){
while (s.find('.') != std::string::npos){
s.pop_back();
}
}
return s; return s;
} }