From 1c460de4ade2af2ed5b4f478df76b40a2ed70f21 Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 21 Jan 2026 21:26:41 -0500 Subject: [PATCH] database & ui stuff --- include/db/databasemanager.h | 7 +- include/widgets/searchwidget.h | 10 ++- src/db/databasemanager.cpp | 128 +++++++++++++++++++-------------- src/widgets/searchwidget.cpp | 54 +++++++------- 4 files changed, 114 insertions(+), 85 deletions(-) diff --git a/include/db/databasemanager.h b/include/db/databasemanager.h index 3386a42..d3116a7 100644 --- a/include/db/databasemanager.h +++ b/include/db/databasemanager.h @@ -8,7 +8,10 @@ class DatabaseManager { public: static DatabaseManager& instance(); - static constexpr int CURRENT_SCHEMA_VERSION = 9; + static constexpr int USER_SCHEMA_VERSION = 9; + static constexpr int USDA_SCHEMA_VERSION = 1; // Schema version for USDA data import + static constexpr int APP_ID_USDA = 0x55534441; // 'USDA' (ASCII) + static constexpr int APP_ID_USER = 0x4E555452; // 'NUTR' (ASCII) bool connect(const QString& path); [[nodiscard]] bool isOpen() const; [[nodiscard]] QSqlDatabase database() const; @@ -31,6 +34,8 @@ private: ~DatabaseManager(); void initUserDatabase(); + void applySchema(QSqlQuery& query, const QString& schemaPath); + int getSchemaVersion(const QSqlDatabase& db); QSqlDatabase m_db; QSqlDatabase m_userDb; diff --git a/include/widgets/searchwidget.h b/include/widgets/searchwidget.h index 2efe769..96f121c 100644 --- a/include/widgets/searchwidget.h +++ b/include/widgets/searchwidget.h @@ -1,9 +1,11 @@ #ifndef SEARCHWIDGET_H #define SEARCHWIDGET_H +#include #include #include #include +#include #include #include #include @@ -25,19 +27,21 @@ private slots: void performSearch(); void onRowDoubleClicked(int row, int column); void onCustomContextMenu(const QPoint& pos); - void showHistory(); + void onCompleterActivated(const QString& text); private: void addToHistory(int foodId, const QString& foodName); void loadHistory(); + void updateCompleterModel(); QLineEdit* searchInput; - QPushButton* searchButton; - QPushButton* historyButton; QTableWidget* resultsTable; FoodRepository repository; QTimer* searchTimer; + QCompleter* historyCompleter; + QStringListModel* historyModel; + struct HistoryItem { int id; QString name; diff --git a/src/db/databasemanager.cpp b/src/db/databasemanager.cpp index e4d417f..7c2aec1 100644 --- a/src/db/databasemanager.cpp +++ b/src/db/databasemanager.cpp @@ -87,20 +87,33 @@ DatabaseManager::DatabaseInfo DatabaseManager::getDatabaseInfo(const QString& pa QSqlQuery query(db); // Get Version - if (query.exec("PRAGMA user_version") && query.next()) { - info.version = query.value(0).toInt(); + info.version = instance().getSchemaVersion(db); + + // Get App ID + int appId = 0; + if (query.exec("PRAGMA application_id") && query.next()) { + appId = query.value(0).toInt(); } // Determine Type - bool hasFoodDes = query.exec("SELECT 1 FROM food_des LIMIT 1"); - bool hasLogFood = query.exec("SELECT 1 FROM log_food LIMIT 1"); - - if (hasFoodDes) { + if (appId == APP_ID_USDA) { info.type = "USDA"; info.isValid = true; - } else if (hasLogFood) { + } else if (appId == APP_ID_USER) { info.type = "User"; info.isValid = true; + } else { + // Fallback: Check tables + bool hasFoodDes = query.exec("SELECT 1 FROM food_des LIMIT 1"); + bool hasLogFood = query.exec("SELECT 1 FROM log_food LIMIT 1"); + + if (hasFoodDes) { + info.type = "USDA"; + info.isValid = true; + } else if (hasLogFood) { + info.type = "User"; + info.isValid = true; + } } db.close(); @@ -124,64 +137,71 @@ void DatabaseManager::initUserDatabase() { QSqlQuery query(m_userDb); // Check version - int schemaVersionOnDisk = 0; - if (query.exec("PRAGMA user_version") && query.next()) { - schemaVersionOnDisk = query.value(0).toInt(); - } + int schemaVersionOnDisk = getSchemaVersion(m_userDb); qDebug() << "User database version:" << schemaVersionOnDisk; if (schemaVersionOnDisk == 0) { // Initialize from tables.sql - // In a real deployed app, this file should be in a resource (.qrc) or installed path - // For now, we look in the submodule path if running from source, or a known fallback QString schemaPath = QDir::currentPath() + "/lib/ntsqlite/sql/tables.sql"; if (!QFileInfo::exists(schemaPath)) { - // Fallback for installed location (adjust as needed for packaging) + // Fallback for installed location schemaPath = "/usr/share/nutra/sql/tables.sql"; } + applySchema(query, schemaPath); + } else { + // Migration logic would go here + } +} - QFile schemaFile(schemaPath); - if (schemaFile.open(QIODevice::ReadOnly)) { - QTextStream in(&schemaFile); - QString sql = in.readAll(); - - // Allow for simple splitting for now as tables.sql is simple - QStringList statements = sql.split(';', Qt::SkipEmptyParts); - for (const QString& stmt : statements) { - QString trimmed = stmt.trimmed(); - if (!trimmed.isEmpty() && !trimmed.startsWith("--")) { - if (!query.exec(trimmed)) { - qWarning() << "Schema init warning:" << query.lastError().text() - << "\nStmt:" << trimmed; - } - } - } - // Ensure version is set (tables.sql has it, but good to ensure) - query.exec(QString("PRAGMA user_version = %1").arg(CURRENT_SCHEMA_VERSION)); - qDebug() << "Upgraded user database version from" << schemaVersionOnDisk << "to" - << CURRENT_SCHEMA_VERSION << "."; - - // --- Seeding Data (moved from previous implementation) --- - - // 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(); - } +void DatabaseManager::applySchema(QSqlQuery& query, const QString& schemaPath) { + QFile schemaFile(schemaPath); + if (!schemaFile.open(QIODevice::ReadOnly)) { + qCritical() << "Could not find or open schema file:" << schemaPath; + return; + } + + QTextStream in(&schemaFile); + QString sql = in.readAll(); + + // Allow for simple splitting for now as tables.sql is simple + QStringList statements = sql.split(';', Qt::SkipEmptyParts); + for (const QString& stmt : statements) { + QString trimmed = stmt.trimmed(); + if (!trimmed.isEmpty() && !trimmed.startsWith("--")) { + if (!query.exec(trimmed)) { + qWarning() << "Schema init warning:" << query.lastError().text() + << "\nStmt:" << trimmed; } - } else { - qCritical() << "Could not find or open schema file:" << schemaPath; } - } else { - // Migration logic would go here - // if (currentVersion < 2) { ... } } + // Ensure version and ID are set + query.exec(QString("PRAGMA user_version = %1").arg(USER_SCHEMA_VERSION)); + query.exec(QString("PRAGMA application_id = %1").arg(APP_ID_USER)); + qDebug() << "Upgraded user database version to" << USER_SCHEMA_VERSION << "and set App ID."; + + // --- Seeding Data --- + + // 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(); + } + } +} + +int DatabaseManager::getSchemaVersion(const QSqlDatabase& db) { + if (!db.isOpen()) return 0; + QSqlQuery query(db); + if (query.exec("PRAGMA user_version") && query.next()) { + return query.value(0).toInt(); + } + return 0; } diff --git a/src/widgets/searchwidget.cpp b/src/widgets/searchwidget.cpp index 7c5d19e..432cfcf 100644 --- a/src/widgets/searchwidget.cpp +++ b/src/widgets/searchwidget.cpp @@ -11,33 +11,34 @@ #include #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..."); + searchInput->setPlaceholderText("Search for food (or type to see history)..."); searchTimer = new QTimer(this); searchTimer->setSingleShot(true); searchTimer->setInterval(600); // 600ms debounce + // History Completer + historyModel = new QStringListModel(this); + historyCompleter = new QCompleter(historyModel, this); + historyCompleter->setCaseSensitivity(Qt::CaseInsensitive); + historyCompleter->setCompletionMode(QCompleter::PopupCompletion); + searchInput->setCompleter(historyCompleter); + + connect(historyCompleter, QOverload::of(&QCompleter::activated), this, + &SearchWidget::onCompleterActivated); + 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); - searchButton = new QPushButton("Search", this); - connect(searchButton, &QPushButton::clicked, this, &SearchWidget::performSearch); - searchLayout->addWidget(searchButton); - - historyButton = new QPushButton("History", this); - connect(historyButton, &QPushButton::clicked, this, &SearchWidget::showHistory); - searchLayout->addWidget(historyButton); layout->addLayout(searchLayout); @@ -73,7 +74,7 @@ void SearchWidget::performSearch() { resultsTable->setRowCount(0); std::vector results = repository.searchFoods(query); - int elapsed = timer.elapsed(); + int elapsed = static_cast(timer.elapsed()); resultsTable->setRowCount(static_cast(results.size())); for (int i = 0; i < static_cast(results.size()); ++i) { @@ -160,7 +161,7 @@ void SearchWidget::onCustomContextMenu(const QPoint& pos) { QAction* selectedAction = menu.exec(resultsTable->viewport()->mapToGlobal(pos)); - if (selectedAction) { + if (selectedAction != nullptr) { addToHistory(foodId, foodName); } @@ -203,6 +204,8 @@ void SearchWidget::addToHistory(int foodId, const QString& foodName) { list.append(m); } settings.setValue("recentFoods", list); + + updateCompleterModel(); } void SearchWidget::loadHistory() { @@ -217,21 +220,18 @@ void SearchWidget::loadHistory() { item.timestamp = m["timestamp"].toDateTime(); recentHistory.append(item); } + updateCompleterModel(); } -void SearchWidget::showHistory() { - resultsTable->setRowCount(0); - resultsTable->setRowCount(recentHistory.size()); - - for (int i = 0; i < recentHistory.size(); ++i) { - const auto& item = recentHistory[i]; - resultsTable->setItem(i, 0, new QTableWidgetItem(QString::number(item.id))); - resultsTable->setItem(i, 1, new QTableWidgetItem(item.name)); - resultsTable->setItem(i, 2, new QTableWidgetItem("History")); - // Empty cols for nutrients etc since we don't store them in history - for (int c = 3; c < 7; ++c) { - resultsTable->setItem(i, c, new QTableWidgetItem("")); - } +void SearchWidget::updateCompleterModel() { + QStringList suggestions; + for (const auto& item : recentHistory) { + suggestions << item.name; } - emit searchStatus(QString("Showing %1 recent items").arg(recentHistory.size())); + historyModel->setStringList(suggestions); +} + +void SearchWidget::onCompleterActivated(const QString& text) { + searchInput->setText(text); + performSearch(); } -- 2.52.0