mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-04-07 06:27:39 +03:00
Make search always visible (PR #67)
* Moved search bar to toolbar and consolidated search options into dropdown list * Updated GUI tests to be atomic and rewrote search tests * Searches are saved between databases * Search is cleared when all databases are closed * Implemented global search shortcut (CTRL+F) and a notification bar when in search mode
This commit is contained in:
parent
3f80134f07
commit
13983d0e51
15 changed files with 512 additions and 417 deletions
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
|
||||
#include "DatabaseWidget.h"
|
||||
#include "ui_SearchWidget.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QDesktopServices>
|
||||
|
@ -25,8 +24,10 @@
|
|||
#include <QLineEdit>
|
||||
#include <QKeyEvent>
|
||||
#include <QSplitter>
|
||||
#include <QTimer>
|
||||
#include <QLabel>
|
||||
#include <QProcess>
|
||||
#include <QHeaderView>
|
||||
#include <QApplication>
|
||||
|
||||
#include "autotype/AutoType.h"
|
||||
#include "core/Config.h"
|
||||
|
@ -50,24 +51,16 @@
|
|||
DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
: QStackedWidget(parent)
|
||||
, m_db(db)
|
||||
, m_searchUi(new Ui::SearchWidget())
|
||||
, m_searchWidget(new QWidget())
|
||||
, m_newGroup(nullptr)
|
||||
, m_newEntry(nullptr)
|
||||
, m_newParent(nullptr)
|
||||
{
|
||||
m_searchUi->setupUi(m_searchWidget);
|
||||
|
||||
m_searchTimer = new QTimer(this);
|
||||
m_searchTimer->setSingleShot(true);
|
||||
|
||||
m_mainWidget = new QWidget(this);
|
||||
QLayout* layout = new QHBoxLayout(m_mainWidget);
|
||||
m_splitter = new QSplitter(m_mainWidget);
|
||||
m_splitter->setChildrenCollapsible(false);
|
||||
|
||||
QWidget* rightHandSideWidget = new QWidget(m_splitter);
|
||||
m_searchWidget->setParent(rightHandSideWidget);
|
||||
|
||||
m_groupView = new GroupView(db, m_splitter);
|
||||
m_groupView->setObjectName("groupView");
|
||||
|
@ -82,25 +75,24 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
|||
connect(m_entryView, SIGNAL(customContextMenuRequested(QPoint)),
|
||||
SLOT(emitEntryContextMenuRequested(QPoint)));
|
||||
|
||||
QAction* closeAction = new QAction(m_searchWidget);
|
||||
QIcon closeIcon = filePath()->icon("actions", "dialog-close");
|
||||
closeAction->setIcon(closeIcon);
|
||||
m_searchUi->closeSearchButton->setDefaultAction(closeAction);
|
||||
m_searchUi->closeSearchButton->setShortcut(Qt::Key_Escape);
|
||||
m_searchWidget->hide();
|
||||
m_searchUi->caseSensitiveCheckBox->setVisible(false);
|
||||
m_searchUi->searchEdit->installEventFilter(this);
|
||||
// Add a notification for when we are searching
|
||||
m_searchingLabel = new QLabel();
|
||||
m_searchingLabel->setText(tr("Searching..."));
|
||||
m_searchingLabel->setAlignment(Qt::AlignCenter);
|
||||
m_searchingLabel->setStyleSheet("background-color: rgb(255, 253, 160);"
|
||||
"border: 2px solid rgb(190, 190, 190);"
|
||||
"border-radius: 5px;");
|
||||
|
||||
QVBoxLayout* vLayout = new QVBoxLayout(rightHandSideWidget);
|
||||
vLayout->setMargin(0);
|
||||
vLayout->addWidget(m_searchWidget);
|
||||
vLayout->addWidget(m_searchingLabel);
|
||||
vLayout->addWidget(m_entryView);
|
||||
|
||||
m_searchingLabel->setVisible(false);
|
||||
|
||||
rightHandSideWidget->setLayout(vLayout);
|
||||
|
||||
setTabOrder(m_searchUi->searchRootRadioButton, m_entryView);
|
||||
setTabOrder(m_entryView, m_groupView);
|
||||
setTabOrder(m_groupView, m_searchWidget);
|
||||
|
||||
m_splitter->addWidget(m_groupView);
|
||||
m_splitter->addWidget(rightHandSideWidget);
|
||||
|
@ -158,13 +150,9 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
|||
connect(m_keepass1OpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool)));
|
||||
connect(m_unlockDatabaseWidget, SIGNAL(editFinished(bool)), SLOT(unlockDatabase(bool)));
|
||||
connect(this, SIGNAL(currentChanged(int)), this, SLOT(emitCurrentModeChanged()));
|
||||
connect(m_searchUi->searchEdit, SIGNAL(textChanged(QString)), this, SLOT(startSearchTimer()));
|
||||
connect(m_searchUi->caseSensitiveCheckBox, SIGNAL(toggled(bool)), this, SLOT(startSearch()));
|
||||
connect(m_searchUi->searchCurrentRadioButton, SIGNAL(toggled(bool)), this, SLOT(startSearch()));
|
||||
connect(m_searchUi->searchRootRadioButton, SIGNAL(toggled(bool)), this, SLOT(startSearch()));
|
||||
connect(m_searchUi->searchEdit, SIGNAL(returnPressed()), m_entryView, SLOT(setFocus()));
|
||||
connect(m_searchTimer, SIGNAL(timeout()), this, SLOT(search()));
|
||||
connect(closeAction, SIGNAL(triggered()), this, SLOT(closeSearch()));
|
||||
|
||||
m_searchCaseSensitive = false;
|
||||
m_searchCurrentGroup = false;
|
||||
|
||||
setCurrentWidget(m_mainWidget);
|
||||
}
|
||||
|
@ -764,118 +752,82 @@ void DatabaseWidget::switchToImportKeepass1(const QString& fileName)
|
|||
setCurrentWidget(m_keepass1OpenWidget);
|
||||
}
|
||||
|
||||
void DatabaseWidget::openSearch()
|
||||
void DatabaseWidget::search(const QString& searchtext)
|
||||
{
|
||||
if (isInSearchMode()) {
|
||||
m_searchUi->searchEdit->selectAll();
|
||||
|
||||
if (!m_searchUi->searchEdit->hasFocus()) {
|
||||
m_searchUi->searchEdit->setFocus();
|
||||
// make sure the search action is checked again
|
||||
emitCurrentModeChanged();
|
||||
}
|
||||
if (searchtext.isEmpty())
|
||||
{
|
||||
endSearch();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
showSearch();
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseWidget::closeSearch()
|
||||
{
|
||||
Q_ASSERT(m_lastGroup);
|
||||
|
||||
Q_EMIT listModeAboutToActivate();
|
||||
|
||||
m_groupView->setCurrentGroup(m_lastGroup);
|
||||
m_searchTimer->stop();
|
||||
|
||||
Q_EMIT listModeActivated();
|
||||
}
|
||||
|
||||
void DatabaseWidget::showSearch()
|
||||
{
|
||||
Q_EMIT searchModeAboutToActivate();
|
||||
|
||||
m_searchUi->searchEdit->blockSignals(true);
|
||||
m_searchUi->searchEdit->clear();
|
||||
m_searchUi->searchEdit->blockSignals(false);
|
||||
if (!isInSearchMode())
|
||||
{
|
||||
m_lastGroup = m_groupView->currentGroup();
|
||||
Q_ASSERT(m_lastGroup);
|
||||
}
|
||||
|
||||
m_searchUi->searchCurrentRadioButton->blockSignals(true);
|
||||
m_searchUi->searchRootRadioButton->blockSignals(true);
|
||||
m_searchUi->searchRootRadioButton->setChecked(true);
|
||||
m_searchUi->searchCurrentRadioButton->blockSignals(false);
|
||||
m_searchUi->searchRootRadioButton->blockSignals(false);
|
||||
Group* searchGroup = m_searchCurrentGroup ? m_lastGroup : m_db->rootGroup();
|
||||
Qt::CaseSensitivity sensitivity = m_searchCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;
|
||||
|
||||
m_lastGroup = m_groupView->currentGroup();
|
||||
QList<Entry*> searchResult = EntrySearcher().search(searchtext, searchGroup, sensitivity);
|
||||
|
||||
Q_ASSERT(m_lastGroup);
|
||||
m_entryView->setEntryList(searchResult);
|
||||
m_lastSearchText = searchtext;
|
||||
|
||||
if (m_lastGroup == m_db->rootGroup()) {
|
||||
m_searchUi->optionsWidget->hide();
|
||||
m_searchUi->searchCurrentRadioButton->hide();
|
||||
m_searchUi->searchRootRadioButton->hide();
|
||||
// Display a label detailing our search results
|
||||
if (searchResult.size() > 0) {
|
||||
m_searchingLabel->setText(tr("Search Results (%1)").arg(searchResult.size()));
|
||||
}
|
||||
else {
|
||||
m_searchUi->optionsWidget->show();
|
||||
m_searchUi->searchCurrentRadioButton->show();
|
||||
m_searchUi->searchRootRadioButton->show();
|
||||
m_searchUi->searchCurrentRadioButton->setText(tr("Current group")
|
||||
.append(" (")
|
||||
.append(m_lastGroup->name())
|
||||
.append(")"));
|
||||
m_searchingLabel->setText(tr("No Results"));
|
||||
}
|
||||
m_groupView->setCurrentIndex(QModelIndex());
|
||||
|
||||
m_searchWidget->show();
|
||||
search();
|
||||
m_searchUi->searchEdit->setFocus();
|
||||
m_searchingLabel->setVisible(true);
|
||||
|
||||
Q_EMIT searchModeActivated();
|
||||
}
|
||||
|
||||
void DatabaseWidget::search()
|
||||
void DatabaseWidget::setSearchCaseSensitive(bool state)
|
||||
{
|
||||
Q_ASSERT(m_lastGroup);
|
||||
m_searchCaseSensitive = state;
|
||||
|
||||
Group* searchGroup;
|
||||
if (m_searchUi->searchCurrentRadioButton->isChecked()) {
|
||||
searchGroup = m_lastGroup;
|
||||
}
|
||||
else if (m_searchUi->searchRootRadioButton->isChecked()) {
|
||||
searchGroup = m_db->rootGroup();
|
||||
}
|
||||
else {
|
||||
Q_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Qt::CaseSensitivity sensitivity;
|
||||
if (m_searchUi->caseSensitiveCheckBox->isChecked()) {
|
||||
sensitivity = Qt::CaseSensitive;
|
||||
}
|
||||
else {
|
||||
sensitivity = Qt::CaseInsensitive;
|
||||
}
|
||||
|
||||
QList<Entry*> searchResult = EntrySearcher().search(m_searchUi->searchEdit->text(), searchGroup, sensitivity);
|
||||
|
||||
m_entryView->setEntryList(searchResult);
|
||||
if (isInSearchMode())
|
||||
search(m_lastSearchText);
|
||||
}
|
||||
|
||||
void DatabaseWidget::startSearchTimer()
|
||||
void DatabaseWidget::setSearchCurrentGroup(bool state)
|
||||
{
|
||||
if (!m_searchTimer->isActive()) {
|
||||
m_searchTimer->stop();
|
||||
}
|
||||
m_searchTimer->start(100);
|
||||
m_searchCurrentGroup = state;
|
||||
|
||||
if (isInSearchMode())
|
||||
search(m_lastSearchText);
|
||||
}
|
||||
|
||||
void DatabaseWidget::startSearch()
|
||||
QString DatabaseWidget::getCurrentSearch()
|
||||
{
|
||||
if (!m_searchTimer->isActive()) {
|
||||
m_searchTimer->stop();
|
||||
return m_lastSearchText;
|
||||
}
|
||||
|
||||
void DatabaseWidget::endSearch()
|
||||
{
|
||||
if (isInSearchMode())
|
||||
{
|
||||
Q_ASSERT(m_lastGroup);
|
||||
|
||||
Q_EMIT listModeAboutToActivate();
|
||||
|
||||
m_groupView->setCurrentGroup(m_lastGroup);
|
||||
m_entryView->setGroup(m_lastGroup);
|
||||
|
||||
Q_EMIT listModeActivated();
|
||||
}
|
||||
search();
|
||||
|
||||
m_searchingLabel->setVisible(false);
|
||||
m_searchingLabel->setText(tr("Searching..."));
|
||||
|
||||
m_lastSearchText.clear();
|
||||
}
|
||||
|
||||
void DatabaseWidget::emitGroupContextMenuRequested(const QPoint& pos)
|
||||
|
@ -908,16 +860,12 @@ void DatabaseWidget::clearLastGroup(Group* group)
|
|||
{
|
||||
if (group) {
|
||||
m_lastGroup = nullptr;
|
||||
m_searchWidget->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseWidget::lock()
|
||||
{
|
||||
Q_ASSERT(currentMode() != DatabaseWidget::LockedMode);
|
||||
if (isInSearchMode()) {
|
||||
closeSearch();
|
||||
}
|
||||
|
||||
if (m_groupView->currentGroup()) {
|
||||
m_groupBeforeLock = m_groupView->currentGroup()->uuid();
|
||||
|
@ -1008,34 +956,3 @@ bool DatabaseWidget::currentEntryHasNotes()
|
|||
}
|
||||
return !currentEntry->notes().isEmpty();
|
||||
}
|
||||
|
||||
bool DatabaseWidget::eventFilter(QObject* object, QEvent* event)
|
||||
{
|
||||
if (object == m_searchUi->searchEdit) {
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
|
||||
if (keyEvent->matches(QKeySequence::Copy)) {
|
||||
// If Control+C is pressed in the search edit when no
|
||||
// text is selected, copy the password of the current
|
||||
// entry.
|
||||
Entry* currentEntry = m_entryView->currentEntry();
|
||||
if (currentEntry && !m_searchUi->searchEdit->hasSelectedText()) {
|
||||
setClipboardTextAndMinimize(currentEntry->password());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (keyEvent->matches(QKeySequence::MoveToNextLine)) {
|
||||
// If Down is pressed at EOL in the search edit, move
|
||||
// the focus to the entry view.
|
||||
if (!m_searchUi->searchEdit->hasSelectedText()
|
||||
&& m_searchUi->searchEdit->cursorPosition() == m_searchUi->searchEdit->text().size()) {
|
||||
m_entryView->setFocus();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue