--- /dev/null
+BasedOnStyle: Google
+IndentWidth: 4
+TabWidth: 4
+AccessModifierOffset: -4
+ColumnLimit: 100
+AllowShortFunctionsOnASingleLine: Empty
+KeepEmptyLinesAtTheStartOfBlocks: false
class DatabaseManager {
public:
- static DatabaseManager &instance();
- bool connect(const QString &path);
- [[nodiscard]] bool isOpen() const;
- [[nodiscard]] QSqlDatabase database() const;
- [[nodiscard]] QSqlDatabase userDatabase() const;
+ static DatabaseManager& instance();
+ bool connect(const QString& path);
+ [[nodiscard]] bool isOpen() const;
+ [[nodiscard]] QSqlDatabase database() const;
+ [[nodiscard]] QSqlDatabase userDatabase() const;
- DatabaseManager(const DatabaseManager &) = delete;
- DatabaseManager &operator=(const DatabaseManager &) = delete;
+ DatabaseManager(const DatabaseManager&) = delete;
+ DatabaseManager& operator=(const DatabaseManager&) = delete;
private:
- DatabaseManager();
- ~DatabaseManager();
+ DatabaseManager();
+ ~DatabaseManager();
- void initUserDatabase();
+ void initUserDatabase();
- QSqlDatabase m_db;
- QSqlDatabase m_userDb;
+ QSqlDatabase m_db;
+ QSqlDatabase m_userDb;
};
-#endif // DATABASEMANAGER_H
+#endif // DATABASEMANAGER_H
#include <vector>
struct Nutrient {
- int id;
- QString description;
- double amount;
- QString unit;
- double rdaPercentage; // Calculated
+ int id;
+ QString description;
+ double amount;
+ QString unit;
+ double rdaPercentage; // Calculated
};
struct ServingWeight {
- QString description;
- double grams;
+ QString description;
+ double grams;
};
struct FoodItem {
- int id;
- QString description;
- QString foodGroupName;
- int nutrientCount;
- int aminoCount;
- int flavCount;
- int score; // For search results
- std::vector<Nutrient> nutrients; // Full details for results
+ int id;
+ QString description;
+ QString foodGroupName;
+ int nutrientCount;
+ int aminoCount;
+ int flavCount;
+ int score; // For search results
+ std::vector<Nutrient> nutrients; // Full details for results
};
class FoodRepository {
public:
- explicit FoodRepository();
+ explicit FoodRepository();
- // Search foods by keyword
- std::vector<FoodItem> searchFoods(const QString &query);
+ // Search foods by keyword
+ std::vector<FoodItem> searchFoods(const QString& query);
- // Get detailed nutrients for a generic food (100g)
- // Returns a list of nutrients
- std::vector<Nutrient> getFoodNutrients(int foodId);
+ // Get detailed nutrients for a generic food (100g)
+ // Returns a list of nutrients
+ std::vector<Nutrient> getFoodNutrients(int foodId);
- // Get available serving weights (units) for a food
- std::vector<ServingWeight> getFoodServings(int foodId);
+ // Get available serving weights (units) for a food
+ std::vector<ServingWeight> getFoodServings(int foodId);
- // RDA methods
- std::map<int, double> getNutrientRdas();
- void updateRda(int nutrId, double value);
+ // RDA methods
+ std::map<int, double> getNutrientRdas();
+ void updateRda(int nutrId, double value);
- // Helper to get nutrient definition basics if needed
- // QString getNutrientName(int nutrientId);
+ // Helper to get nutrient definition basics if needed
+ // QString getNutrientName(int nutrientId);
private:
- // Internal helper methods
- void ensureCacheLoaded();
- void loadRdas();
+ // Internal helper methods
+ void ensureCacheLoaded();
+ void loadRdas();
- bool m_cacheLoaded = false;
- // Cache stores basic food info
- std::vector<FoodItem> m_cache;
- std::map<int, double> m_rdas;
+ bool m_cacheLoaded = false;
+ // Cache stores basic food info
+ std::vector<FoodItem> m_cache;
+ std::map<int, double> m_rdas;
};
-#endif // FOODREPOSITORY_H
+#endif // FOODREPOSITORY_H
--- /dev/null
+#ifndef MEALREPOSITORY_H
+#define MEALREPOSITORY_H
+
+#include <QDate>
+#include <QString>
+#include <map>
+#include <vector>
+
+struct MealLogItem {
+ int id; // log_food.id
+ int foodId;
+ double grams;
+ int mealId;
+ QString mealName;
+ // Potentially cached description?
+ QString foodName; // Joined from food_des if we query it
+};
+
+class MealRepository {
+public:
+ MealRepository();
+
+ // Meal Names (Breakfast, Lunch, etc.)
+ std::map<int, QString> getMealNames();
+
+ // Logging
+ void addFoodLog(int foodId, double grams, int mealId, QDate date = QDate::currentDate());
+ std::vector<MealLogItem> getDailyLogs(QDate date = QDate::currentDate());
+ void clearDailyLogs(QDate date = QDate::currentDate());
+ void removeLogEntry(int logId);
+
+private:
+ std::map<int, QString> m_mealNamesCache;
+ void ensureMealNamesLoaded();
+};
+
+#endif // MEALREPOSITORY_H
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
+#include <QMainWindow>
+#include <QTabWidget>
+
#include "widgets/detailswidget.h"
#include "widgets/mealwidget.h"
#include "widgets/searchwidget.h"
-#include <QMainWindow>
-#include <QTabWidget>
class MainWindow : public QMainWindow {
- Q_OBJECT
+ Q_OBJECT
public:
- MainWindow(QWidget *parent = nullptr);
- ~MainWindow() override;
+ MainWindow(QWidget* parent = nullptr);
+ ~MainWindow() override;
private slots:
- void onOpenDatabase();
- void onRecentFileClick();
- void onSettings();
- void onAbout();
+ void onOpenDatabase();
+ void onRecentFileClick();
+ void onSettings();
+ void onAbout();
private:
- void setupUi();
- void updateRecentFileActions();
- void addToRecentFiles(const QString &path);
-
- QTabWidget *tabs;
- SearchWidget *searchWidget;
- DetailsWidget *detailsWidget;
- MealWidget *mealWidget;
- FoodRepository repository;
-
- QMenu *recentFilesMenu;
- static constexpr int MaxRecentFiles = 5;
- QAction *recentFileActions[MaxRecentFiles];
+ void setupUi();
+ void updateRecentFileActions();
+ void addToRecentFiles(const QString& path);
+
+ QTabWidget* tabs;
+ SearchWidget* searchWidget;
+ DetailsWidget* detailsWidget;
+ MealWidget* mealWidget;
+ FoodRepository repository;
+
+ QMenu* recentFilesMenu;
+ static constexpr int MaxRecentFiles = 5;
+ QAction* recentFileActions[MaxRecentFiles];
};
-#endif // MAINWINDOW_H
+#endif // MAINWINDOW_H
namespace Utils {
// Calculate Levenshtein distance between two strings
-int levenshteinDistance(const QString &s1, const QString &s2);
+int levenshteinDistance(const QString& s1, const QString& s2);
// Calculate a simple fuzzy match score (0-100)
// Higher is better.
-int calculateFuzzyScore(const QString &query, const QString &target);
+int calculateFuzzyScore(const QString& query, const QString& target);
-} // namespace Utils
+} // namespace Utils
-#endif // STRING_UTILS_H
+#endif // STRING_UTILS_H
#ifndef DETAILSWIDGET_H
#define DETAILSWIDGET_H
-#include "db/foodrepository.h"
#include <QLabel>
#include <QPushButton>
#include <QTableWidget>
#include <QWidget>
+#include "db/foodrepository.h"
+
class DetailsWidget : public QWidget {
- Q_OBJECT
+ Q_OBJECT
public:
- explicit DetailsWidget(QWidget *parent = nullptr);
+ explicit DetailsWidget(QWidget* parent = nullptr);
- void loadFood(int foodId, const QString &foodName);
+ void loadFood(int foodId, const QString& foodName);
signals:
- void addToMeal(int foodId, const QString &foodName, double grams);
+ void addToMeal(int foodId, const QString& foodName, double grams);
private slots:
- void onAddClicked();
+ void onAddClicked();
private:
- QLabel *nameLabel;
- QTableWidget *nutrientsTable;
- QPushButton *addButton;
- FoodRepository repository;
+ QLabel* nameLabel;
+ QTableWidget* nutrientsTable;
+ QPushButton* addButton;
+ FoodRepository repository;
- int currentFoodId;
- QString currentFoodName;
+ int currentFoodId;
+ QString currentFoodName;
};
-#endif // DETAILSWIDGET_H
+#endif // DETAILSWIDGET_H
#ifndef MEALWIDGET_H
#define MEALWIDGET_H
-#include "db/foodrepository.h"
#include <QPushButton>
#include <QTableWidget>
#include <QWidget>
-#include <map>
#include <vector>
+#include "db/foodrepository.h"
+#include "db/mealrepository.h"
+
struct MealItem {
- int foodId;
- QString name;
- double grams;
- std::vector<Nutrient> nutrients_100g; // Base nutrients
+ int foodId;
+ QString name;
+ double grams;
+ std::vector<Nutrient> nutrients_100g;
};
class MealWidget : public QWidget {
- Q_OBJECT
+ Q_OBJECT
public:
- explicit MealWidget(QWidget *parent = nullptr);
+ explicit MealWidget(QWidget* parent = nullptr);
- void addFood(int foodId, const QString &foodName, double grams);
+ void addFood(int foodId, const QString& foodName, double grams);
private slots:
- void clearMeal();
+ void clearMeal();
private:
- void updateTotals();
+ void updateTotals();
+ void refresh();
+
+ QTableWidget* itemsTable;
+ QPushButton* clearButton;
+ QTableWidget* totalsTable;
- QTableWidget *itemsTable;
- QTableWidget *totalsTable;
- QPushButton *clearButton;
+ FoodRepository repository;
+ MealRepository m_mealRepo;
- std::vector<MealItem> mealItems;
- FoodRepository repository;
+ std::vector<MealItem> mealItems;
};
-#endif // MEALWIDGET_H
+#endif // MEALWIDGET_H
#ifndef RDASETTINGSWIDGET_H
#define RDASETTINGSWIDGET_H
-#include "db/foodrepository.h"
#include <QDialog>
#include <QTableWidget>
+#include "db/foodrepository.h"
+
class RDASettingsWidget : public QDialog {
- Q_OBJECT
+ Q_OBJECT
public:
- explicit RDASettingsWidget(FoodRepository &repository,
- QWidget *parent = nullptr);
+ explicit RDASettingsWidget(FoodRepository& repository, QWidget* parent = nullptr);
private slots:
- void onCellChanged(int row, int column);
+ void onCellChanged(int row, int column);
private:
- void loadData();
+ void loadData();
- FoodRepository &m_repository;
- QTableWidget *m_table;
- bool m_loading = false;
+ FoodRepository& m_repository;
+ QTableWidget* m_table;
+ bool m_loading = false;
};
-#endif // RDASETTINGSWIDGET_H
+#endif // RDASETTINGSWIDGET_H
#ifndef SEARCHWIDGET_H
#define SEARCHWIDGET_H
-#include "db/foodrepository.h"
#include <QLineEdit>
#include <QPushButton>
#include <QTableWidget>
#include <QTimer>
#include <QWidget>
+#include "db/foodrepository.h"
+
class SearchWidget : public QWidget {
- Q_OBJECT
+ Q_OBJECT
public:
- explicit SearchWidget(QWidget *parent = nullptr);
+ explicit SearchWidget(QWidget* parent = nullptr);
signals:
- void foodSelected(int foodId, const QString &foodName);
- void addToMealRequested(int foodId, const QString &foodName, double grams);
+ void foodSelected(int foodId, const QString& foodName);
+ void addToMealRequested(int foodId, const QString& foodName, double grams);
private slots:
- void performSearch();
- void onRowDoubleClicked(int row, int column);
- void onCustomContextMenu(const QPoint &pos);
+ void performSearch();
+ void onRowDoubleClicked(int row, int column);
+ void onCustomContextMenu(const QPoint& pos);
private:
- QLineEdit *searchInput;
- QPushButton *searchButton;
- QTableWidget *resultsTable;
- FoodRepository repository;
- QTimer *searchTimer;
+ QLineEdit* searchInput;
+ QPushButton* searchButton;
+ QTableWidget* resultsTable;
+ FoodRepository repository;
+ QTimer* searchTimer;
};
-#endif // SEARCHWIDGET_H
+#endif // SEARCHWIDGET_H
#ifndef WEIGHTINPUTDIALOG_H
#define WEIGHTINPUTDIALOG_H
-#include "db/foodrepository.h"
#include <QComboBox>
#include <QDialog>
#include <QDoubleSpinBox>
#include <QLabel>
+#include "db/foodrepository.h"
+
class WeightInputDialog : public QDialog {
- Q_OBJECT
+ Q_OBJECT
public:
- explicit WeightInputDialog(const QString &foodName,
- const std::vector<ServingWeight> &servings,
- QWidget *parent = nullptr);
+ explicit WeightInputDialog(const QString& foodName, const std::vector<ServingWeight>& servings,
+ QWidget* parent = nullptr);
- double getGrams() const;
+ double getGrams() const;
private:
- QDoubleSpinBox *amountSpinBox;
- QComboBox *unitComboBox;
- std::vector<ServingWeight> m_servings;
+ QDoubleSpinBox* amountSpinBox;
+ QComboBox* unitComboBox;
+ std::vector<ServingWeight> m_servings;
- static constexpr double GRAMS_PER_OZ = 28.3495;
- static constexpr double GRAMS_PER_LB = 453.592;
+ static constexpr double GRAMS_PER_OZ = 28.3495;
+ static constexpr double GRAMS_PER_LB = 453.592;
};
-#endif // WEIGHTINPUTDIALOG_H
+#endif // WEIGHTINPUTDIALOG_H
#include "db/databasemanager.h"
+
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QSqlError>
#include <QVariant>
-DatabaseManager &DatabaseManager::instance() {
- static DatabaseManager instance;
- return instance;
+DatabaseManager& DatabaseManager::instance() {
+ static DatabaseManager instance;
+ return instance;
}
DatabaseManager::DatabaseManager() {
- m_userDb = QSqlDatabase::addDatabase("QSQLITE", "user_db");
- initUserDatabase();
+ m_userDb = QSqlDatabase::addDatabase("QSQLITE", "user_db");
+ initUserDatabase();
}
DatabaseManager::~DatabaseManager() {
- if (m_db.isOpen()) {
- m_db.close();
- }
+ if (m_db.isOpen()) {
+ m_db.close();
+ }
}
-bool DatabaseManager::connect(const QString &path) {
- if (m_db.isOpen()) {
- return true;
- }
+bool DatabaseManager::connect(const QString& path) {
+ if (m_db.isOpen()) {
+ return true;
+ }
- if (!QFileInfo::exists(path)) {
- qCritical() << "Database file not found:" << path;
- return false;
- }
+ if (!QFileInfo::exists(path)) {
+ qCritical() << "Database file not found:" << path;
+ return false;
+ }
- m_db = QSqlDatabase::addDatabase("QSQLITE");
- m_db.setDatabaseName(path);
- m_db.setConnectOptions("QSQLITE_OPEN_READONLY");
+ m_db = QSqlDatabase::addDatabase("QSQLITE");
+ m_db.setDatabaseName(path);
+ m_db.setConnectOptions("QSQLITE_OPEN_READONLY");
- if (!m_db.open()) {
- qCritical() << "Error opening database:" << m_db.lastError().text();
- return false;
- }
+ if (!m_db.open()) {
+ qCritical() << "Error opening database:" << m_db.lastError().text();
+ return false;
+ }
- return true;
+ return true;
}
-bool DatabaseManager::isOpen() const { return m_db.isOpen(); }
+bool DatabaseManager::isOpen() const {
+ return m_db.isOpen();
+}
-QSqlDatabase DatabaseManager::database() const { return m_db; }
+QSqlDatabase DatabaseManager::database() const {
+ return m_db;
+}
-QSqlDatabase DatabaseManager::userDatabase() const { return m_userDb; }
+QSqlDatabase DatabaseManager::userDatabase() const {
+ return m_userDb;
+}
void DatabaseManager::initUserDatabase() {
- QString path = QDir::homePath() + "/.nutra/nt.sqlite3";
- m_userDb.setDatabaseName(path);
-
- if (!m_userDb.open()) {
- qCritical() << "Failed to open user database:"
- << m_userDb.lastError().text();
- return;
- }
-
- QSqlQuery query(m_userDb);
- // Create profile table (simplified version of CLI's schema)
- if (!query.exec("CREATE TABLE IF NOT EXISTS profile ("
- "id INTEGER PRIMARY KEY AUTOINCREMENT, "
- "name TEXT UNIQUE NOT NULL)")) {
- qCritical() << "Failed to create profile table:"
- << query.lastError().text();
- }
-
- // Ensure default profile exists
- query.exec("INSERT OR IGNORE INTO profile (id, name) VALUES (1, 'default')");
-
- // Create rda table
- if (!query.exec("CREATE TABLE IF NOT EXISTS rda ("
- "profile_id INTEGER NOT NULL, "
- "nutr_id INTEGER NOT NULL, "
- "rda REAL NOT NULL, "
- "PRIMARY KEY (profile_id, nutr_id), "
- "FOREIGN KEY (profile_id) REFERENCES profile (id))")) {
- qCritical() << "Failed to create rda table:" << query.lastError().text();
- }
+ QString path = QDir::homePath() + "/.nutra/nt.sqlite3";
+ m_userDb.setDatabaseName(path);
+
+ if (!m_userDb.open()) {
+ qCritical() << "Failed to open user database:" << m_userDb.lastError().text();
+ return;
+ }
+
+ QSqlQuery query(m_userDb);
+
+ // Helper to execute schema creation
+ auto createTable = [&](const QString& sql) {
+ if (!query.exec(sql)) {
+ qCritical() << "Failed to create table:" << query.lastError().text() << "\nSQL:" << sql;
+ }
+ };
+
+ createTable(
+ "CREATE TABLE IF NOT EXISTS version ("
+ "id integer PRIMARY KEY AUTOINCREMENT, "
+ "version text NOT NULL UNIQUE, "
+ "created date NOT NULL, "
+ "notes text)");
+
+ createTable(
+ "CREATE TABLE IF NOT EXISTS bmr_eq ("
+ "id integer PRIMARY KEY, "
+ "name text NOT NULL UNIQUE)");
+
+ createTable(
+ "CREATE TABLE IF NOT EXISTS bf_eq ("
+ "id integer PRIMARY KEY, "
+ "name text NOT NULL UNIQUE)");
+
+ createTable(
+ "CREATE TABLE IF NOT EXISTS profile ("
+ "id integer PRIMARY KEY AUTOINCREMENT, "
+ "uuid int NOT NULL DEFAULT (RANDOM()), "
+ "name text NOT NULL UNIQUE, "
+ "gender text, "
+ "dob date, "
+ "act_lvl int DEFAULT 2, "
+ "goal_wt real, "
+ "goal_bf real DEFAULT 18, "
+ "bmr_eq_id int DEFAULT 1, "
+ "bf_eq_id int DEFAULT 1, "
+ "created int DEFAULT (strftime ('%s', 'now')), "
+ "FOREIGN KEY (bmr_eq_id) REFERENCES bmr_eq (id) ON UPDATE "
+ "CASCADE ON DELETE CASCADE, "
+ "FOREIGN KEY (bf_eq_id) REFERENCES bf_eq (id) ON UPDATE CASCADE "
+ "ON DELETE CASCADE)");
+
+ createTable(
+ "CREATE TABLE IF NOT EXISTS rda ("
+ "profile_id int NOT NULL, "
+ "nutr_id int NOT NULL, "
+ "rda real NOT NULL, "
+ "PRIMARY KEY (profile_id, nutr_id), "
+ "FOREIGN KEY (profile_id) REFERENCES profile (id) ON UPDATE "
+ "CASCADE ON DELETE CASCADE)");
+
+ createTable(
+ "CREATE TABLE IF NOT EXISTS custom_food ("
+ "id integer PRIMARY KEY AUTOINCREMENT, "
+ "tagname text NOT NULL UNIQUE, "
+ "name text NOT NULL UNIQUE, "
+ "created int DEFAULT (strftime ('%s', 'now')))");
+
+ createTable(
+ "CREATE TABLE IF NOT EXISTS cf_dat ("
+ "cf_id int NOT NULL, "
+ "nutr_id int NOT NULL, "
+ "nutr_val real NOT NULL, "
+ "notes text, "
+ "created int DEFAULT (strftime ('%s', 'now')), "
+ "PRIMARY KEY (cf_id, nutr_id), "
+ "FOREIGN KEY (cf_id) REFERENCES custom_food (id) ON UPDATE "
+ "CASCADE ON DELETE CASCADE)");
+
+ createTable(
+ "CREATE TABLE IF NOT EXISTS meal_name ("
+ "id integer PRIMARY KEY AUTOINCREMENT, "
+ "name text NOT NULL)");
+
+ createTable(
+ "CREATE TABLE IF NOT EXISTS log_food ("
+ "id integer PRIMARY KEY AUTOINCREMENT, "
+ "profile_id int NOT NULL, "
+ "date int DEFAULT (strftime ('%s', 'now')), "
+ "meal_id int NOT NULL, "
+ "food_id int NOT NULL, "
+ "msre_id int NOT NULL, "
+ "amt real NOT NULL, "
+ "created int DEFAULT (strftime ('%s', 'now')), "
+ "FOREIGN KEY (profile_id) REFERENCES profile (id) ON UPDATE "
+ "CASCADE ON DELETE CASCADE, "
+ "FOREIGN KEY (meal_id) REFERENCES meal_name (id) ON UPDATE "
+ "CASCADE ON DELETE CASCADE)");
+
+ createTable(
+ "CREATE TABLE IF NOT EXISTS log_cf ("
+ "id integer PRIMARY KEY AUTOINCREMENT, "
+ "profile_id int NOT NULL, "
+ "date int DEFAULT (strftime ('%s', 'now')), "
+ "meal_id int NOT NULL, "
+ "food_id int NOT NULL, "
+ "custom_food_id int, "
+ "msre_id int NOT NULL, "
+ "amt real NOT NULL, "
+ "created int DEFAULT (strftime ('%s', 'now')), "
+ "FOREIGN KEY (profile_id) REFERENCES profile (id) ON UPDATE "
+ "CASCADE ON DELETE CASCADE, "
+ "FOREIGN KEY (meal_id) REFERENCES meal_name (id) ON UPDATE "
+ "CASCADE ON DELETE CASCADE, "
+ "FOREIGN KEY (custom_food_id) REFERENCES custom_food (id) ON "
+ "UPDATE CASCADE ON DELETE CASCADE)");
+
+ // Default Data Seeding
+
+ // Ensure default profile exists
+ query.exec("INSERT OR IGNORE INTO profile (id, name) VALUES (1, 'default')");
+
+ // Seed standard meal names if table is empty
+ query.exec("SELECT count(*) FROM meal_name");
+ if (query.next() && query.value(0).toInt() == 0) {
+ QStringList meals = {"Breakfast", "Lunch", "Dinner", "Snack", "Brunch"};
+ for (const auto& meal : meals) {
+ query.prepare("INSERT INTO meal_name (name) VALUES (?)");
+ query.addBindValue(meal);
+ query.exec();
+ }
+ }
}
#include "db/foodrepository.h"
-#include "db/databasemanager.h"
+
#include <QDebug>
#include <QSqlError>
#include <QSqlQuery>
#include <QVariant>
#include <map>
+#include "db/databasemanager.h"
+
FoodRepository::FoodRepository() {}
-#include "utils/string_utils.h"
#include <algorithm>
+#include "utils/string_utils.h"
+
// ...
void FoodRepository::ensureCacheLoaded() {
- if (m_cacheLoaded)
- return;
-
- QSqlDatabase db = DatabaseManager::instance().database();
- if (!db.isOpen())
- return;
-
- // 1. Load Food Items with Group Names
- QSqlQuery query("SELECT f.id, f.long_desc, g.fdgrp_desc "
- "FROM food_des f "
- "JOIN fdgrp g ON f.fdgrp_id = g.id",
- db);
- std::map<int, int> nutrientCounts;
-
- // 2. Load Nutrient Counts (Bulk)
- QSqlQuery countQuery(
- "SELECT food_id, count(*) FROM nut_data GROUP BY food_id", db);
- while (countQuery.next()) {
- nutrientCounts[countQuery.value(0).toInt()] = countQuery.value(1).toInt();
- }
-
- while (query.next()) {
- FoodItem item;
- item.id = query.value(0).toInt();
- item.description = query.value(1).toString();
- item.foodGroupName = query.value(2).toString();
-
- // Set counts from map (default 0 if not found)
- auto it = nutrientCounts.find(item.id);
- item.nutrientCount = (it != nutrientCounts.end()) ? it->second : 0;
-
- item.aminoCount = 0; // TODO: Implement specific counts if needed
- item.flavCount = 0;
- item.score = 0;
- m_cache.push_back(item);
- }
- loadRdas();
- m_cacheLoaded = true;
-}
-
-void FoodRepository::loadRdas() {
- m_rdas.clear();
- QSqlDatabase db = DatabaseManager::instance().database();
- QSqlDatabase userDb = DatabaseManager::instance().userDatabase();
+ if (m_cacheLoaded) return;
- // 1. Load Defaults from USDA
- if (db.isOpen()) {
- QSqlQuery query("SELECT id, rda FROM nutrients_overview", db);
- while (query.next()) {
- m_rdas[query.value(0).toInt()] = query.value(1).toDouble();
+ QSqlDatabase db = DatabaseManager::instance().database();
+ if (!db.isOpen()) return;
+
+ // 1. Load Food Items with Group Names
+ QSqlQuery query(
+ "SELECT f.id, f.long_desc, g.fdgrp_desc "
+ "FROM food_des f "
+ "JOIN fdgrp g ON f.fdgrp_id = g.id",
+ db);
+ std::map<int, int> nutrientCounts;
+
+ // 2. Load Nutrient Counts (Bulk)
+ QSqlQuery countQuery("SELECT food_id, count(*) FROM nut_data GROUP BY food_id", db);
+ while (countQuery.next()) {
+ nutrientCounts[countQuery.value(0).toInt()] = countQuery.value(1).toInt();
}
- }
- // 2. Load Overrides from User DB
- if (userDb.isOpen()) {
- QSqlQuery query("SELECT nutr_id, rda FROM rda WHERE profile_id = 1",
- userDb);
while (query.next()) {
- m_rdas[query.value(0).toInt()] = query.value(1).toDouble();
+ FoodItem item;
+ item.id = query.value(0).toInt();
+ item.description = query.value(1).toString();
+ item.foodGroupName = query.value(2).toString();
+
+ // Set counts from map (default 0 if not found)
+ auto it = nutrientCounts.find(item.id);
+ item.nutrientCount = (it != nutrientCounts.end()) ? it->second : 0;
+
+ item.aminoCount = 0; // TODO: Implement specific counts if needed
+ item.flavCount = 0;
+ item.score = 0;
+ m_cache.push_back(item);
}
- }
+ loadRdas();
+ m_cacheLoaded = true;
}
-std::vector<FoodItem> FoodRepository::searchFoods(const QString &query) {
- ensureCacheLoaded();
- std::vector<FoodItem> results;
+void FoodRepository::loadRdas() {
+ m_rdas.clear();
+ QSqlDatabase db = DatabaseManager::instance().database();
+ QSqlDatabase userDb = DatabaseManager::instance().userDatabase();
+
+ // 1. Load Defaults from USDA
+ if (db.isOpen()) {
+ QSqlQuery query("SELECT id, rda FROM nutrients_overview", db);
+ while (query.next()) {
+ m_rdas[query.value(0).toInt()] = query.value(1).toDouble();
+ }
+ }
- if (query.trimmed().isEmpty())
- return results;
+ // 2. Load Overrides from User DB
+ if (userDb.isOpen()) {
+ QSqlQuery query("SELECT nutr_id, rda FROM rda WHERE profile_id = 1", userDb);
+ while (query.next()) {
+ m_rdas[query.value(0).toInt()] = query.value(1).toDouble();
+ }
+ }
+}
- // Calculate scores
- // create a temporary list of pointers or indices to sort?
- // Copying might be expensive if cache is huge (8k items is fine though)
-
- // Let's iterate and keep top matches.
- struct ScoredItem {
- const FoodItem *item;
- int score;
- };
- std::vector<ScoredItem> scoredItems;
- scoredItems.reserve(m_cache.size());
-
- for (const auto &item : m_cache) {
- int score = Utils::calculateFuzzyScore(query, item.description);
- if (score > 40) { // Threshold
- scoredItems.push_back({&item, score});
+std::vector<FoodItem> FoodRepository::searchFoods(const QString& query) {
+ ensureCacheLoaded();
+ std::vector<FoodItem> results;
+
+ if (query.trimmed().isEmpty()) return results;
+
+ // Calculate scores
+ // create a temporary list of pointers or indices to sort?
+ // Copying might be expensive if cache is huge (8k items is fine though)
+
+ // Let's iterate and keep top matches.
+ struct ScoredItem {
+ const FoodItem* item;
+ int score;
+ };
+ std::vector<ScoredItem> scoredItems;
+ scoredItems.reserve(m_cache.size());
+
+ for (const auto& item : m_cache) {
+ int score = Utils::calculateFuzzyScore(query, item.description);
+ if (score > 40) { // Threshold
+ scoredItems.push_back({&item, score});
+ }
}
- }
-
- // Sort by score desc
- std::sort(scoredItems.begin(), scoredItems.end(),
- [](const ScoredItem &a, const ScoredItem &b) {
- return a.score > b.score;
- });
-
- // Take top 100
- int count = 0;
- std::vector<int> resultIds;
- std::map<int, int> idToIndex;
-
- for (const auto &si : scoredItems) {
- if (count >= 100)
- break;
- FoodItem res = *si.item;
- res.score = si.score;
- // We will populate nutrients shortly
- results.push_back(res);
- resultIds.push_back(res.id);
- idToIndex[res.id] = count;
- count++;
- }
-
- // Batch fetch nutrients for these results
- if (!resultIds.empty()) {
- QSqlDatabase db = DatabaseManager::instance().database();
- QStringList idStrings;
- for (int id : resultIds)
- idStrings << QString::number(id);
-
- QString sql =
- QString("SELECT n.food_id, n.nutr_id, n.nutr_val, d.nutr_desc, d.unit "
- "FROM nut_data n "
- "JOIN nutr_def d ON n.nutr_id = d.id "
- "WHERE n.food_id IN (%1)")
- .arg(idStrings.join(","));
-
- QSqlQuery nutQuery(sql, db);
- while (nutQuery.next()) {
- int fid = nutQuery.value(0).toInt();
- Nutrient nut;
- nut.id = nutQuery.value(1).toInt();
- nut.amount = nutQuery.value(2).toDouble();
- nut.description = nutQuery.value(3).toString();
- nut.unit = nutQuery.value(4).toString();
-
- if (m_rdas.count(nut.id) != 0U && m_rdas[nut.id] > 0) {
- nut.rdaPercentage = (nut.amount / m_rdas[nut.id]) * 100.0;
- } else {
- nut.rdaPercentage = 0.0;
- }
-
- if (idToIndex.count(fid) != 0U) {
- results[idToIndex[fid]].nutrients.push_back(nut);
- }
+
+ // Sort by score desc
+ std::sort(scoredItems.begin(), scoredItems.end(),
+ [](const ScoredItem& a, const ScoredItem& b) { return a.score > b.score; });
+
+ // Take top 100
+ int count = 0;
+ std::vector<int> resultIds;
+ std::map<int, int> idToIndex;
+
+ for (const auto& si : scoredItems) {
+ if (count >= 100) break;
+ FoodItem res = *si.item;
+ res.score = si.score;
+ // We will populate nutrients shortly
+ results.push_back(res);
+ resultIds.push_back(res.id);
+ idToIndex[res.id] = count;
+ count++;
}
- // Update counts based on actual data
- for (auto &res : results) {
- res.nutrientCount = static_cast<int>(res.nutrients.size());
- // TODO: Logic for amino/flav counts if we have ranges of IDs
+ // Batch fetch nutrients for these results
+ if (!resultIds.empty()) {
+ QSqlDatabase db = DatabaseManager::instance().database();
+ QStringList idStrings;
+ for (int id : resultIds) idStrings << QString::number(id);
+
+ QString sql = QString(
+ "SELECT n.food_id, n.nutr_id, n.nutr_val, d.nutr_desc, d.unit "
+ "FROM nut_data n "
+ "JOIN nutr_def d ON n.nutr_id = d.id "
+ "WHERE n.food_id IN (%1)")
+ .arg(idStrings.join(","));
+
+ QSqlQuery nutQuery(sql, db);
+ while (nutQuery.next()) {
+ int fid = nutQuery.value(0).toInt();
+ Nutrient nut;
+ nut.id = nutQuery.value(1).toInt();
+ nut.amount = nutQuery.value(2).toDouble();
+ nut.description = nutQuery.value(3).toString();
+ nut.unit = nutQuery.value(4).toString();
+
+ if (m_rdas.count(nut.id) != 0U && m_rdas[nut.id] > 0) {
+ nut.rdaPercentage = (nut.amount / m_rdas[nut.id]) * 100.0;
+ } else {
+ nut.rdaPercentage = 0.0;
+ }
+
+ if (idToIndex.count(fid) != 0U) {
+ results[idToIndex[fid]].nutrients.push_back(nut);
+ }
+ }
+
+ // Update counts based on actual data
+ for (auto& res : results) {
+ res.nutrientCount = static_cast<int>(res.nutrients.size());
+ // TODO: Logic for amino/flav counts if we have ranges of IDs
+ }
}
- }
- return results;
+ return results;
}
std::vector<Nutrient> FoodRepository::getFoodNutrients(int foodId) {
- std::vector<Nutrient> results;
- QSqlDatabase db = DatabaseManager::instance().database();
+ std::vector<Nutrient> results;
+ QSqlDatabase db = DatabaseManager::instance().database();
- if (!db.isOpen())
- return results;
+ if (!db.isOpen()) return results;
+
+ QSqlQuery query(db);
+ if (!query.prepare("SELECT n.nutr_id, n.nutr_val, d.nutr_desc, d.unit "
+ "FROM nut_data n "
+ "JOIN nutr_def d ON n.nutr_id = d.id "
+ "WHERE n.food_id = ?")) {
+ qCritical() << "Prepare failed:" << query.lastError().text();
+ return results;
+ }
- QSqlQuery query(db);
- if (!query.prepare("SELECT n.nutr_id, n.nutr_val, d.nutr_desc, d.unit "
- "FROM nut_data n "
- "JOIN nutr_def d ON n.nutr_id = d.id "
- "WHERE n.food_id = ?")) {
+ query.bindValue(0, foodId);
- qCritical() << "Prepare failed:" << query.lastError().text();
- return results;
- }
+ if (query.exec()) {
+ while (query.next()) {
+ Nutrient nut;
+ nut.id = query.value(0).toInt();
+ nut.amount = query.value(1).toDouble();
+ nut.description = query.value(2).toString();
+ nut.unit = query.value(3).toString();
- query.bindValue(0, foodId);
+ if (m_rdas.count(nut.id) != 0U && m_rdas[nut.id] > 0) {
+ nut.rdaPercentage = (nut.amount / m_rdas[nut.id]) * 100.0;
+ } else {
+ nut.rdaPercentage = 0.0;
+ }
- if (query.exec()) {
- while (query.next()) {
- Nutrient nut;
- nut.id = query.value(0).toInt();
- nut.amount = query.value(1).toDouble();
- nut.description = query.value(2).toString();
- nut.unit = query.value(3).toString();
-
- if (m_rdas.count(nut.id) != 0U && m_rdas[nut.id] > 0) {
- nut.rdaPercentage = (nut.amount / m_rdas[nut.id]) * 100.0;
- } else {
- nut.rdaPercentage = 0.0;
- }
-
- results.push_back(nut);
- }
+ results.push_back(nut);
+ }
- } else {
- qCritical() << "Nutrient query failed:" << query.lastError().text();
- }
+ } else {
+ qCritical() << "Nutrient query failed:" << query.lastError().text();
+ }
- return results;
+ return results;
}
std::vector<ServingWeight> FoodRepository::getFoodServings(int foodId) {
- std::vector<ServingWeight> results;
- QSqlDatabase db = DatabaseManager::instance().database();
-
- if (!db.isOpen())
- return results;
+ std::vector<ServingWeight> results;
+ QSqlDatabase db = DatabaseManager::instance().database();
- QSqlQuery query(db);
- if (!query.prepare("SELECT d.msre_desc, s.grams "
- "FROM serving s "
- "JOIN serv_desc d ON s.msre_id = d.id "
- "WHERE s.food_id = ?")) {
- qCritical() << "Prepare servings failed:" << query.lastError().text();
- return results;
- }
+ if (!db.isOpen()) return results;
- query.bindValue(0, foodId);
+ QSqlQuery query(db);
+ if (!query.prepare("SELECT d.msre_desc, s.grams "
+ "FROM serving s "
+ "JOIN serv_desc d ON s.msre_id = d.id "
+ "WHERE s.food_id = ?")) {
+ qCritical() << "Prepare servings failed:" << query.lastError().text();
+ return results;
+ }
- if (query.exec()) {
- while (query.next()) {
- ServingWeight sw;
- sw.description = query.value(0).toString();
- sw.grams = query.value(1).toDouble();
- results.push_back(sw);
+ query.bindValue(0, foodId);
+
+ if (query.exec()) {
+ while (query.next()) {
+ ServingWeight sw;
+ sw.description = query.value(0).toString();
+ sw.grams = query.value(1).toDouble();
+ results.push_back(sw);
+ }
+ } else {
+ qCritical() << "Servings query failed:" << query.lastError().text();
}
- } else {
- qCritical() << "Servings query failed:" << query.lastError().text();
- }
- return results;
+ return results;
}
std::map<int, double> FoodRepository::getNutrientRdas() {
- ensureCacheLoaded();
- return m_rdas;
+ ensureCacheLoaded();
+ return m_rdas;
}
void FoodRepository::updateRda(int nutrId, double value) {
- QSqlDatabase userDb = DatabaseManager::instance().userDatabase();
- if (!userDb.isOpen())
- return;
-
- QSqlQuery query(userDb);
- query.prepare("INSERT OR REPLACE INTO rda (profile_id, nutr_id, rda) "
- "VALUES (1, ?, ?)");
- query.bindValue(0, nutrId);
- query.bindValue(1, value);
-
- if (query.exec()) {
- m_rdas[nutrId] = value;
- } else {
- qCritical() << "Failed to update RDA:" << query.lastError().text();
- }
+ QSqlDatabase userDb = DatabaseManager::instance().userDatabase();
+ if (!userDb.isOpen()) return;
+
+ QSqlQuery query(userDb);
+ query.prepare(
+ "INSERT OR REPLACE INTO rda (profile_id, nutr_id, rda) "
+ "VALUES (1, ?, ?)");
+ query.bindValue(0, nutrId);
+ query.bindValue(1, value);
+
+ if (query.exec()) {
+ m_rdas[nutrId] = value;
+ } else {
+ qCritical() << "Failed to update RDA:" << query.lastError().text();
+ }
}
--- /dev/null
+#include "db/mealrepository.h"
+
+#include <QDateTime>
+#include <QDebug>
+#include <QSqlError>
+#include <QSqlQuery>
+#include <QVariant>
+
+#include "db/databasemanager.h"
+
+MealRepository::MealRepository() {}
+
+void MealRepository::ensureMealNamesLoaded() {
+ if (!m_mealNamesCache.empty()) return;
+
+ QSqlDatabase db = DatabaseManager::instance().userDatabase();
+ if (!db.isOpen()) return;
+
+ QSqlQuery query("SELECT id, name FROM meal_name", db);
+ while (query.next()) {
+ m_mealNamesCache[query.value(0).toInt()] = query.value(1).toString();
+ }
+}
+
+std::map<int, QString> MealRepository::getMealNames() {
+ ensureMealNamesLoaded();
+ return m_mealNamesCache;
+}
+
+void MealRepository::addFoodLog(int foodId, double grams, int mealId, QDate date) {
+ QSqlDatabase db = DatabaseManager::instance().userDatabase();
+ if (!db.isOpen()) return;
+
+ // Use current time if today, otherwise noon of target date
+ qint64 timestamp;
+ if (date == QDate::currentDate()) {
+ timestamp = QDateTime::currentSecsSinceEpoch();
+ } else {
+ timestamp = date.startOfDay().toSecsSinceEpoch() + 43200; // Noon
+ }
+
+ QSqlQuery query(db);
+ query.prepare(
+ "INSERT INTO log_food (profile_id, date, meal_id, food_id, msre_id, amt) "
+ "VALUES (1, ?, ?, ?, 0, ?)"); // msre_id 0 for default/grams
+ query.addBindValue(timestamp);
+ query.addBindValue(mealId);
+ query.addBindValue(foodId);
+ query.addBindValue(grams);
+
+ if (!query.exec()) {
+ qCritical() << "Failed to add food log:" << query.lastError().text();
+ }
+}
+
+std::vector<MealLogItem> MealRepository::getDailyLogs(QDate date) {
+ std::vector<MealLogItem> results;
+ QSqlDatabase userDb = DatabaseManager::instance().userDatabase();
+ QSqlDatabase mainDb = DatabaseManager::instance().database();
+
+ if (!userDb.isOpen()) return results;
+
+ ensureMealNamesLoaded();
+
+ qint64 startOfDay = date.startOfDay().toSecsSinceEpoch();
+ qint64 endOfDay = date.endOfDay().toSecsSinceEpoch();
+
+ QSqlQuery query(userDb);
+ query.prepare(
+ "SELECT id, food_id, meal_id, amt FROM log_food "
+ "WHERE date >= ? AND date <= ? AND profile_id = 1");
+ query.addBindValue(startOfDay);
+ query.addBindValue(endOfDay);
+
+ std::vector<int> foodIds;
+
+ if (query.exec()) {
+ while (query.next()) {
+ MealLogItem item;
+ item.id = query.value(0).toInt();
+ item.foodId = query.value(1).toInt();
+ item.mealId = query.value(2).toInt();
+ item.grams = query.value(3).toDouble();
+
+ if (m_mealNamesCache.count(item.mealId)) {
+ item.mealName = m_mealNamesCache[item.mealId];
+ } else {
+ item.mealName = "Unknown";
+ }
+
+ results.push_back(item);
+ foodIds.push_back(item.foodId);
+ }
+ } else {
+ qCritical() << "Failed to fetch daily logs:" << query.lastError().text();
+ }
+
+ // Hydrate food names from Main DB
+ if (!foodIds.empty() && mainDb.isOpen()) {
+ QStringList idStrings;
+ for (int id : foodIds) idStrings << QString::number(id);
+
+ // Simple name fetch
+ // Optimization: Could use FoodRepository cache if available, but direct
+ // query is safe here
+ QString sql =
+ QString("SELECT id, long_desc FROM food_des WHERE id IN (%1)").arg(idStrings.join(","));
+ QSqlQuery nameQuery(sql, mainDb);
+
+ std::map<int, QString> names;
+ while (nameQuery.next()) {
+ names[nameQuery.value(0).toInt()] = nameQuery.value(1).toString();
+ }
+
+ for (auto& item : results) {
+ if (names.count(item.foodId)) {
+ item.foodName = names[item.foodId];
+ } else {
+ item.foodName = "Unknown Food"; // Should not happen if DBs consistent
+ }
+ }
+ }
+
+ return results;
+}
+
+void MealRepository::clearDailyLogs(QDate date) {
+ QSqlDatabase db = DatabaseManager::instance().userDatabase();
+ if (!db.isOpen()) return;
+
+ qint64 startOfDay = date.startOfDay().toSecsSinceEpoch();
+ qint64 endOfDay = date.endOfDay().toSecsSinceEpoch();
+
+ QSqlQuery query(db);
+ query.prepare("DELETE FROM log_food WHERE date >= ? AND date <= ? AND profile_id = 1");
+ query.addBindValue(startOfDay);
+ query.addBindValue(endOfDay);
+
+ if (!query.exec()) {
+ qCritical() << "Failed to clear daily logs:" << query.lastError().text();
+ }
+}
+
+void MealRepository::removeLogEntry(int logId) {
+ QSqlDatabase db = DatabaseManager::instance().userDatabase();
+ if (!db.isOpen()) return;
+
+ QSqlQuery query(db);
+ query.prepare("DELETE FROM log_food WHERE id = ?");
+ query.addBindValue(logId);
+ query.exec();
+}
-#include "db/databasemanager.h"
-#include "mainwindow.h"
#include <QApplication>
#include <QDebug>
#include <QDir>
#include <QMessageBox>
#include <QStandardPaths>
-int main(int argc, char *argv[]) {
- QApplication app(argc, argv);
- QApplication::setApplicationName("Nutra");
- QApplication::setOrganizationName("NutraTech");
- QApplication::setWindowIcon(QIcon(":/resources/nutrition_icon-no_bg.png"));
+#include "db/databasemanager.h"
+#include "mainwindow.h"
- // Connect to database
- // Search order:
- // 1. Environment variable NUTRA_DB_PATH
- // 2. Local user data: ~/.local/share/nutra/usda.sqlite3
- // 3. System install: /usr/local/share/nutra/usda.sqlite3
- // 4. System install: /usr/share/nutra/usda.sqlite3
- // 5. Legacy: ~/.nutra/usda.sqlite3
+int main(int argc, char* argv[]) {
+ QApplication app(argc, argv);
+ QApplication::setApplicationName("Nutra");
+ QApplication::setOrganizationName("NutraTech");
+ QApplication::setWindowIcon(QIcon(":/resources/nutrition_icon-no_bg.png"));
- QStringList searchPaths;
- QString envPath = qEnvironmentVariable("NUTRA_DB_PATH");
- if (!envPath.isEmpty())
- searchPaths << envPath;
+ // Connect to database
+ // Search order:
+ // 1. Environment variable NUTRA_DB_PATH
+ // 2. Local user data: ~/.local/share/nutra/usda.sqlite3
+ // 3. System install: /usr/local/share/nutra/usda.sqlite3
+ // 4. System install: /usr/share/nutra/usda.sqlite3
+ // 5. Legacy: ~/.nutra/usda.sqlite3
- searchPaths << QStandardPaths::locate(QStandardPaths::AppDataLocation,
- "usda.sqlite3",
- QStandardPaths::LocateFile);
- searchPaths << QDir::homePath() + "/.local/share/nutra/usda.sqlite3";
- searchPaths << "/usr/local/share/nutra/usda.sqlite3";
- searchPaths << "/usr/share/nutra/usda.sqlite3";
- searchPaths << QDir::homePath() + "/.nutra/usda.sqlite3";
+ QStringList searchPaths;
+ QString envPath = qEnvironmentVariable("NUTRA_DB_PATH");
+ if (!envPath.isEmpty()) searchPaths << envPath;
- QString dbPath;
- for (const QString &path : searchPaths) {
- if (!path.isEmpty() && QFileInfo::exists(path)) {
- dbPath = path;
- break;
- }
- }
+ searchPaths << QStandardPaths::locate(QStandardPaths::AppDataLocation, "usda.sqlite3",
+ QStandardPaths::LocateFile);
+ searchPaths << QDir::homePath() + "/.local/share/nutra/usda.sqlite3";
+ searchPaths << "/usr/local/share/nutra/usda.sqlite3";
+ searchPaths << "/usr/share/nutra/usda.sqlite3";
+ searchPaths << QDir::homePath() + "/.nutra/usda.sqlite3";
- if (dbPath.isEmpty()) {
- qWarning() << "Database not found in standard locations.";
- QMessageBox::StandardButton resBtn = QMessageBox::question(
- nullptr, "Database Not Found",
- "The Nutrient database (usda.sqlite3) was not found.\nWould you like "
- "to browse for it manually?",
- QMessageBox::No | QMessageBox::Yes, QMessageBox::Yes);
-
- if (resBtn == QMessageBox::Yes) {
- dbPath = QFileDialog::getOpenFileName(
- nullptr, "Find usda.sqlite3", QDir::homePath() + "/.nutra",
- "SQLite Databases (*.sqlite3 *.db)");
+ QString dbPath;
+ for (const QString& path : searchPaths) {
+ if (!path.isEmpty() && QFileInfo::exists(path)) {
+ dbPath = path;
+ break;
+ }
}
if (dbPath.isEmpty()) {
- return 1; // User cancelled or still not found
+ qWarning() << "Database not found in standard locations.";
+ QMessageBox::StandardButton resBtn = QMessageBox::question(
+ nullptr, "Database Not Found",
+ "The Nutrient database (usda.sqlite3) was not found.\nWould you like "
+ "to browse for it manually?",
+ QMessageBox::No | QMessageBox::Yes, QMessageBox::Yes);
+
+ if (resBtn == QMessageBox::Yes) {
+ dbPath = QFileDialog::getOpenFileName(nullptr, "Find usda.sqlite3",
+ QDir::homePath() + "/.nutra",
+ "SQLite Databases (*.sqlite3 *.db)");
+ }
+
+ if (dbPath.isEmpty()) {
+ return 1; // User cancelled or still not found
+ }
}
- }
- if (!DatabaseManager::instance().connect(dbPath)) {
- QString errorMsg =
- QString("Failed to connect to database at:\n%1\n\nPlease ensure the "
- "database file exists or browse for a valid SQLite file.")
- .arg(dbPath);
- qCritical() << errorMsg;
- QMessageBox::critical(nullptr, "Database Error", errorMsg);
- // Let's try to let the user browse one more time before giving up
- return 1;
- }
- qDebug() << "Connected to database at:" << dbPath;
+ if (!DatabaseManager::instance().connect(dbPath)) {
+ QString errorMsg = QString(
+ "Failed to connect to database at:\n%1\n\nPlease ensure the "
+ "database file exists or browse for a valid SQLite file.")
+ .arg(dbPath);
+ qCritical() << errorMsg;
+ QMessageBox::critical(nullptr, "Database Error", errorMsg);
+ // Let's try to let the user browse one more time before giving up
+ return 1;
+ }
+ qDebug() << "Connected to database at:" << dbPath;
- MainWindow window;
- window.show();
+ MainWindow window;
+ window.show();
- return QApplication::exec();
+ return QApplication::exec();
}
#include "mainwindow.h"
-#include "db/databasemanager.h"
-#include "widgets/rdasettingswidget.h"
+
#include <QAction>
#include <QDebug>
#include <QFileDialog>
#include <QVBoxLayout>
#include <QWidget>
-MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
- for (int i = 0; i < MaxRecentFiles; ++i) {
- recentFileActions[i] = new QAction(this);
- recentFileActions[i]->setVisible(false);
- connect(recentFileActions[i], &QAction::triggered, this,
- &MainWindow::onRecentFileClick);
- }
- setupUi();
- updateRecentFileActions();
+#include "db/databasemanager.h"
+#include "widgets/rdasettingswidget.h"
+
+MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
+ for (int i = 0; i < MaxRecentFiles; ++i) {
+ recentFileActions[i] = new QAction(this);
+ recentFileActions[i]->setVisible(false);
+ connect(recentFileActions[i], &QAction::triggered, this, &MainWindow::onRecentFileClick);
+ }
+ setupUi();
+ updateRecentFileActions();
}
MainWindow::~MainWindow() = default;
void MainWindow::setupUi() {
- setWindowTitle("Nutrient Coach");
- setWindowIcon(QIcon(":/resources/nutrition_icon-no_bg.png"));
- resize(1000, 700);
-
- // File Menu
- auto *fileMenu = menuBar()->addMenu("&File");
- auto *openDbAction = fileMenu->addAction("&Open Database...");
- recentFilesMenu = fileMenu->addMenu("Recent Databases");
- fileMenu->addSeparator();
- auto *exitAction = fileMenu->addAction("E&xit");
- connect(openDbAction, &QAction::triggered, this, &MainWindow::onOpenDatabase);
- connect(exitAction, &QAction::triggered, this, &QWidget::close);
-
- for (int i = 0; i < MaxRecentFiles; ++i)
- recentFilesMenu->addAction(recentFileActions[i]);
-
- // Edit Menu
- QMenu *editMenu = menuBar()->addMenu("Edit");
- QAction *rdaAction = editMenu->addAction("RDA Settings");
- connect(rdaAction, &QAction::triggered, this, [this]() {
- RDASettingsWidget dlg(repository, this);
- dlg.exec();
- });
-
- QAction *settingsAction = editMenu->addAction("Settings");
- connect(settingsAction, &QAction::triggered, this, &MainWindow::onSettings);
-
- // Help Menu
- auto *helpMenu = menuBar()->addMenu("&Help");
- auto *aboutAction = helpMenu->addAction("&About");
- connect(aboutAction, &QAction::triggered, this, &MainWindow::onAbout);
-
- auto *centralWidget = new QWidget(this);
- setCentralWidget(centralWidget);
-
- auto *mainLayout = new QVBoxLayout(centralWidget);
-
- tabs = new QTabWidget(this);
- mainLayout->addWidget(tabs);
-
- // Search Tab
- searchWidget = new SearchWidget(this);
- tabs->addTab(searchWidget, "Search Foods");
-
- // Connect signal
- connect(searchWidget, &SearchWidget::foodSelected, this,
- [=](int foodId, const QString &foodName) {
- qDebug() << "Selected food:" << foodName << "(" << foodId << ")";
- detailsWidget->loadFood(foodId, foodName);
- tabs->setCurrentWidget(detailsWidget);
- });
-
- connect(searchWidget, &SearchWidget::addToMealRequested, this,
- [=](int foodId, const QString &foodName, double grams) {
- mealWidget->addFood(foodId, foodName, grams);
- tabs->setCurrentWidget(mealWidget);
- });
-
- // Analysis Tab
- detailsWidget = new DetailsWidget(this);
- tabs->addTab(detailsWidget, "Analyze");
-
- // Meal Tab
- mealWidget = new MealWidget(this);
- tabs->addTab(mealWidget, "Meal Tracker");
-
- // Connect Analysis -> Meal
- connect(detailsWidget, &DetailsWidget::addToMeal, this,
- [=](int foodId, const QString &foodName, double grams) {
- mealWidget->addFood(foodId, foodName, grams);
- // Optional: switch tab?
- // tabs->setCurrentWidget(mealWidget);
- });
+ setWindowTitle("Nutrient Coach");
+ setWindowIcon(QIcon(":/resources/nutrition_icon-no_bg.png"));
+ resize(1000, 700);
+
+ // File Menu
+ auto* fileMenu = menuBar()->addMenu("&File");
+ auto* openDbAction = fileMenu->addAction("&Open Database...");
+ recentFilesMenu = fileMenu->addMenu("Recent Databases");
+ fileMenu->addSeparator();
+ auto* exitAction = fileMenu->addAction("E&xit");
+ connect(openDbAction, &QAction::triggered, this, &MainWindow::onOpenDatabase);
+ connect(exitAction, &QAction::triggered, this, &QWidget::close);
+
+ for (int i = 0; i < MaxRecentFiles; ++i) recentFilesMenu->addAction(recentFileActions[i]);
+
+ // Edit Menu
+ QMenu* editMenu = menuBar()->addMenu("Edit");
+ QAction* rdaAction = editMenu->addAction("RDA Settings");
+ connect(rdaAction, &QAction::triggered, this, [this]() {
+ RDASettingsWidget dlg(repository, this);
+ dlg.exec();
+ });
+
+ QAction* settingsAction = editMenu->addAction("Settings");
+ connect(settingsAction, &QAction::triggered, this, &MainWindow::onSettings);
+
+ // Help Menu
+ auto* helpMenu = menuBar()->addMenu("&Help");
+ auto* aboutAction = helpMenu->addAction("&About");
+ connect(aboutAction, &QAction::triggered, this, &MainWindow::onAbout);
+
+ auto* centralWidget = new QWidget(this);
+ setCentralWidget(centralWidget);
+
+ auto* mainLayout = new QVBoxLayout(centralWidget);
+
+ tabs = new QTabWidget(this);
+ mainLayout->addWidget(tabs);
+
+ // Search Tab
+ searchWidget = new SearchWidget(this);
+ tabs->addTab(searchWidget, "Search Foods");
+
+ // Connect signal
+ connect(searchWidget, &SearchWidget::foodSelected, this,
+ [=](int foodId, const QString& foodName) {
+ qDebug() << "Selected food:" << foodName << "(" << foodId << ")";
+ detailsWidget->loadFood(foodId, foodName);
+ tabs->setCurrentWidget(detailsWidget);
+ });
+
+ connect(searchWidget, &SearchWidget::addToMealRequested, this,
+ [=](int foodId, const QString& foodName, double grams) {
+ mealWidget->addFood(foodId, foodName, grams);
+ tabs->setCurrentWidget(mealWidget);
+ });
+
+ // Analysis Tab
+ detailsWidget = new DetailsWidget(this);
+ tabs->addTab(detailsWidget, "Analyze");
+
+ // Meal Tab
+ mealWidget = new MealWidget(this);
+ tabs->addTab(mealWidget, "Meal Tracker");
+
+ // Connect Analysis -> Meal
+ connect(detailsWidget, &DetailsWidget::addToMeal, this,
+ [=](int foodId, const QString& foodName, double grams) {
+ mealWidget->addFood(foodId, foodName, grams);
+ // Optional: switch tab?
+ // tabs->setCurrentWidget(mealWidget);
+ });
}
void MainWindow::onOpenDatabase() {
- QString fileName = QFileDialog::getOpenFileName(
- this, "Open USDA Database", QDir::homePath() + "/.nutra",
- "SQLite Databases (*.sqlite3 *.db)");
-
- if (!fileName.isEmpty()) {
- if (DatabaseManager::instance().connect(fileName)) {
- qDebug() << "Switched to database:" << fileName;
- addToRecentFiles(fileName);
- // In a real app, we'd emit a signal or refresh all widgets
- // For now, let's just log and show success
- QMessageBox::information(this, "Database Opened",
- "Successfully connected to: " + fileName);
- } else {
- QMessageBox::critical(this, "Database Error",
- "Failed to connect to the database.");
+ QString fileName =
+ QFileDialog::getOpenFileName(this, "Open USDA Database", QDir::homePath() + "/.nutra",
+ "SQLite Databases (*.sqlite3 *.db)");
+
+ if (!fileName.isEmpty()) {
+ if (DatabaseManager::instance().connect(fileName)) {
+ qDebug() << "Switched to database:" << fileName;
+ addToRecentFiles(fileName);
+ // In a real app, we'd emit a signal or refresh all widgets
+ // For now, let's just log and show success
+ QMessageBox::information(this, "Database Opened",
+ "Successfully connected to: " + fileName);
+ } else {
+ QMessageBox::critical(this, "Database Error", "Failed to connect to the database.");
+ }
}
- }
}
void MainWindow::onRecentFileClick() {
- auto *action = qobject_cast<QAction *>(sender());
- if (action != nullptr) {
- QString fileName = action->data().toString();
- if (DatabaseManager::instance().connect(fileName)) {
- qDebug() << "Switched to database (recent):" << fileName;
- addToRecentFiles(fileName);
- QMessageBox::information(this, "Database Opened",
- "Successfully connected to: " + fileName);
- } else {
- QMessageBox::critical(this, "Database Error",
- "Failed to connect to: " + fileName);
+ auto* action = qobject_cast<QAction*>(sender());
+ if (action != nullptr) {
+ QString fileName = action->data().toString();
+ if (DatabaseManager::instance().connect(fileName)) {
+ qDebug() << "Switched to database (recent):" << fileName;
+ addToRecentFiles(fileName);
+ QMessageBox::information(this, "Database Opened",
+ "Successfully connected to: " + fileName);
+ } else {
+ QMessageBox::critical(this, "Database Error", "Failed to connect to: " + fileName);
+ }
}
- }
}
void MainWindow::updateRecentFileActions() {
- QSettings settings("NutraTech", "Nutra");
- QStringList files = settings.value("recentFiles").toStringList();
-
- int numRecentFiles = qMin(files.size(), MaxRecentFiles);
-
- for (int i = 0; i < numRecentFiles; ++i) {
- QString text =
- QString("&%1 %2").arg(i + 1).arg(QFileInfo(files[i]).fileName());
- recentFileActions[i]->setText(text);
- recentFileActions[i]->setData(files[i]);
- recentFileActions[i]->setVisible(true);
- }
- for (int i = numRecentFiles; i < MaxRecentFiles; ++i)
- recentFileActions[i]->setVisible(false);
-
- recentFilesMenu->setEnabled(numRecentFiles > 0);
+ QSettings settings("NutraTech", "Nutra");
+ QStringList files = settings.value("recentFiles").toStringList();
+
+ int numRecentFiles = qMin(files.size(), MaxRecentFiles);
+
+ for (int i = 0; i < numRecentFiles; ++i) {
+ QString text = QString("&%1 %2").arg(i + 1).arg(QFileInfo(files[i]).fileName());
+ recentFileActions[i]->setText(text);
+ recentFileActions[i]->setData(files[i]);
+ recentFileActions[i]->setVisible(true);
+ }
+ for (int i = numRecentFiles; i < MaxRecentFiles; ++i) recentFileActions[i]->setVisible(false);
+
+ recentFilesMenu->setEnabled(numRecentFiles > 0);
}
-void MainWindow::addToRecentFiles(const QString &path) {
- QSettings settings("NutraTech", "Nutra");
- QStringList files = settings.value("recentFiles").toStringList();
- files.removeAll(path);
- files.prepend(path);
- while (files.size() > MaxRecentFiles)
- files.removeLast();
+void MainWindow::addToRecentFiles(const QString& path) {
+ QSettings settings("NutraTech", "Nutra");
+ QStringList files = settings.value("recentFiles").toStringList();
+ files.removeAll(path);
+ files.prepend(path);
+ while (files.size() > MaxRecentFiles) files.removeLast();
- settings.setValue("recentFiles", files);
- updateRecentFileActions();
+ settings.setValue("recentFiles", files);
+ updateRecentFileActions();
}
void MainWindow::onSettings() {
- QMessageBox::information(this, "Settings", "Settings dialog coming soon!");
+ QMessageBox::information(this, "Settings", "Settings dialog coming soon!");
}
void MainWindow::onAbout() {
- QMessageBox::about(
- this, "About Nutrient Coach",
- QString("<h3>Nutrient Coach %1</h3>"
- "<p>A C++/Qt application for tracking nutrition.</p>"
- "<p>Homepage: <a "
- "href=\"https://github.com/nutratech/gui\">https://github.com/"
- "nutratech/gui</a></p>")
- .arg(NUTRA_VERSION_STRING));
+ QMessageBox::about(this, "About Nutrient Coach",
+ QString("<h3>Nutrient Coach %1</h3>"
+ "<p>A C++/Qt application for tracking nutrition.</p>"
+ "<p>Homepage: <a "
+ "href=\"https://github.com/nutratech/gui\">https://github.com/"
+ "nutratech/gui</a></p>")
+ .arg(NUTRA_VERSION_STRING));
}
#include "utils/string_utils.h"
+
#include <QRegularExpression>
#include <QStringList>
#include <QtGlobal>
-#include <algorithm> // Required for std::max
+#include <algorithm> // Required for std::max
#include <cmath>
#ifndef QT_VERSION_CHECK
-#define QT_VERSION_CHECK(major, minor, patch) \
- ((major << 16) | (minor << 8) | (patch))
+#define QT_VERSION_CHECK(major, minor, patch) ((major << 16) | (minor << 8) | (patch))
#endif
namespace Utils {
-int levenshteinDistance(const QString &s1, const QString &s2) {
- const auto m = s1.length();
- const auto n = s2.length();
-
- std::vector<std::vector<int>> dp(m + 1, std::vector<int>(n + 1));
-
- for (int i = 0; i <= m; ++i) {
- dp[i][0] = i;
- }
- for (int j = 0; j <= n; ++j) {
- dp[0][j] = j;
- }
-
- for (int i = 1; i <= m; ++i) {
- for (int j = 1; j <= n; ++j) {
- if (s1[i - 1] == s2[j - 1]) {
- dp[i][j] = dp[i - 1][j - 1];
- } else {
- dp[i][j] = 1 + std::min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]});
- }
+int levenshteinDistance(const QString& s1, const QString& s2) {
+ const auto m = s1.length();
+ const auto n = s2.length();
+
+ std::vector<std::vector<int>> dp(m + 1, std::vector<int>(n + 1));
+
+ for (int i = 0; i <= m; ++i) {
+ dp[i][0] = i;
+ }
+ for (int j = 0; j <= n; ++j) {
+ dp[0][j] = j;
+ }
+
+ for (int i = 1; i <= m; ++i) {
+ for (int j = 1; j <= n; ++j) {
+ if (s1[i - 1] == s2[j - 1]) {
+ dp[i][j] = dp[i - 1][j - 1];
+ } else {
+ dp[i][j] = 1 + std::min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]});
+ }
+ }
}
- }
- return dp[m][n];
+ return dp[m][n];
}
-int calculateFuzzyScore(const QString &query, const QString &target) {
- if (query.isEmpty()) {
- return 0;
- }
- if (target.isEmpty()) {
- return 0;
- }
-
- QString q = query.toLower();
- QString t = target.toLower();
-
- // 1. Exact match bonus
- if (t == q) {
- return 100;
- }
-
- // 2. Contains match bonus (very strong signal)
- if (t.contains(q)) {
- return 90; // Base score for containing the string
- }
-
- // 3. Token-based matching (handling "grass fed" vs "beef, grass-fed")
- static const QRegularExpression regex("[\\s,-]+");
+int calculateFuzzyScore(const QString& query, const QString& target) {
+ if (query.isEmpty()) {
+ return 0;
+ }
+ if (target.isEmpty()) {
+ return 0;
+ }
+
+ QString q = query.toLower();
+ QString t = target.toLower();
+
+ // 1. Exact match bonus
+ if (t == q) {
+ return 100;
+ }
+
+ // 2. Contains match bonus (very strong signal)
+ if (t.contains(q)) {
+ return 90; // Base score for containing the string
+ }
+
+ // 3. Token-based matching (handling "grass fed" vs "beef, grass-fed")
+ static const QRegularExpression regex("[\\s,-]+");
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
- auto behavior = Qt::SkipEmptyParts;
+ auto behavior = Qt::SkipEmptyParts;
#else
- auto behavior = QString::SkipEmptyParts;
+ auto behavior = QString::SkipEmptyParts;
#endif
- QStringList queryTokens = q.split(regex, behavior);
- QStringList targetTokens = t.split(regex, behavior);
-
- int totalScore = 0;
- int matchedTokens = 0;
-
- for (const QString &qToken : queryTokens) {
- int maxTokenScore = 0;
- for (const QString &tToken : targetTokens) {
- int dist = levenshteinDistance(qToken, tToken);
- int maxLen = static_cast<int>(std::max(qToken.length(), tToken.length()));
- if (maxLen == 0)
- continue;
-
- int score = 0;
- if (tToken.startsWith(qToken)) {
- score = 95; // Prefix match is very good
- } else {
- double ratio = 1.0 - (static_cast<double>(dist) / maxLen);
- score = static_cast<int>(ratio * 100);
- }
-
- maxTokenScore = std::max(maxTokenScore, score);
+ QStringList queryTokens = q.split(regex, behavior);
+ QStringList targetTokens = t.split(regex, behavior);
+
+ int totalScore = 0;
+ int matchedTokens = 0;
+
+ for (const QString& qToken : queryTokens) {
+ int maxTokenScore = 0;
+ for (const QString& tToken : targetTokens) {
+ int dist = levenshteinDistance(qToken, tToken);
+ int maxLen = static_cast<int>(std::max(qToken.length(), tToken.length()));
+ if (maxLen == 0) continue;
+
+ int score = 0;
+ if (tToken.startsWith(qToken)) {
+ score = 95; // Prefix match is very good
+ } else {
+ double ratio = 1.0 - (static_cast<double>(dist) / maxLen);
+ score = static_cast<int>(ratio * 100);
+ }
+
+ maxTokenScore = std::max(maxTokenScore, score);
+ }
+ totalScore += maxTokenScore;
+ if (maxTokenScore > 60) matchedTokens++;
}
- totalScore += maxTokenScore;
- if (maxTokenScore > 60)
- matchedTokens++;
- }
- if (queryTokens.isEmpty()) {
- return 0;
- }
+ if (queryTokens.isEmpty()) {
+ return 0;
+ }
- int averageScore = static_cast<int>(totalScore / queryTokens.size());
+ int averageScore = static_cast<int>(totalScore / queryTokens.size());
- // Penalize if not all tokens matched somewhat well
- if (matchedTokens < queryTokens.size()) {
- averageScore -= 20;
- }
+ // Penalize if not all tokens matched somewhat well
+ if (matchedTokens < queryTokens.size()) {
+ averageScore -= 20;
+ }
- return std::max(0, averageScore);
+ return std::max(0, averageScore);
}
-} // namespace Utils
+} // namespace Utils
#include "widgets/detailswidget.h"
+
#include <QDebug>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QVBoxLayout>
-DetailsWidget::DetailsWidget(QWidget *parent)
- : QWidget(parent), currentFoodId(-1) {
- auto *layout = new QVBoxLayout(this);
-
- // Header
- auto *headerLayout = new QHBoxLayout();
- nameLabel = new QLabel("No food selected", this);
- QFont font = nameLabel->font();
- font.setPointSize(14);
- font.setBold(true);
- nameLabel->setFont(font);
-
- addButton = new QPushButton("Add to Meal", this);
- addButton->setEnabled(false);
- connect(addButton, &QPushButton::clicked, this, &DetailsWidget::onAddClicked);
-
- headerLayout->addWidget(nameLabel);
- headerLayout->addStretch();
- headerLayout->addWidget(addButton);
- layout->addLayout(headerLayout);
-
- // Nutrients Table
- nutrientsTable = new QTableWidget(this);
- nutrientsTable->setColumnCount(3);
- nutrientsTable->setHorizontalHeaderLabels({"Nutrient", "Amount", "Unit"});
- nutrientsTable->horizontalHeader()->setSectionResizeMode(
- 0, QHeaderView::Stretch);
- nutrientsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
- layout->addWidget(nutrientsTable);
+DetailsWidget::DetailsWidget(QWidget* parent) : QWidget(parent), currentFoodId(-1) {
+ auto* layout = new QVBoxLayout(this);
+
+ // Header
+ auto* headerLayout = new QHBoxLayout();
+ nameLabel = new QLabel("No food selected", this);
+ QFont font = nameLabel->font();
+ font.setPointSize(14);
+ font.setBold(true);
+ nameLabel->setFont(font);
+
+ addButton = new QPushButton("Add to Meal", this);
+ addButton->setEnabled(false);
+ connect(addButton, &QPushButton::clicked, this, &DetailsWidget::onAddClicked);
+
+ headerLayout->addWidget(nameLabel);
+ headerLayout->addStretch();
+ headerLayout->addWidget(addButton);
+ layout->addLayout(headerLayout);
+
+ // Nutrients Table
+ nutrientsTable = new QTableWidget(this);
+ nutrientsTable->setColumnCount(3);
+ nutrientsTable->setHorizontalHeaderLabels({"Nutrient", "Amount", "Unit"});
+ nutrientsTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+ nutrientsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ layout->addWidget(nutrientsTable);
}
-void DetailsWidget::loadFood(int foodId, const QString &foodName) {
- currentFoodId = foodId;
- currentFoodName = foodName;
- nameLabel->setText(foodName + QString(" (ID: %1)").arg(foodId));
- addButton->setEnabled(true);
+void DetailsWidget::loadFood(int foodId, const QString& foodName) {
+ currentFoodId = foodId;
+ currentFoodName = foodName;
+ nameLabel->setText(foodName + QString(" (ID: %1)").arg(foodId));
+ addButton->setEnabled(true);
- nutrientsTable->setRowCount(0);
+ nutrientsTable->setRowCount(0);
- std::vector<Nutrient> nutrients = repository.getFoodNutrients(foodId);
+ std::vector<Nutrient> nutrients = repository.getFoodNutrients(foodId);
- nutrientsTable->setRowCount(static_cast<int>(nutrients.size()));
- for (int i = 0; i < static_cast<int>(nutrients.size()); ++i) {
- const auto &nut = nutrients[i];
- nutrientsTable->setItem(i, 0, new QTableWidgetItem(nut.description));
- nutrientsTable->setItem(i, 1,
- new QTableWidgetItem(QString::number(nut.amount)));
- nutrientsTable->setItem(i, 2, new QTableWidgetItem(nut.unit));
- }
+ nutrientsTable->setRowCount(static_cast<int>(nutrients.size()));
+ for (int i = 0; i < static_cast<int>(nutrients.size()); ++i) {
+ const auto& nut = nutrients[i];
+ nutrientsTable->setItem(i, 0, new QTableWidgetItem(nut.description));
+ nutrientsTable->setItem(i, 1, new QTableWidgetItem(QString::number(nut.amount)));
+ nutrientsTable->setItem(i, 2, new QTableWidgetItem(nut.unit));
+ }
}
void DetailsWidget::onAddClicked() {
- if (currentFoodId != -1) {
- // Default 100g
- emit addToMeal(currentFoodId, currentFoodName, 100.0);
- }
+ if (currentFoodId != -1) {
+ // Default 100g
+ emit addToMeal(currentFoodId, currentFoodName, 100.0);
+ }
}
#include "widgets/mealwidget.h"
+
#include <QDebug>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QVBoxLayout>
-MealWidget::MealWidget(QWidget *parent) : QWidget(parent) {
- auto *layout = new QVBoxLayout(this);
-
- // Items List
- layout->addWidget(new QLabel("Meal Composition", this));
- itemsTable = new QTableWidget(this);
- itemsTable->setColumnCount(3);
- itemsTable->setHorizontalHeaderLabels({"Food", "Grams", "Calories"});
- itemsTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
- layout->addWidget(itemsTable);
-
- // Controls
- clearButton = new QPushButton("Clear Meal", this);
- connect(clearButton, &QPushButton::clicked, this, &MealWidget::clearMeal);
- layout->addWidget(clearButton);
-
- // Totals
- layout->addWidget(new QLabel("Total Nutrition", this));
- totalsTable = new QTableWidget(this);
- totalsTable->setColumnCount(3);
- totalsTable->setHorizontalHeaderLabels({"Nutrient", "Total", "Unit"});
- totalsTable->horizontalHeader()->setSectionResizeMode(0,
- QHeaderView::Stretch);
- layout->addWidget(totalsTable);
+MealWidget::MealWidget(QWidget* parent) : QWidget(parent) {
+ auto* layout = new QVBoxLayout(this);
+
+ // Items List
+ layout->addWidget(new QLabel("Meal Composition", this));
+ itemsTable = new QTableWidget(this);
+ itemsTable->setColumnCount(3);
+ itemsTable->setHorizontalHeaderLabels({"Food", "Grams", "Calories"});
+ itemsTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+ layout->addWidget(itemsTable);
+
+ // Controls
+ clearButton = new QPushButton("Clear Meal", this);
+ connect(clearButton, &QPushButton::clicked, this, &MealWidget::clearMeal);
+ layout->addWidget(clearButton);
+
+ // Totals
+ layout->addWidget(new QLabel("Total Nutrition", this));
+ totalsTable = new QTableWidget(this);
+ totalsTable->setColumnCount(3);
+ totalsTable->setHorizontalHeaderLabels({"Nutrient", "Total", "Unit"});
+ totalsTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+ layout->addWidget(totalsTable);
+
+ refresh(); // Load initial state
+}
+
+void MealWidget::addFood(int foodId, const QString& foodName, double grams) {
+ // Default to meal_id 1 (e.g. Breakfast/General) for now
+ // TODO: Add UI to select meal
+ m_mealRepo.addFoodLog(foodId, grams, 1);
+ refresh();
}
-void MealWidget::addFood(int foodId, const QString &foodName, double grams) {
- std::vector<Nutrient> baseNutrients = repository.getFoodNutrients(foodId);
-
- MealItem item;
- item.foodId = foodId;
- item.name = foodName;
- item.grams = grams;
- item.nutrients_100g = baseNutrients;
-
- mealItems.push_back(item);
-
- // Update Items Table
- int row = itemsTable->rowCount();
- itemsTable->insertRow(row);
- itemsTable->setItem(row, 0, new QTableWidgetItem(foodName));
- itemsTable->setItem(row, 1, new QTableWidgetItem(QString::number(grams)));
-
- // Calculate Calories (ID 208 usually, or find by name?)
- // repository returns IDs based on DB. 208 is KCAL in SR28.
- double kcal = 0;
- for (const auto &nut : baseNutrients) {
- if (nut.id == 208) {
- kcal = (nut.amount * grams) / 100.0;
- break;
+void MealWidget::refresh() {
+ mealItems.clear();
+ itemsTable->setRowCount(0);
+
+ auto logs = m_mealRepo.getDailyLogs(); // defaults to today
+
+ for (const auto& log : logs) {
+ std::vector<Nutrient> baseNutrients = repository.getFoodNutrients(log.foodId);
+
+ MealItem item;
+ item.foodId = log.foodId;
+ item.name = log.foodName;
+ item.grams = log.grams;
+ item.nutrients_100g = baseNutrients;
+ mealItems.push_back(item);
+
+ // Update Items Table
+ int row = itemsTable->rowCount();
+ itemsTable->insertRow(row);
+ itemsTable->setItem(row, 0, new QTableWidgetItem(item.name));
+ itemsTable->setItem(row, 1, new QTableWidgetItem(QString::number(item.grams)));
+
+ // Calculate Calories (ID 208)
+ double kcal = 0;
+ for (const auto& nut : baseNutrients) {
+ if (nut.id == 208) {
+ kcal = (nut.amount * item.grams) / 100.0;
+ break;
+ }
+ }
+ itemsTable->setItem(row, 2, new QTableWidgetItem(QString::number(kcal, 'f', 1)));
}
- }
- itemsTable->setItem(row, 2,
- new QTableWidgetItem(QString::number(kcal, 'f', 1)));
- updateTotals();
+ updateTotals();
}
void MealWidget::clearMeal() {
- mealItems.clear();
- itemsTable->setRowCount(0);
- updateTotals();
+ m_mealRepo.clearDailyLogs();
+ refresh();
}
void MealWidget::updateTotals() {
- std::map<int, double> totals; // id -> amount
- std::map<int, QString> units;
- std::map<int, QString> names;
-
- for (const auto &item : mealItems) {
- double scale = item.grams / 100.0;
- for (const auto &nut : item.nutrients_100g) {
- totals[nut.id] += nut.amount * scale;
- names.try_emplace(nut.id, nut.description);
- units.try_emplace(nut.id, nut.unit);
+ std::map<int, double> totals; // id -> amount
+ std::map<int, QString> units;
+ std::map<int, QString> names;
+
+ for (const auto& item : mealItems) {
+ double scale = item.grams / 100.0;
+ for (const auto& nut : item.nutrients_100g) {
+ totals[nut.id] += nut.amount * scale;
+ names.try_emplace(nut.id, nut.description);
+ units.try_emplace(nut.id, nut.unit);
+ }
+ }
+
+ totalsTable->setRowCount(static_cast<int>(totals.size()));
+ int row = 0;
+ for (const auto& pair : totals) {
+ int nid = pair.first;
+ double amount = pair.second;
+
+ totalsTable->setItem(row, 0, new QTableWidgetItem(names[nid]));
+ totalsTable->setItem(row, 1, new QTableWidgetItem(QString::number(amount, 'f', 2)));
+ totalsTable->setItem(row, 2, new QTableWidgetItem(units[nid]));
+ row++;
}
- }
-
- totalsTable->setRowCount(static_cast<int>(totals.size()));
- int row = 0;
- for (const auto &pair : totals) {
- int nid = pair.first;
- double amount = pair.second;
-
- totalsTable->setItem(row, 0, new QTableWidgetItem(names[nid]));
- totalsTable->setItem(row, 1,
- new QTableWidgetItem(QString::number(amount, 'f', 2)));
- totalsTable->setItem(row, 2, new QTableWidgetItem(units[nid]));
- row++;
- }
}
#include "widgets/rdasettingswidget.h"
-#include "db/databasemanager.h"
+
#include <QHeaderView>
#include <QLabel>
#include <QSqlQuery>
#include <QVBoxLayout>
-RDASettingsWidget::RDASettingsWidget(FoodRepository &repository,
- QWidget *parent)
+#include "db/databasemanager.h"
+
+RDASettingsWidget::RDASettingsWidget(FoodRepository& repository, QWidget* parent)
: QDialog(parent), m_repository(repository) {
- setWindowTitle("RDA Settings");
- resize(600, 400);
+ setWindowTitle("RDA Settings");
+ resize(600, 400);
- auto *layout = new QVBoxLayout(this);
- layout->addWidget(new QLabel("Customize your Recommended Daily Allowances "
- "(RDA). Changes are saved automatically."));
+ auto* layout = new QVBoxLayout(this);
+ layout->addWidget(
+ new QLabel("Customize your Recommended Daily Allowances "
+ "(RDA). Changes are saved automatically."));
- m_table = new QTableWidget(this);
- m_table->setColumnCount(4);
- m_table->setHorizontalHeaderLabels({"ID", "Nutrient", "RDA", "Unit"});
- m_table->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
- m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
+ m_table = new QTableWidget(this);
+ m_table->setColumnCount(4);
+ m_table->setHorizontalHeaderLabels({"ID", "Nutrient", "RDA", "Unit"});
+ m_table->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
+ m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
- loadData();
+ loadData();
- connect(m_table, &QTableWidget::cellChanged, this,
- &RDASettingsWidget::onCellChanged);
+ connect(m_table, &QTableWidget::cellChanged, this, &RDASettingsWidget::onCellChanged);
- layout->addWidget(m_table);
+ layout->addWidget(m_table);
}
void RDASettingsWidget::loadData() {
- m_loading = true;
- m_table->setRowCount(0);
+ m_loading = true;
+ m_table->setRowCount(0);
- QSqlDatabase db = DatabaseManager::instance().database();
- if (!db.isOpen())
- return;
+ QSqlDatabase db = DatabaseManager::instance().database();
+ if (!db.isOpen()) return;
- // Get metadata from USDA
- QSqlQuery query(
- "SELECT id, nutr_desc, unit FROM nutrients_overview ORDER BY nutr_desc",
- db);
- auto currentRdas = m_repository.getNutrientRdas();
+ // Get metadata from USDA
+ QSqlQuery query("SELECT id, nutr_desc, unit FROM nutrients_overview ORDER BY nutr_desc", db);
+ auto currentRdas = m_repository.getNutrientRdas();
- int row = 0;
- while (query.next()) {
- int id = query.value(0).toInt();
- QString name = query.value(1).toString();
- QString unit = query.value(2).toString();
- double rda = currentRdas.count(id) != 0U ? currentRdas[id] : 0.0;
+ int row = 0;
+ while (query.next()) {
+ int id = query.value(0).toInt();
+ QString name = query.value(1).toString();
+ QString unit = query.value(2).toString();
+ double rda = currentRdas.count(id) != 0U ? currentRdas[id] : 0.0;
- m_table->insertRow(row);
+ m_table->insertRow(row);
- auto *idItem = new QTableWidgetItem(QString::number(id));
- idItem->setFlags(idItem->flags() & ~Qt::ItemIsEditable);
- m_table->setItem(row, 0, idItem);
+ auto* idItem = new QTableWidgetItem(QString::number(id));
+ idItem->setFlags(idItem->flags() & ~Qt::ItemIsEditable);
+ m_table->setItem(row, 0, idItem);
- auto *nameItem = new QTableWidgetItem(name);
- nameItem->setFlags(nameItem->flags() & ~Qt::ItemIsEditable);
- m_table->setItem(row, 1, nameItem);
+ auto* nameItem = new QTableWidgetItem(name);
+ nameItem->setFlags(nameItem->flags() & ~Qt::ItemIsEditable);
+ m_table->setItem(row, 1, nameItem);
- auto *rdaItem = new QTableWidgetItem(QString::number(rda));
- m_table->setItem(row, 2, rdaItem);
+ auto* rdaItem = new QTableWidgetItem(QString::number(rda));
+ m_table->setItem(row, 2, rdaItem);
- auto *unitItem = new QTableWidgetItem(unit);
- unitItem->setFlags(unitItem->flags() & ~Qt::ItemIsEditable);
- m_table->setItem(row, 3, unitItem);
+ auto* unitItem = new QTableWidgetItem(unit);
+ unitItem->setFlags(unitItem->flags() & ~Qt::ItemIsEditable);
+ m_table->setItem(row, 3, unitItem);
- row++;
- }
+ row++;
+ }
- m_loading = false;
+ m_loading = false;
}
void RDASettingsWidget::onCellChanged(int row, int column) {
- if (m_loading || column != 2)
- return;
+ if (m_loading || column != 2) return;
- int id = m_table->item(row, 0)->text().toInt();
- double value = m_table->item(row, 2)->text().toDouble();
+ int id = m_table->item(row, 0)->text().toInt();
+ double value = m_table->item(row, 2)->text().toDouble();
- m_repository.updateRda(id, value);
+ m_repository.updateRda(id, value);
}
#include "widgets/searchwidget.h"
-#include "widgets/weightinputdialog.h"
+
#include <QAction>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QMessageBox>
#include <QVBoxLayout>
-SearchWidget::SearchWidget(QWidget *parent) : QWidget(parent) {
- auto *layout = new QVBoxLayout(this);
-
- // Search bar
- auto *searchLayout = new QHBoxLayout();
- searchInput = new QLineEdit(this);
- searchInput->setPlaceholderText("Search for food...");
-
- searchTimer = new QTimer(this);
- searchTimer->setSingleShot(true);
- searchTimer->setInterval(600); // 600ms debounce
-
- connect(searchInput, &QLineEdit::textChanged, this,
- [=]() { searchTimer->start(); });
- connect(searchTimer, &QTimer::timeout, this, &SearchWidget::performSearch);
- connect(searchInput, &QLineEdit::returnPressed, this,
- &SearchWidget::performSearch);
-
- searchButton = new QPushButton("Search", this);
- connect(searchButton, &QPushButton::clicked, this,
- &SearchWidget::performSearch);
-
- searchLayout->addWidget(searchInput);
- searchLayout->addWidget(searchButton);
- layout->addLayout(searchLayout);
-
- // Results table
- resultsTable = new QTableWidget(this);
- resultsTable->setColumnCount(7);
- resultsTable->setHorizontalHeaderLabels(
- {"ID", "Description", "Group", "Nutr", "Amino", "Flav", "Score"});
-
- resultsTable->horizontalHeader()->setSectionResizeMode(1,
- QHeaderView::Stretch);
- resultsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
- resultsTable->setSelectionMode(QAbstractItemView::SingleSelection);
- resultsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
- resultsTable->setContextMenuPolicy(Qt::CustomContextMenu);
-
- connect(resultsTable, &QTableWidget::cellDoubleClicked, this,
- &SearchWidget::onRowDoubleClicked);
- connect(resultsTable, &QTableWidget::customContextMenuRequested, this,
- &SearchWidget::onCustomContextMenu);
-
- layout->addWidget(resultsTable);
+#include "widgets/weightinputdialog.h"
+
+SearchWidget::SearchWidget(QWidget* parent) : QWidget(parent) {
+ auto* layout = new QVBoxLayout(this);
+
+ // Search bar
+ auto* searchLayout = new QHBoxLayout();
+ searchInput = new QLineEdit(this);
+ searchInput->setPlaceholderText("Search for food...");
+
+ searchTimer = new QTimer(this);
+ searchTimer->setSingleShot(true);
+ searchTimer->setInterval(600); // 600ms debounce
+
+ connect(searchInput, &QLineEdit::textChanged, this, [=]() { searchTimer->start(); });
+ connect(searchTimer, &QTimer::timeout, this, &SearchWidget::performSearch);
+ connect(searchInput, &QLineEdit::returnPressed, this, &SearchWidget::performSearch);
+
+ searchButton = new QPushButton("Search", this);
+ connect(searchButton, &QPushButton::clicked, this, &SearchWidget::performSearch);
+
+ searchLayout->addWidget(searchInput);
+ searchLayout->addWidget(searchButton);
+ layout->addLayout(searchLayout);
+
+ // Results table
+ resultsTable = new QTableWidget(this);
+ resultsTable->setColumnCount(7);
+ resultsTable->setHorizontalHeaderLabels(
+ {"ID", "Description", "Group", "Nutr", "Amino", "Flav", "Score"});
+
+ resultsTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
+ resultsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
+ resultsTable->setSelectionMode(QAbstractItemView::SingleSelection);
+ resultsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ resultsTable->setContextMenuPolicy(Qt::CustomContextMenu);
+
+ connect(resultsTable, &QTableWidget::cellDoubleClicked, this,
+ &SearchWidget::onRowDoubleClicked);
+ connect(resultsTable, &QTableWidget::customContextMenuRequested, this,
+ &SearchWidget::onCustomContextMenu);
+
+ layout->addWidget(resultsTable);
}
void SearchWidget::performSearch() {
- QString query = searchInput->text().trimmed();
- if (query.length() < 2)
- return;
-
- resultsTable->setRowCount(0);
-
- std::vector<FoodItem> results = repository.searchFoods(query);
-
- resultsTable->setRowCount(static_cast<int>(results.size()));
- for (int i = 0; i < static_cast<int>(results.size()); ++i) {
- const auto &item = results[i];
- resultsTable->setItem(i, 0, new QTableWidgetItem(QString::number(item.id)));
- resultsTable->setItem(i, 1, new QTableWidgetItem(item.description));
- resultsTable->setItem(i, 2, new QTableWidgetItem(item.foodGroupName));
- resultsTable->setItem(
- i, 3, new QTableWidgetItem(QString::number(item.nutrientCount)));
- resultsTable->setItem(
- i, 4, new QTableWidgetItem(QString::number(item.aminoCount)));
- resultsTable->setItem(
- i, 5, new QTableWidgetItem(QString::number(item.flavCount)));
- resultsTable->setItem(i, 6,
- new QTableWidgetItem(QString::number(item.score)));
- }
+ QString query = searchInput->text().trimmed();
+ if (query.length() < 2) return;
+
+ resultsTable->setRowCount(0);
+
+ std::vector<FoodItem> results = repository.searchFoods(query);
+
+ resultsTable->setRowCount(static_cast<int>(results.size()));
+ for (int i = 0; i < static_cast<int>(results.size()); ++i) {
+ const auto& item = results[i];
+ resultsTable->setItem(i, 0, new QTableWidgetItem(QString::number(item.id)));
+ resultsTable->setItem(i, 1, new QTableWidgetItem(item.description));
+ resultsTable->setItem(i, 2, new QTableWidgetItem(item.foodGroupName));
+ resultsTable->setItem(i, 3, new QTableWidgetItem(QString::number(item.nutrientCount)));
+ resultsTable->setItem(i, 4, new QTableWidgetItem(QString::number(item.aminoCount)));
+ resultsTable->setItem(i, 5, new QTableWidgetItem(QString::number(item.flavCount)));
+ resultsTable->setItem(i, 6, new QTableWidgetItem(QString::number(item.score)));
+ }
}
void SearchWidget::onRowDoubleClicked(int row, int column) {
- Q_UNUSED(column);
- QTableWidgetItem *idItem = resultsTable->item(row, 0);
- QTableWidgetItem *descItem = resultsTable->item(row, 1);
+ Q_UNUSED(column);
+ QTableWidgetItem* idItem = resultsTable->item(row, 0);
+ QTableWidgetItem* descItem = resultsTable->item(row, 1);
- if (idItem != nullptr && descItem != nullptr) {
- emit foodSelected(idItem->text().toInt(), descItem->text());
- }
+ if (idItem != nullptr && descItem != nullptr) {
+ emit foodSelected(idItem->text().toInt(), descItem->text());
+ }
}
-void SearchWidget::onCustomContextMenu(const QPoint &pos) {
- QTableWidgetItem *item = resultsTable->itemAt(pos);
- if (item == nullptr)
- return;
+void SearchWidget::onCustomContextMenu(const QPoint& pos) {
+ QTableWidgetItem* item = resultsTable->itemAt(pos);
+ if (item == nullptr) return;
- int row = item->row();
- QTableWidgetItem *idItem = resultsTable->item(row, 0);
- QTableWidgetItem *descItem = resultsTable->item(row, 1);
+ int row = item->row();
+ QTableWidgetItem* idItem = resultsTable->item(row, 0);
+ QTableWidgetItem* descItem = resultsTable->item(row, 1);
- if (idItem == nullptr || descItem == nullptr)
- return;
+ if (idItem == nullptr || descItem == nullptr) return;
- int foodId = idItem->text().toInt();
- QString foodName = descItem->text();
+ int foodId = idItem->text().toInt();
+ QString foodName = descItem->text();
- QMenu menu(this);
- QAction *analyzeAction = menu.addAction("Analyze");
- QAction *addToMealAction = menu.addAction("Add to Meal");
+ QMenu menu(this);
+ QAction* analyzeAction = menu.addAction("Analyze");
+ QAction* addToMealAction = menu.addAction("Add to Meal");
- QAction *selectedAction =
- menu.exec(resultsTable->viewport()->mapToGlobal(pos));
+ QAction* selectedAction = menu.exec(resultsTable->viewport()->mapToGlobal(pos));
- if (selectedAction == analyzeAction) {
- emit foodSelected(foodId, foodName);
- } else if (selectedAction == addToMealAction) {
- std::vector<ServingWeight> servings = repository.getFoodServings(foodId);
- WeightInputDialog dlg(foodName, servings, this);
- if (dlg.exec() == QDialog::Accepted) {
- emit addToMealRequested(foodId, foodName, dlg.getGrams());
+ if (selectedAction == analyzeAction) {
+ emit foodSelected(foodId, foodName);
+ } else if (selectedAction == addToMealAction) {
+ std::vector<ServingWeight> servings = repository.getFoodServings(foodId);
+ WeightInputDialog dlg(foodName, servings, this);
+ if (dlg.exec() == QDialog::Accepted) {
+ emit addToMealRequested(foodId, foodName, dlg.getGrams());
+ }
}
- }
}
#include "widgets/weightinputdialog.h"
+
#include <QHBoxLayout>
#include <QPushButton>
#include <QVBoxLayout>
-WeightInputDialog::WeightInputDialog(const QString &foodName,
- const std::vector<ServingWeight> &servings,
- QWidget *parent)
+WeightInputDialog::WeightInputDialog(const QString& foodName,
+ const std::vector<ServingWeight>& servings, QWidget* parent)
: QDialog(parent), m_servings(servings) {
- setWindowTitle("Add to Meal - " + foodName);
- auto *layout = new QVBoxLayout(this);
-
- layout->addWidget(
- new QLabel("How much " + foodName + " are you adding?", this));
-
- auto *inputLayout = new QHBoxLayout();
- amountSpinBox = new QDoubleSpinBox(this);
- amountSpinBox->setRange(0.1, 10000.0);
- amountSpinBox->setValue(1.0);
- amountSpinBox->setDecimals(2);
-
- unitComboBox = new QComboBox(this);
- unitComboBox->addItem("Grams (g)", 1.0);
- unitComboBox->addItem("Ounces (oz)", GRAMS_PER_OZ);
- unitComboBox->addItem("Pounds (lb)", GRAMS_PER_LB);
-
- for (const auto &sw : servings) {
- unitComboBox->addItem(sw.description, sw.grams);
- }
-
- // Default to Grams and set value to 100 if Grams is selected
- unitComboBox->setCurrentIndex(0);
- amountSpinBox->setValue(100.0);
-
- // Update value when unit changes? No, let's keep it simple.
- // Usually 100g is a good default, but 1 serving might be better if available.
- if (!servings.empty()) {
- unitComboBox->setCurrentIndex(3); // First serving
- amountSpinBox->setValue(1.0);
- }
-
- inputLayout->addWidget(amountSpinBox);
- inputLayout->addWidget(unitComboBox);
- layout->addLayout(inputLayout);
+ setWindowTitle("Add to Meal - " + foodName);
+ auto* layout = new QVBoxLayout(this);
- auto *buttonLayout = new QHBoxLayout();
- auto *okButton = new QPushButton("Add to Meal", this);
- auto *cancelButton = new QPushButton("Cancel", this);
+ layout->addWidget(new QLabel("How much " + foodName + " are you adding?", this));
- connect(okButton, &QPushButton::clicked, this, &QDialog::accept);
- connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
-
- buttonLayout->addStretch();
- buttonLayout->addWidget(cancelButton);
- buttonLayout->addWidget(okButton);
- layout->addLayout(buttonLayout);
+ auto* inputLayout = new QHBoxLayout();
+ amountSpinBox = new QDoubleSpinBox(this);
+ amountSpinBox->setRange(0.1, 10000.0);
+ amountSpinBox->setValue(1.0);
+ amountSpinBox->setDecimals(2);
+
+ unitComboBox = new QComboBox(this);
+ unitComboBox->addItem("Grams (g)", 1.0);
+ unitComboBox->addItem("Ounces (oz)", GRAMS_PER_OZ);
+ unitComboBox->addItem("Pounds (lb)", GRAMS_PER_LB);
+
+ for (const auto& sw : servings) {
+ unitComboBox->addItem(sw.description, sw.grams);
+ }
+
+ // Default to Grams and set value to 100 if Grams is selected
+ unitComboBox->setCurrentIndex(0);
+ amountSpinBox->setValue(100.0);
+
+ // Update value when unit changes? No, let's keep it simple.
+ // Usually 100g is a good default, but 1 serving might be better if available.
+ if (!servings.empty()) {
+ unitComboBox->setCurrentIndex(3); // First serving
+ amountSpinBox->setValue(1.0);
+ }
+
+ inputLayout->addWidget(amountSpinBox);
+ inputLayout->addWidget(unitComboBox);
+ layout->addLayout(inputLayout);
+
+ auto* buttonLayout = new QHBoxLayout();
+ auto* okButton = new QPushButton("Add to Meal", this);
+ auto* cancelButton = new QPushButton("Cancel", this);
+
+ connect(okButton, &QPushButton::clicked, this, &QDialog::accept);
+ connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
+
+ buttonLayout->addStretch();
+ buttonLayout->addWidget(cancelButton);
+ buttonLayout->addWidget(okButton);
+ layout->addLayout(buttonLayout);
}
double WeightInputDialog::getGrams() const {
- double amount = amountSpinBox->value();
- double multiplier = unitComboBox->currentData().toDouble();
- return amount * multiplier;
+ double amount = amountSpinBox->value();
+ double multiplier = unitComboBox->currentData().toDouble();
+ return amount * multiplier;
}
-#include "db/databasemanager.h"
-#include "db/foodrepository.h"
#include <QDir>
#include <QFileInfo>
#include <QtTest>
+#include "db/databasemanager.h"
+#include "db/foodrepository.h"
+
class TestFoodRepository : public QObject {
- Q_OBJECT
+ Q_OBJECT
private slots:
- void initTestCase() {
- // Setup DB connection
- // Allow override via environment variable for CI/testing
- QString envPath = qgetenv("NUTRA_DB_PATH");
- QString dbPath =
- envPath.isEmpty() ? QDir::homePath() + "/.nutra/usda.sqlite3" : envPath;
-
- if (!QFileInfo::exists(dbPath)) {
- QSKIP("Database file not found (NUTRA_DB_PATH or ~/.nutra/usda.sqlite3). "
- "Skipping DB tests.");
+ void initTestCase() {
+ // Setup DB connection
+ // Allow override via environment variable for CI/testing
+ QString envPath = qgetenv("NUTRA_DB_PATH");
+ QString dbPath = envPath.isEmpty() ? QDir::homePath() + "/.nutra/usda.sqlite3" : envPath;
+
+ if (!QFileInfo::exists(dbPath)) {
+ QSKIP(
+ "Database file not found (NUTRA_DB_PATH or ~/.nutra/usda.sqlite3). "
+ "Skipping DB tests.");
+ }
+
+ bool connected = DatabaseManager::instance().connect(dbPath);
+ QVERIFY2(connected, "Failed to connect to database");
+ }
+
+ void testSearchFoods() {
+ FoodRepository repo;
+ auto results = repo.searchFoods("apple");
+ QVERIFY2(!results.empty(), "Search should return results for 'apple'");
+ bool found = false;
+ for (const auto& item : results) {
+ if (item.description.contains("Apple", Qt::CaseInsensitive)) {
+ found = true;
+ break;
+ }
+ }
+ QVERIFY2(found, "Search results should contain 'Apple'");
}
- bool connected = DatabaseManager::instance().connect(dbPath);
- QVERIFY2(connected, "Failed to connect to database");
- }
-
- void testSearchFoods() {
- FoodRepository repo;
- auto results = repo.searchFoods("apple");
- QVERIFY2(!results.empty(), "Search should return results for 'apple'");
- bool found = false;
- for (const auto &item : results) {
- if (item.description.contains("Apple", Qt::CaseInsensitive)) {
- found = true;
- break;
- }
+ void testGetFoodNutrients() {
+ FoodRepository repo;
+ // Known ID for "Apples, raw, with skin" might be 9003 in SR28, but let's
+ // search first or pick a known one if we knew it. Let's just use the first
+ // result from search.
+ auto results = repo.searchFoods("apple");
+ if (results.empty()) QSKIP("No foods found to test nutrients");
+
+ int foodId = results[0].id;
+ auto nutrients = repo.getFoodNutrients(foodId);
+ QVERIFY2(!nutrients.empty(), "Nutrients should not be empty for a valid food");
}
- QVERIFY2(found, "Search results should contain 'Apple'");
- }
-
- void testGetFoodNutrients() {
- FoodRepository repo;
- // Known ID for "Apples, raw, with skin" might be 9003 in SR28, but let's
- // search first or pick a known one if we knew it. Let's just use the first
- // result from search.
- auto results = repo.searchFoods("apple");
- if (results.empty())
- QSKIP("No foods found to test nutrients");
-
- int foodId = results[0].id;
- auto nutrients = repo.getFoodNutrients(foodId);
- QVERIFY2(!nutrients.empty(),
- "Nutrients should not be empty for a valid food");
- }
};
QTEST_MAIN(TestFoodRepository)