]> Nutra Git (v1) - nutratech/gui.git/commitdiff
database & ui stuff
authorShane Jaroch <chown_tee@proton.me>
Thu, 22 Jan 2026 02:26:41 +0000 (21:26 -0500)
committerShane Jaroch <chown_tee@proton.me>
Thu, 22 Jan 2026 02:26:41 +0000 (21:26 -0500)
include/db/databasemanager.h
include/widgets/searchwidget.h
src/db/databasemanager.cpp
src/widgets/searchwidget.cpp

index 3386a4292eb090ec479571b57932bdbb77ee7908..d3116a78f8bbe4959f22e201c00a79db9a751968 100644 (file)
@@ -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;
index 2efe7690e4b09b6298fed94aadddf11d48425898..96f121cf424f3f5e47bcbc943a8e46e822e26924 100644 (file)
@@ -1,9 +1,11 @@
 #ifndef SEARCHWIDGET_H
 #define SEARCHWIDGET_H
 
+#include <QCompleter>
 #include <QDateTime>
 #include <QLineEdit>
 #include <QPushButton>
+#include <QStringListModel>
 #include <QTableWidget>
 #include <QTimer>
 #include <QWidget>
@@ -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;
index e4d417f2389e423efcedeb337b09648b366cc6f6..7c2aec127346b1179291895e15f7a7b34dc64be0 100644 (file)
@@ -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;
 }
index 7c5d19e77f4731dcb67c129e2d683a393bb09e8f..432cfcfabd6b2238741a8f9b83ca952a2de5c93f 100644 (file)
 #include <QVBoxLayout>
 
 #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<const QString&>::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<FoodItem> results = repository.searchFoods(query);
-    int elapsed = timer.elapsed();
+    int elapsed = static_cast<int>(timer.elapsed());
 
     resultsTable->setRowCount(static_cast<int>(results.size()));
     for (int i = 0; i < static_cast<int>(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();
 }