keepassxc

Форк
0
/
SearchWidget.cpp 
235 строк · 8.4 Кб
1
/*
2
 *  Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
3
 *
4
 *  This program is free software: you can redistribute it and/or modify
5
 *  it under the terms of the GNU General Public License as published by
6
 *  the Free Software Foundation, either version 2 or (at your option)
7
 *  version 3 of the License.
8
 *
9
 *  This program is distributed in the hope that it will be useful,
10
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 *  GNU General Public License for more details.
13
 *
14
 *  You should have received a copy of the GNU General Public License
15
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17

18
#include "SearchWidget.h"
19
#include "ui_SearchHelpWidget.h"
20
#include "ui_SearchWidget.h"
21

22
#include <QKeyEvent>
23
#include <QMenu>
24
#include <QShortcut>
25
#include <QToolButton>
26

27
#include "core/SignalMultiplexer.h"
28
#include "gui/Icons.h"
29
#include "gui/widgets/PopupHelpWidget.h"
30

31
SearchWidget::SearchWidget(QWidget* parent)
32
    : QWidget(parent)
33
    , m_ui(new Ui::SearchWidget())
34
    , m_searchTimer(new QTimer(this))
35
    , m_clearSearchTimer(new QTimer(this))
36
{
37
    m_ui->setupUi(this);
38
    setFocusProxy(m_ui->searchEdit);
39

40
    m_helpWidget = new PopupHelpWidget(m_ui->searchEdit);
41
    Ui::SearchHelpWidget helpUi;
42
    helpUi.setupUi(m_helpWidget);
43

44
    m_searchTimer->setSingleShot(true);
45
    m_clearSearchTimer->setSingleShot(true);
46

47
    new QShortcut(Qt::CTRL + Qt::Key_J, this, SLOT(toggleHelp()), nullptr, Qt::WidgetWithChildrenShortcut);
48

49
    connect(m_ui->searchEdit, SIGNAL(textChanged(QString)), SLOT(startSearchTimer()));
50
    connect(m_ui->helpIcon, SIGNAL(triggered()), SLOT(toggleHelp()));
51
    connect(m_ui->searchIcon, SIGNAL(triggered()), SLOT(showSearchMenu()));
52
    connect(m_ui->saveIcon, &QAction::triggered, this, [this] { emit saveSearch(m_ui->searchEdit->text()); });
53
    connect(m_searchTimer, SIGNAL(timeout()), SLOT(startSearch()));
54
    connect(m_clearSearchTimer, SIGNAL(timeout()), SLOT(clearSearch()));
55
    connect(this, SIGNAL(escapePressed()), SLOT(clearSearch()));
56

57
    m_ui->searchEdit->setPlaceholderText(tr("Search (%1)…", "Search placeholder text, %1 is the keyboard shortcut")
58
                                             .arg(QKeySequence(QKeySequence::Find).toString(QKeySequence::NativeText)));
59
    m_ui->searchEdit->installEventFilter(this);
60

61
    m_searchMenu = new QMenu(this);
62
    m_actionCaseSensitive = m_searchMenu->addAction(tr("Case sensitive"), this, SLOT(updateCaseSensitive()));
63
    m_actionCaseSensitive->setObjectName("actionSearchCaseSensitive");
64
    m_actionCaseSensitive->setCheckable(true);
65

66
    m_actionLimitGroup = m_searchMenu->addAction(tr("Limit search to selected group"), this, SLOT(updateLimitGroup()));
67
    m_actionLimitGroup->setObjectName("actionSearchLimitGroup");
68
    m_actionLimitGroup->setCheckable(true);
69
    m_actionLimitGroup->setChecked(config()->get(Config::SearchLimitGroup).toBool());
70

71
    m_ui->searchIcon->setIcon(icons()->icon("system-search"));
72
    m_ui->searchEdit->addAction(m_ui->searchIcon, QLineEdit::LeadingPosition);
73

74
    m_ui->helpIcon->setIcon(icons()->icon("system-help"));
75
    m_ui->searchEdit->addAction(m_ui->helpIcon, QLineEdit::TrailingPosition);
76

77
    m_ui->saveIcon->setIcon(icons()->icon("document-save"));
78
    m_ui->searchEdit->addAction(m_ui->saveIcon, QLineEdit::TrailingPosition);
79
    m_ui->saveIcon->setVisible(false);
80

81
    // Fix initial visibility of actions (bug in Qt)
82
    for (QToolButton* toolButton : m_ui->searchEdit->findChildren<QToolButton*>()) {
83
        toolButton->setVisible(toolButton->defaultAction()->isVisible());
84
    }
85
}
86

87
SearchWidget::~SearchWidget() = default;
88

89
bool SearchWidget::eventFilter(QObject* obj, QEvent* event)
90
{
91
    if (event->type() == QEvent::KeyPress) {
92
        auto keyEvent = static_cast<QKeyEvent*>(event);
93
        if (keyEvent->key() == Qt::Key_Escape) {
94
            emit escapePressed();
95
            return true;
96
        } else if (keyEvent->matches(QKeySequence::Copy)) {
97
            // If Control+C is pressed in the search edit when no text
98
            // is selected, copy the password of the current entry.
99
            if (!m_ui->searchEdit->hasSelectedText()) {
100
                emit copyPressed();
101
                return true;
102
            }
103
        } else if (keyEvent->matches(QKeySequence::MoveToNextLine)) {
104
            if (m_ui->searchEdit->cursorPosition() == m_ui->searchEdit->text().length()) {
105
                // If down is pressed at EOL, move the focus to the entry view
106
                emit downPressed();
107
                return true;
108
            } else {
109
                // Otherwise move the cursor to EOL
110
                m_ui->searchEdit->setCursorPosition(m_ui->searchEdit->text().length());
111
                return true;
112
            }
113
        }
114
    } else if (event->type() == QEvent::FocusOut) {
115
        if (config()->get(Config::Security_ClearSearch).toBool()) {
116
            int timeout = config()->get(Config::Security_ClearSearchTimeout).toInt();
117
            if (timeout > 0) {
118
                // Auto-clear search after set timeout (5 minutes by default)
119
                m_clearSearchTimer->start(timeout * 60000); // 60 sec * 1000 ms
120
            }
121
        }
122
        emit lostFocus();
123
    } else if (event->type() == QEvent::FocusIn) {
124
        // Never clear the search if we are using it
125
        m_clearSearchTimer->stop();
126
    }
127

128
    return QWidget::eventFilter(obj, event);
129
}
130

131
void SearchWidget::connectSignals(SignalMultiplexer& mx)
132
{
133
    // Connects basically only to the current DatabaseWidget, but allows to switch between instances!
134
    mx.connect(this, SIGNAL(search(QString)), SLOT(search(QString)));
135
    mx.connect(this, SIGNAL(saveSearch(QString)), SLOT(saveSearch(QString)));
136
    mx.connect(this, SIGNAL(caseSensitiveChanged(bool)), SLOT(setSearchCaseSensitive(bool)));
137
    mx.connect(this, SIGNAL(limitGroupChanged(bool)), SLOT(setSearchLimitGroup(bool)));
138
    mx.connect(this, SIGNAL(copyPressed()), SLOT(copyPassword()));
139
    mx.connect(this, SIGNAL(downPressed()), SLOT(focusOnEntries()));
140
    mx.connect(SIGNAL(requestSearch(QString)), m_ui->searchEdit, SLOT(setText(QString)));
141
    mx.connect(SIGNAL(clearSearch()), this, SLOT(clearSearch()));
142
    mx.connect(SIGNAL(entrySelectionChanged()), this, SLOT(resetSearchClearTimer()));
143
    mx.connect(SIGNAL(currentModeChanged(DatabaseWidget::Mode)), this, SLOT(resetSearchClearTimer()));
144
    mx.connect(SIGNAL(databaseUnlocked()), this, SLOT(focusSearch()));
145
    mx.connect(m_ui->searchEdit, SIGNAL(returnPressed()), SLOT(switchToEntryEdit()));
146
}
147

148
void SearchWidget::databaseChanged(DatabaseWidget* dbWidget)
149
{
150
    if (dbWidget != nullptr) {
151
        // Set current search text from this database
152
        m_ui->searchEdit->setText(dbWidget->getCurrentSearch());
153
        // Enforce search policy
154
        emit caseSensitiveChanged(m_actionCaseSensitive->isChecked());
155
        emit limitGroupChanged(m_actionLimitGroup->isChecked());
156
    } else {
157
        clearSearch();
158
    }
159
}
160

161
void SearchWidget::startSearchTimer()
162
{
163
    if (!m_searchTimer->isActive()) {
164
        m_searchTimer->stop();
165
    }
166
    m_searchTimer->start(100);
167
}
168

169
void SearchWidget::startSearch()
170
{
171
    if (!m_searchTimer->isActive()) {
172
        m_searchTimer->stop();
173
    }
174

175
    m_ui->saveIcon->setVisible(true);
176
    search(m_ui->searchEdit->text());
177
}
178

179
void SearchWidget::resetSearchClearTimer()
180
{
181
    // Restart the search clear timer if it is running
182
    if (m_clearSearchTimer->isActive()) {
183
        m_clearSearchTimer->start();
184
    }
185
}
186

187
void SearchWidget::updateCaseSensitive()
188
{
189
    emit caseSensitiveChanged(m_actionCaseSensitive->isChecked());
190
}
191

192
void SearchWidget::setCaseSensitive(bool state)
193
{
194
    m_actionCaseSensitive->setChecked(state);
195
    updateCaseSensitive();
196
}
197

198
void SearchWidget::updateLimitGroup()
199
{
200
    config()->set(Config::SearchLimitGroup, m_actionLimitGroup->isChecked());
201
    emit limitGroupChanged(m_actionLimitGroup->isChecked());
202
}
203

204
void SearchWidget::setLimitGroup(bool state)
205
{
206
    m_actionLimitGroup->setChecked(state);
207
    updateLimitGroup();
208
}
209

210
void SearchWidget::focusSearch()
211
{
212
    m_ui->searchEdit->setFocus();
213
    m_ui->searchEdit->selectAll();
214
}
215

216
void SearchWidget::clearSearch()
217
{
218
    m_ui->searchEdit->clear();
219
    m_ui->saveIcon->setVisible(false);
220
    emit searchCanceled();
221
}
222

223
void SearchWidget::toggleHelp()
224
{
225
    if (m_helpWidget->isVisible()) {
226
        m_helpWidget->hide();
227
    } else {
228
        m_helpWidget->show();
229
    }
230
}
231

232
void SearchWidget::showSearchMenu()
233
{
234
    m_searchMenu->exec(m_ui->searchEdit->mapToGlobal(m_ui->searchEdit->rect().bottomLeft()));
235
}
236

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.