Skip to content

Commit 3c71ae4

Browse files
committed
Implement search functionality for emv-viewer
1 parent 4e1a766 commit 3c71ae4

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed

viewer/emv-viewer-mainwindow.cpp

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include <QtWidgets/QToolButton>
3535
#include <QtWidgets/QScrollBar>
3636
#include <QtWidgets/QStatusBar>
37+
#include <QtWidgets/QTreeWidgetItemIterator>
3738
#include <QtGui/QIcon>
3839
#include <QtGui/QClipboard>
3940
#include <QtGui/QDesktopServices>
@@ -54,6 +55,13 @@ EmvViewerMainWindow::EmvViewerMainWindow(
5455
updateTimer->setObjectName(QStringLiteral("updateTimer"));
5556
updateTimer->setSingleShot(true);
5657

58+
// Prepare timer used for search input updates. Do this before setupUi()
59+
// calls QMetaObject::connectSlotsByName() to ensure that auto-connect
60+
// works for on_searchTimer_timeout().
61+
searchTimer = new QTimer(this);
62+
searchTimer->setObjectName(QStringLiteral("searchTimer"));
63+
searchTimer->setSingleShot(true);
64+
5765
// Setup UI widgets
5866
setupUi(this);
5967
setWindowIcon(QIcon(":icons/openemv_emv_utils_512x512.png"));
@@ -83,6 +91,9 @@ EmvViewerMainWindow::EmvViewerMainWindow(
8391
searchLineEdit->setPlaceholderText(tr("Find in fields..."));
8492
searchLineEdit->setClearButtonEnabled(true);
8593
treeViewToolBar->addWidget(searchLineEdit);
94+
connect(searchLineEdit, &QLineEdit::textChanged, this, [this]() {
95+
searchTimer->start(300);
96+
});
8697

8798
searchPreviousButton = new QToolButton(this);
8899
searchPreviousButton->setObjectName(QStringLiteral("searchPreviousButton"));
@@ -96,6 +107,7 @@ EmvViewerMainWindow::EmvViewerMainWindow(
96107
searchPreviousButton->setText(QStringLiteral("\u2191"));
97108
}
98109
treeViewToolBar->addWidget(searchPreviousButton);
110+
connect(searchPreviousButton, &QToolButton::clicked, this, &EmvViewerMainWindow::searchPrevious);
99111

100112
searchNextButton = new QToolButton(this);
101113
searchNextButton->setObjectName(QStringLiteral("searchNextButton"));
@@ -109,6 +121,7 @@ EmvViewerMainWindow::EmvViewerMainWindow(
109121
searchNextButton->setText(QStringLiteral("\u2193"));
110122
}
111123
treeViewToolBar->addWidget(searchNextButton);
124+
connect(searchNextButton, &QToolButton::clicked, this, &EmvViewerMainWindow::searchNext);
112125

113126
// Load previous UI values
114127
loadSettings();
@@ -237,13 +250,148 @@ void EmvViewerMainWindow::updateTreeView()
237250
return;
238251
}
239252
treeView->populateItems(str);
253+
254+
if (!searchLineEdit->text().isEmpty()) {
255+
// Restart search after tree update
256+
startSearch();
257+
}
258+
}
259+
260+
void EmvViewerMainWindow::startSearch()
261+
{
262+
// Reset search state
263+
searchMatches.clear();
264+
currentSearchIndex = -1;
265+
266+
QString searchText = searchLineEdit->text();
267+
if (searchText.isEmpty()) {
268+
updateSearchStatus();
269+
return;
270+
}
271+
272+
// Find all search matches and remember the first index after the
273+
// currently selected item
274+
QTreeWidgetItemIterator itr(treeView, QTreeWidgetItemIterator::NotHidden);
275+
bool rememberNextIndex = false;
276+
int firstSearchIndex = 0;
277+
while (*itr) {
278+
QTreeWidgetItem* item = *itr;
279+
QString itemText = item->text(0);
280+
281+
if (item == treeView->currentItem()) {
282+
rememberNextIndex = true;
283+
}
284+
285+
if (itemText.contains(searchText, Qt::CaseInsensitive)) {
286+
searchMatches.append(item);
287+
288+
if (rememberNextIndex) {
289+
firstSearchIndex = searchMatches.size() - 1;
290+
rememberNextIndex = false;
291+
}
292+
}
293+
294+
++itr;
295+
}
296+
297+
updateSearchStatus();
298+
299+
if (!searchMatches.isEmpty()) {
300+
selectSearchMatch(firstSearchIndex);
301+
}
302+
}
303+
304+
void EmvViewerMainWindow::searchNext()
305+
{
306+
if (searchMatches.isEmpty()) {
307+
return;
308+
}
309+
310+
int nextSearchIndex = currentSearchIndex + 1;
311+
312+
if (nextSearchIndex >= searchMatches.size()) {
313+
nextSearchIndex = 0;
314+
}
315+
316+
selectSearchMatch(nextSearchIndex);
317+
}
318+
319+
void EmvViewerMainWindow::searchPrevious()
320+
{
321+
if (searchMatches.isEmpty()) {
322+
return;
323+
}
324+
325+
int prevSearchIndex = currentSearchIndex - 1;
326+
327+
if (prevSearchIndex < 0) {
328+
prevSearchIndex = searchMatches.size() - 1;
329+
}
330+
331+
selectSearchMatch(prevSearchIndex);
332+
}
333+
334+
void EmvViewerMainWindow::selectSearchMatch(int index)
335+
{
336+
QTreeWidgetItem* item;
337+
338+
if (index < 0 || index >= searchMatches.size()) {
339+
return;
340+
}
341+
currentSearchIndex = index;
342+
item = searchMatches.at(index);
343+
344+
// Expand parents to make match visible
345+
QTreeWidgetItem* parent = item->parent();
346+
while (parent) {
347+
parent->setExpanded(true);
348+
parent = parent->parent();
349+
}
350+
351+
// Scroll to and select match
352+
treeView->scrollToItem(item);
353+
treeView->setCurrentItem(item);
354+
355+
updateSearchStatus();
356+
}
357+
358+
void EmvViewerMainWindow::updateSearchStatus()
359+
{
360+
bool hasMatches = !searchMatches.isEmpty();
361+
bool hasSearchText = !searchLineEdit->text().isEmpty();
362+
363+
searchNextButton->setEnabled(hasMatches);
364+
searchPreviousButton->setEnabled(hasMatches);
365+
366+
if (!hasSearchText) {
367+
QMainWindow::statusBar()->clearMessage();
368+
} else if (!hasMatches) {
369+
QMainWindow::statusBar()->showMessage(tr("No matches found"));
370+
} else {
371+
QMainWindow::statusBar()->showMessage(
372+
tr("Match %1 of %2").arg(currentSearchIndex + 1).arg(searchMatches.size())
373+
);
374+
}
375+
}
376+
377+
void EmvViewerMainWindow::clearSearch()
378+
{
379+
searchLineEdit->clear();
380+
searchMatches.clear();
381+
currentSearchIndex = -1;
382+
QMainWindow::statusBar()->clearMessage();
240383
}
241384

242385
void EmvViewerMainWindow::on_updateTimer_timeout()
243386
{
244387
updateTreeView();
245388
}
246389

390+
void EmvViewerMainWindow::on_searchTimer_timeout()
391+
{
392+
startSearch();
393+
}
394+
247395
void EmvViewerMainWindow::on_dataEdit_textChanged()
248396
{
249397
// Rehighlight when text changes. This is required because EmvHighlighter

viewer/emv-viewer-mainwindow.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,16 @@ class EmvViewerMainWindow : public QMainWindow, private Ui::MainWindow
5353

5454
void updateTreeView();
5555

56+
void startSearch();
57+
void searchNext();
58+
void searchPrevious();
59+
void selectSearchMatch(int index);
60+
void updateSearchStatus();
61+
void clearSearch();
62+
5663
private slots: // connect-by-name helper functions
5764
void on_updateTimer_timeout();
65+
void on_searchTimer_timeout();
5866
void on_dataEdit_textChanged();
5967
void on_tagsCheckBox_stateChanged(int state);
6068
void on_paddingCheckBox_stateChanged(int state);
@@ -69,10 +77,15 @@ private slots: // connect-by-name helper functions
6977

7078
protected:
7179
QTimer* updateTimer;
80+
QTimer* searchTimer;
7281
EmvHighlighter* highlighter;
7382
QLineEdit* searchLineEdit;
7483
QToolButton* searchNextButton;
7584
QToolButton* searchPreviousButton;
85+
86+
private: // Search state
87+
QList<QTreeWidgetItem*> searchMatches;
88+
int currentSearchIndex = -1;
7689
};
7790

7891
#endif

0 commit comments

Comments
 (0)