# Dynamic Test Discovery
file(GLOB TEST_SOURCES "tests/test_*.cpp")
+add_custom_target(build_tests)
+
foreach(TEST_SOURCE ${TEST_SOURCES})
get_filename_component(TEST_NAME ${TEST_SOURCE} NAME_WE)
target_link_libraries(${TEST_NAME} PRIVATE nutra_lib Qt${QT_VERSION_MAJOR}::Test)
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
+ add_dependencies(build_tests ${TEST_NAME})
endforeach()
.PHONY: test
test: release
- $(CMAKE) --build $(BUILD_DIR) --target test_nutra --config Release
+ $(CMAKE) --build $(BUILD_DIR) --target build_tests --config Release
cd $(BUILD_DIR) && $(CTEST) --output-on-failure -C Release
.PHONY: run
lint: config
@echo "Linting..."
@# Build test target first to generate MOC files for tests
- @$(CMAKE) --build $(BUILD_DIR) --target test_nutra --config Debug 2>/dev/null || true
+ @$(CMAKE) --build $(BUILD_DIR) --target build_tests --config Debug 2>/dev/null || true
@echo "Running cppcheck..."
cppcheck --enable=warning,performance,portability \
--language=c++ --std=c++17 \
public:
explicit SearchWidget(QWidget* parent = nullptr);
+ bool eventFilter(QObject* obj, QEvent* event) override;
void reloadSettings();
void performSearch();
void onRowDoubleClicked(int row, int column);
void onCustomContextMenu(const QPoint& pos);
+ void onHistoryContextMenu(const QPoint& pos);
void onCompleterActivated(const QString& text);
private:
void addToHistory(int foodId, const QString& foodName);
+ void removeFromHistory(int index);
void loadHistory();
void updateCompleterModel();
#include "widgets/searchwidget.h"
+#include <QAbstractItemView>
#include <QAction>
#include <QDateTime>
#include <QElapsedTimer>
+#include <QEvent>
#include <QHBoxLayout>
#include <QHeaderView>
+#include <QKeyEvent>
#include <QMenu>
#include <QMessageBox>
#include <QPushButton>
historyCompleter->setCompletionMode(QCompleter::PopupCompletion);
searchInput->setCompleter(historyCompleter);
+ QAbstractItemView* popup = historyCompleter->popup();
+ popup->setContextMenuPolicy(Qt::CustomContextMenu);
+ popup->installEventFilter(this);
+
+ connect(popup, &QAbstractItemView::customContextMenuRequested, this,
+ &SearchWidget::onHistoryContextMenu);
+
connect(historyCompleter, QOverload<const QString&>::of(&QCompleter::activated), this,
&SearchWidget::onCompleterActivated);
debounce = std::max(debounce, 250);
searchTimer->setInterval(debounce);
}
+
+bool SearchWidget::eventFilter(QObject* obj, QEvent* event) {
+ if (obj == historyCompleter->popup() && event->type() == QEvent::KeyPress) {
+ auto* keyEvent = static_cast<QKeyEvent*>(event);
+ if (keyEvent->key() == Qt::Key_Delete) {
+ QModelIndex index = historyCompleter->popup()->currentIndex();
+ if (index.isValid()) {
+ removeFromHistory(index.row());
+ return true;
+ }
+ }
+ }
+ return QWidget::eventFilter(obj, event);
+}
+
+void SearchWidget::onHistoryContextMenu(const QPoint& pos) {
+ QAbstractItemView* popup = historyCompleter->popup();
+ QModelIndex index = popup->indexAt(pos);
+ if (!index.isValid()) return;
+
+ QMenu menu(this);
+ QAction* deleteAction = menu.addAction("Remove from history");
+ QAction* selectedAction = menu.exec(popup->viewport()->mapToGlobal(pos));
+
+ if (selectedAction == deleteAction) {
+ removeFromHistory(index.row());
+ }
+}
+
+void SearchWidget::removeFromHistory(int index) {
+ if (index < 0 || index >= recentHistory.size()) return;
+
+ recentHistory.removeAt(index);
+
+ // Save to settings
+ QSettings settings("NutraTech", "Nutra");
+ QList<QVariant> list;
+ for (const auto& h : recentHistory) {
+ QVariantMap m;
+ m["id"] = h.id;
+ m["name"] = h.name;
+ m["timestamp"] = h.timestamp;
+ list.append(m);
+ }
+ settings.setValue("recentFoods", list);
+
+ updateCompleterModel();
+}