-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSearchModel.cpp
More file actions
285 lines (240 loc) · 9.84 KB
/
SearchModel.cpp
File metadata and controls
285 lines (240 loc) · 9.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
// SearchModel.cpp
// ============================================================================
// Implementation of the SearchModel class for book search and filtering
// ============================================================================
#include "SearchModel.h"
#include "DatabaseManager.h"
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>
// ============================================================================
// CONSTRUCTOR
// ============================================================================
// Initializes the search model and loads all books from the database
SearchModel::SearchModel(QObject *parent)
: QAbstractListModel(parent)
{
// Load all books from database into cache for fast searching
loadAllBooks();
}
// ============================================================================
// Qt MODEL INTERFACE IMPLEMENTATION
// ============================================================================
// These methods are required by QAbstractListModel for the model to work
// rowCount: Returns the number of books in the current search results
int SearchModel::rowCount(const QModelIndex &parent) const
{
// Return 0 if parent is valid (model is a flat list, no sub-items)
if (parent.isValid())
return 0;
// Return the count of results matching current search criteria
return m_results.count();
}
// data: Retrieve data for a specific book and role (property)
// index: Position of the book in the results list
// role: Which property to retrieve (title, author, status, etc.)
QVariant SearchModel::data(const QModelIndex &index, int role) const
{
// Validate the index is within bounds
if (!index.isValid() || index.row() < 0 || index.row() >= m_results.count())
return QVariant();
// Get the book at this index
const BookResult &book = m_results[index.row()];
// Return the appropriate property based on the role
switch (role) {
case IdRole:
return book.id;
case TitleRole:
return book.title;
case AuthorRole:
return book.author;
case StatusRole:
return book.status;
case ContactNameRole:
return book.contactName;
case ContactNumberRole:
return book.contactNumber;
default:
return QVariant(); // Unknown role
}
}
// roleNames: Map role enums to property names accessible from QML
// This allows QML to access properties like: model.title, model.author, etc.
QHash<int, QByteArray> SearchModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[IdRole] = "id"; // Book ID
roles[TitleRole] = "title"; // Book title
roles[AuthorRole] = "author"; // Author name
roles[StatusRole] = "status"; // Book status
roles[ContactNameRole] = "contactName"; // Contact person
roles[ContactNumberRole] = "contactNumber"; // Contact phone
return roles;
}
// ============================================================================
// PUBLIC SEARCH METHODS
// ============================================================================
// performSearch: Execute a search based on user query and search type
// This is the main entry point for searching books
void SearchModel::performSearch(const QString &query, const QString &searchType)
{
// Update the current search query for display purposes
m_currentSearch = query;
// Perform the appropriate type of search based on user selection
if (searchType == "title") {
// Search by title only
performTitleSearch(query);
}
else if (searchType == "author") {
// Search by author only
performAuthorSearch(query);
}
else if (searchType == "status") {
// Filter by status (exact match)
performStatusSearch(query);
}
else {
// Default: search both title and author
performFullSearch(query);
}
// Notify QML that results have changed
emit resultsChanged();
emit searchChanged();
// Log search results for debugging
qDebug() << "Search completed:" << query << "Results:" << m_results.count();
}
// clearSearch: Reset search and show all books
void SearchModel::clearSearch()
{
// Reset search state
m_currentSearch = "";
// Reset results to show all books
beginResetModel();
m_results = m_allBooks;
endResetModel();
// Notify QML of state change
emit resultsChanged();
emit searchChanged();
qDebug() << "Search cleared. Showing all" << m_results.count() << "books";
}
// ============================================================================
// PRIVATE SEARCH IMPLEMENTATION METHODS
// ============================================================================
// loadAllBooks: Load all books from database into memory
// This is called once during initialization for fast searching
void SearchModel::loadAllBooks()
{
// Clear any existing data
m_results.clear();
m_allBooks.clear();
// Query all books from the database ordered by ID (newest first)
QSqlQuery query("SELECT id, title, author, status, contact_name, contact_number FROM books ORDER BY id DESC");
// Iterate through each row returned by the query
while (query.next()) {
// Create a new book result object
BookResult book;
book.id = query.value(0).toInt(); // Get ID
book.title = query.value(1).toString(); // Get title
book.author = query.value(2).toString(); // Get author
book.status = query.value(3).toString(); // Get status
book.contactName = query.value(4).toString(); // Get contact name
book.contactNumber = query.value(5).toString(); // Get contact number
// Add to both cache lists
m_allBooks.append(book);
m_results.append(book);
}
// Check for database errors
if (query.lastError().isValid()) {
qCritical() << "Failed to load books for search:" << query.lastError().text();
}
qDebug() << "Loaded" << m_allBooks.count() << "books for searching";
}
// performTitleSearch: Search books by title (case-insensitive partial match)
// Example: "the fix" finds "The Fix" and "The Five"
// Also searches for numbers and special characters like "8" in "From a Buick 8"
void SearchModel::performTitleSearch(const QString &query)
{
// Clear previous results
m_results.clear();
// Convert search query to lowercase for case-insensitive comparison
QString lowerQuery = query.toLower().trimmed();
// Return early if query is empty
if (lowerQuery.isEmpty()) {
qDebug() << "Title search: empty query";
return;
}
// Iterate through all books in cache
for (const BookResult &book : m_allBooks) {
QString lowerTitle = book.title.toLower();
// Primary match: exact substring contains (catches "Buick" and "8")
if (lowerTitle.contains(lowerQuery)) {
m_results.append(book); // Add matching book to results
continue;
}
}
qDebug() << "Title search for" << query << "found" << m_results.count() << "matches";
}
// performAuthorSearch: Search books by author (case-insensitive partial match)
// Example: "smith" finds "John Smith" and "Adam Smith"
void SearchModel::performAuthorSearch(const QString &query)
{
// Clear previous results
m_results.clear();
// Convert search query to lowercase for case-insensitive comparison
QString lowerQuery = query.toLower().trimmed();
// Return early if query is empty
if (lowerQuery.isEmpty()) {
qDebug() << "Author search: empty query";
return;
}
// Iterate through all books in cache
for (const BookResult &book : m_allBooks) {
// Check if author name contains the search query (case-insensitive)
if (book.author.toLower().contains(lowerQuery)) {
m_results.append(book); // Add matching book to results
}
}
qDebug() << "Author search for" << query << "found" << m_results.count() << "matches";
}
// performStatusSearch: Filter books by exact status value
// Valid statuses: "SHELF", "LOANED", "BORROWED"
void SearchModel::performStatusSearch(const QString &status)
{
// Clear previous results
m_results.clear();
// Iterate through all books in cache
for (const BookResult &book : m_allBooks) {
// Check if book status matches exactly (case-sensitive)
if (book.status == status) {
m_results.append(book); // Add matching book to results
}
}
qDebug() << "Status search for" << status << "found" << m_results.count() << "matches";
}
// performFullSearch: Search both title and author fields
// Example: "tolkien" finds books with "tolkien" in title OR author
// This is more comprehensive and catches partial matches in either field
void SearchModel::performFullSearch(const QString &query)
{
// Clear previous results
m_results.clear();
// Convert search query to lowercase for case-insensitive comparison
QString lowerQuery = query.toLower().trimmed();
// Return early if query is empty
if (lowerQuery.isEmpty()) {
qDebug() << "Full search: empty query";
return;
}
// Iterate through all books in cache
for (const BookResult &book : m_allBooks) {
QString lowerTitle = book.title.toLower();
QString lowerAuthor = book.author.toLower();
// Check if EITHER title OR author contains the search query (case-insensitive)
// This is sensitive enough to catch "Buick" in "From a Buick 8"
// and also catches numbers like "8"
if (lowerTitle.contains(lowerQuery) || lowerAuthor.contains(lowerQuery)) {
m_results.append(book); // Add matching book to results
}
}
qDebug() << "Full search for" << query << "found" << m_results.count() << "matches";
}