2016-11-02 21:01:02 -04:00
|
|
|
/*
|
2017-06-09 17:40:36 -04:00
|
|
|
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
2016-11-02 21:01:02 -04:00
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 2 or (at your option)
|
|
|
|
* version 3 of the License.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "SearchWidget.h"
|
2018-11-01 21:33:27 -04:00
|
|
|
#include "ui_SearchHelpWidget.h"
|
2019-01-20 09:50:20 -05:00
|
|
|
#include "ui_SearchWidget.h"
|
2016-11-02 21:01:02 -04:00
|
|
|
|
|
|
|
#include <QKeyEvent>
|
|
|
|
#include <QMenu>
|
|
|
|
#include <QShortcut>
|
2017-04-05 04:42:15 -04:00
|
|
|
#include <QToolButton>
|
2016-11-02 21:01:02 -04:00
|
|
|
|
2017-06-20 15:54:13 -04:00
|
|
|
#include "core/Config.h"
|
2016-11-02 21:01:02 -04:00
|
|
|
#include "core/FilePath.h"
|
2018-11-01 21:33:27 -04:00
|
|
|
#include "gui/widgets/PopupHelpWidget.h"
|
2016-11-02 21:01:02 -04:00
|
|
|
|
2017-05-11 14:36:06 -04:00
|
|
|
SearchWidget::SearchWidget(QWidget* parent)
|
2016-11-02 21:01:02 -04:00
|
|
|
: QWidget(parent)
|
|
|
|
, m_ui(new Ui::SearchWidget())
|
2018-11-15 17:37:16 -05:00
|
|
|
, m_searchTimer(new QTimer(this))
|
|
|
|
, m_clearSearchTimer(new QTimer(this))
|
2016-11-02 21:01:02 -04:00
|
|
|
{
|
|
|
|
m_ui->setupUi(this);
|
2019-06-11 03:37:23 -04:00
|
|
|
setFocusProxy(m_ui->searchEdit);
|
2016-11-02 21:01:02 -04:00
|
|
|
|
2018-11-01 21:33:27 -04:00
|
|
|
m_helpWidget = new PopupHelpWidget(m_ui->searchEdit);
|
2019-01-20 09:50:20 -05:00
|
|
|
m_helpWidget->setOffset(QPoint(0, 1));
|
2018-11-01 21:33:27 -04:00
|
|
|
Ui::SearchHelpWidget helpUi;
|
|
|
|
helpUi.setupUi(m_helpWidget);
|
|
|
|
|
2016-11-02 21:01:02 -04:00
|
|
|
m_searchTimer->setSingleShot(true);
|
2018-11-15 17:37:16 -05:00
|
|
|
m_clearSearchTimer->setSingleShot(true);
|
2016-11-02 21:01:02 -04:00
|
|
|
|
|
|
|
connect(m_ui->searchEdit, SIGNAL(textChanged(QString)), SLOT(startSearchTimer()));
|
2017-04-05 04:42:15 -04:00
|
|
|
connect(m_ui->clearIcon, SIGNAL(triggered(bool)), m_ui->searchEdit, SLOT(clear()));
|
2018-11-01 21:33:27 -04:00
|
|
|
connect(m_ui->helpIcon, SIGNAL(triggered()), SLOT(toggleHelp()));
|
2016-11-02 21:01:02 -04:00
|
|
|
connect(m_searchTimer, SIGNAL(timeout()), this, SLOT(startSearch()));
|
2018-11-15 17:37:16 -05:00
|
|
|
connect(m_clearSearchTimer, SIGNAL(timeout()), m_ui->searchEdit, SLOT(clear()));
|
2016-12-22 02:21:52 -05:00
|
|
|
connect(this, SIGNAL(escapePressed()), m_ui->searchEdit, SLOT(clear()));
|
2016-11-02 21:01:02 -04:00
|
|
|
|
2018-11-16 10:00:59 -05:00
|
|
|
new QShortcut(QKeySequence::Find, this, SLOT(searchFocus()), nullptr, Qt::ApplicationShortcut);
|
2017-05-11 14:36:06 -04:00
|
|
|
new QShortcut(Qt::Key_Escape, m_ui->searchEdit, SLOT(clear()), nullptr, Qt::ApplicationShortcut);
|
2016-11-02 21:01:02 -04:00
|
|
|
|
2018-11-16 10:00:59 -05:00
|
|
|
m_ui->searchEdit->setPlaceholderText(tr("Search (%1)...", "Search placeholder text, %1 is the keyboard shortcut")
|
2019-01-20 09:50:20 -05:00
|
|
|
.arg(QKeySequence(QKeySequence::Find).toString(QKeySequence::NativeText)));
|
2016-12-22 02:21:52 -05:00
|
|
|
m_ui->searchEdit->installEventFilter(this);
|
2016-11-02 21:01:02 -04:00
|
|
|
|
2017-05-11 14:36:06 -04:00
|
|
|
QMenu* searchMenu = new QMenu();
|
2018-03-13 15:59:08 -04:00
|
|
|
m_actionCaseSensitive = searchMenu->addAction(tr("Case sensitive"), this, SLOT(updateCaseSensitive()));
|
2016-11-28 19:02:21 -05:00
|
|
|
m_actionCaseSensitive->setObjectName("actionSearchCaseSensitive");
|
2016-11-02 21:01:02 -04:00
|
|
|
m_actionCaseSensitive->setCheckable(true);
|
|
|
|
|
2017-06-20 15:54:13 -04:00
|
|
|
m_actionLimitGroup = searchMenu->addAction(tr("Limit search to selected group"), this, SLOT(updateLimitGroup()));
|
|
|
|
m_actionLimitGroup->setObjectName("actionSearchLimitGroup");
|
|
|
|
m_actionLimitGroup->setCheckable(true);
|
|
|
|
m_actionLimitGroup->setChecked(config()->get("SearchLimitGroup", false).toBool());
|
|
|
|
|
2016-11-02 21:01:02 -04:00
|
|
|
m_ui->searchIcon->setIcon(filePath()->icon("actions", "system-search"));
|
|
|
|
m_ui->searchIcon->setMenu(searchMenu);
|
2017-04-05 04:42:15 -04:00
|
|
|
m_ui->searchEdit->addAction(m_ui->searchIcon, QLineEdit::LeadingPosition);
|
2017-01-14 22:29:16 -05:00
|
|
|
|
2018-11-01 21:33:27 -04:00
|
|
|
m_ui->helpIcon->setIcon(filePath()->icon("actions", "system-help"));
|
|
|
|
m_ui->searchEdit->addAction(m_ui->helpIcon, QLineEdit::TrailingPosition);
|
|
|
|
|
2017-01-14 22:29:16 -05:00
|
|
|
m_ui->clearIcon->setIcon(filePath()->icon("actions", "edit-clear-locationbar-rtl"));
|
2017-04-05 04:42:15 -04:00
|
|
|
m_ui->clearIcon->setVisible(false);
|
|
|
|
m_ui->searchEdit->addAction(m_ui->clearIcon, QLineEdit::TrailingPosition);
|
|
|
|
|
|
|
|
// Fix initial visibility of actions (bug in Qt)
|
2017-05-11 14:36:06 -04:00
|
|
|
for (QToolButton* toolButton : m_ui->searchEdit->findChildren<QToolButton*>()) {
|
2017-04-05 04:42:15 -04:00
|
|
|
toolButton->setVisible(toolButton->defaultAction()->isVisible());
|
|
|
|
}
|
2016-11-02 21:01:02 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
SearchWidget::~SearchWidget()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2017-05-11 14:36:06 -04:00
|
|
|
bool SearchWidget::eventFilter(QObject* obj, QEvent* event)
|
2016-12-23 23:14:50 -05:00
|
|
|
{
|
|
|
|
if (event->type() == QEvent::KeyPress) {
|
2017-05-11 14:36:06 -04:00
|
|
|
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
2016-12-23 23:14:50 -05:00
|
|
|
if (keyEvent->key() == Qt::Key_Escape) {
|
|
|
|
emit escapePressed();
|
|
|
|
return true;
|
2019-04-07 12:00:02 -04:00
|
|
|
} else 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.
|
|
|
|
if (!m_ui->searchEdit->hasSelectedText()) {
|
|
|
|
emit copyPressed();
|
|
|
|
return true;
|
|
|
|
}
|
2017-05-11 14:36:06 -04:00
|
|
|
} else if (keyEvent->matches(QKeySequence::MoveToNextLine)) {
|
2017-01-14 20:27:34 -05:00
|
|
|
if (m_ui->searchEdit->cursorPosition() == m_ui->searchEdit->text().length()) {
|
|
|
|
// If down is pressed at EOL, move the focus to the entry view
|
|
|
|
emit downPressed();
|
|
|
|
return true;
|
2017-05-11 14:36:06 -04:00
|
|
|
} else {
|
2017-01-14 20:27:34 -05:00
|
|
|
// Otherwise move the cursor to EOL
|
|
|
|
m_ui->searchEdit->setCursorPosition(m_ui->searchEdit->text().length());
|
|
|
|
return true;
|
|
|
|
}
|
2016-12-23 23:14:50 -05:00
|
|
|
}
|
2018-11-15 17:37:16 -05:00
|
|
|
} else if (event->type() == QEvent::FocusOut) {
|
2019-07-30 23:44:34 -04:00
|
|
|
if (config()->get("security/clearsearch").toBool()) {
|
|
|
|
int timeout = config()->get("security/clearsearchtimeout").toInt();
|
|
|
|
if (timeout > 0) {
|
|
|
|
// Auto-clear search after set timeout (5 minutes by default)
|
|
|
|
m_clearSearchTimer->start(timeout * 60000); // 60 sec * 1000 ms
|
|
|
|
}
|
|
|
|
}
|
2018-11-15 17:37:16 -05:00
|
|
|
} else if (event->type() == QEvent::FocusIn) {
|
|
|
|
m_clearSearchTimer->stop();
|
2016-12-23 23:14:50 -05:00
|
|
|
}
|
|
|
|
|
2018-11-01 21:33:27 -04:00
|
|
|
return QWidget::eventFilter(obj, event);
|
2016-12-23 23:14:50 -05:00
|
|
|
}
|
|
|
|
|
2016-11-02 21:01:02 -04:00
|
|
|
void SearchWidget::connectSignals(SignalMultiplexer& mx)
|
|
|
|
{
|
2019-03-07 10:57:13 -05:00
|
|
|
// Connects basically only to the current DatabaseWidget, but allows to switch between instances!
|
2016-11-02 21:01:02 -04:00
|
|
|
mx.connect(this, SIGNAL(search(QString)), SLOT(search(QString)));
|
2016-11-28 19:02:21 -05:00
|
|
|
mx.connect(this, SIGNAL(caseSensitiveChanged(bool)), SLOT(setSearchCaseSensitive(bool)));
|
2017-06-20 15:54:13 -04:00
|
|
|
mx.connect(this, SIGNAL(limitGroupChanged(bool)), SLOT(setSearchLimitGroup(bool)));
|
2016-12-23 23:14:50 -05:00
|
|
|
mx.connect(this, SIGNAL(copyPressed()), SLOT(copyPassword()));
|
|
|
|
mx.connect(this, SIGNAL(downPressed()), SLOT(setFocus()));
|
2018-03-19 23:16:22 -04:00
|
|
|
mx.connect(SIGNAL(clearSearch()), m_ui->searchEdit, SLOT(clear()));
|
2017-01-04 20:46:52 -05:00
|
|
|
mx.connect(m_ui->searchEdit, SIGNAL(returnPressed()), SLOT(switchToEntryEdit()));
|
2016-11-02 21:01:02 -04:00
|
|
|
}
|
|
|
|
|
2017-05-11 14:36:06 -04:00
|
|
|
void SearchWidget::databaseChanged(DatabaseWidget* dbWidget)
|
2016-11-02 21:01:02 -04:00
|
|
|
{
|
|
|
|
if (dbWidget != nullptr) {
|
|
|
|
// Set current search text from this database
|
|
|
|
m_ui->searchEdit->setText(dbWidget->getCurrentSearch());
|
2017-08-22 18:21:08 -04:00
|
|
|
// Keyboard focus on search widget at database unlocking
|
2018-11-22 05:47:31 -05:00
|
|
|
connect(dbWidget, SIGNAL(databaseUnlocked()), this, SLOT(searchFocus()));
|
2016-11-02 21:01:02 -04:00
|
|
|
// Enforce search policy
|
2016-11-28 19:02:21 -05:00
|
|
|
emit caseSensitiveChanged(m_actionCaseSensitive->isChecked());
|
2017-06-20 15:54:13 -04:00
|
|
|
emit limitGroupChanged(m_actionLimitGroup->isChecked());
|
2016-11-02 21:01:02 -04:00
|
|
|
} else {
|
|
|
|
m_ui->searchEdit->clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SearchWidget::startSearchTimer()
|
|
|
|
{
|
|
|
|
if (!m_searchTimer->isActive()) {
|
|
|
|
m_searchTimer->stop();
|
|
|
|
}
|
2018-03-25 16:24:30 -04:00
|
|
|
m_searchTimer->start(300);
|
2016-11-02 21:01:02 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void SearchWidget::startSearch()
|
|
|
|
{
|
|
|
|
if (!m_searchTimer->isActive()) {
|
|
|
|
m_searchTimer->stop();
|
|
|
|
}
|
|
|
|
|
2017-01-14 22:29:16 -05:00
|
|
|
bool hasText = m_ui->searchEdit->text().length() > 0;
|
2017-04-05 04:42:15 -04:00
|
|
|
m_ui->clearIcon->setVisible(hasText);
|
2017-01-14 22:29:16 -05:00
|
|
|
|
2016-11-02 21:01:02 -04:00
|
|
|
search(m_ui->searchEdit->text());
|
|
|
|
}
|
|
|
|
|
|
|
|
void SearchWidget::updateCaseSensitive()
|
|
|
|
{
|
2016-11-28 19:02:21 -05:00
|
|
|
emit caseSensitiveChanged(m_actionCaseSensitive->isChecked());
|
2016-11-02 21:01:02 -04:00
|
|
|
}
|
|
|
|
|
2016-11-28 19:02:21 -05:00
|
|
|
void SearchWidget::setCaseSensitive(bool state)
|
2016-11-02 21:01:02 -04:00
|
|
|
{
|
2016-11-28 19:02:21 -05:00
|
|
|
m_actionCaseSensitive->setChecked(state);
|
|
|
|
updateCaseSensitive();
|
2016-11-02 21:01:02 -04:00
|
|
|
}
|
2017-01-04 20:46:52 -05:00
|
|
|
|
2017-06-20 15:54:13 -04:00
|
|
|
void SearchWidget::updateLimitGroup()
|
|
|
|
{
|
|
|
|
config()->set("SearchLimitGroup", m_actionLimitGroup->isChecked());
|
|
|
|
emit limitGroupChanged(m_actionLimitGroup->isChecked());
|
|
|
|
}
|
|
|
|
|
|
|
|
void SearchWidget::setLimitGroup(bool state)
|
|
|
|
{
|
|
|
|
m_actionLimitGroup->setChecked(state);
|
|
|
|
updateLimitGroup();
|
|
|
|
}
|
|
|
|
|
2017-01-04 20:46:52 -05:00
|
|
|
void SearchWidget::searchFocus()
|
|
|
|
{
|
|
|
|
m_ui->searchEdit->setFocus();
|
|
|
|
m_ui->searchEdit->selectAll();
|
|
|
|
}
|
2018-11-01 21:33:27 -04:00
|
|
|
|
|
|
|
void SearchWidget::toggleHelp()
|
|
|
|
{
|
|
|
|
if (m_helpWidget->isVisible()) {
|
|
|
|
m_helpWidget->hide();
|
|
|
|
} else {
|
|
|
|
m_helpWidget->show();
|
|
|
|
}
|
|
|
|
}
|