diff --git a/src/core/EntryAttributes.cpp b/src/core/EntryAttributes.cpp index bcc08f0fad..80067c5637 100644 --- a/src/core/EntryAttributes.cpp +++ b/src/core/EntryAttributes.cpp @@ -67,6 +67,15 @@ QString EntryAttributes::value(const QString& key) const return m_attributes.value(key); } +QList EntryAttributes::values(const QList& keys) const +{ + QList values; + for (const QString& key : keys) { + values.append(m_attributes.value(key)); + } + return values; +} + bool EntryAttributes::contains(const QString& key) const { return m_attributes.contains(key); diff --git a/src/core/EntryAttributes.h b/src/core/EntryAttributes.h index fdae8a6240..2cba13c648 100644 --- a/src/core/EntryAttributes.h +++ b/src/core/EntryAttributes.h @@ -36,6 +36,7 @@ class EntryAttributes : public QObject bool hasKey(const QString& key) const; QList customKeys() const; QString value(const QString& key) const; + QList values(const QList& keys) const; bool contains(const QString& key) const; bool containsValue(const QString& value) const; bool isProtected(const QString& key) const; diff --git a/src/core/EntrySearcher.cpp b/src/core/EntrySearcher.cpp index a639afd8b3..8ee07dd209 100644 --- a/src/core/EntrySearcher.cpp +++ b/src/core/EntrySearcher.cpp @@ -28,31 +28,83 @@ EntrySearcher::EntrySearcher(bool caseSensitive) { } +/** + * Search group, and its children, by parsing the provided search + * string for search terms. + * + * @param searchString search terms + * @param baseGroup group to start search from, cannot be null + * @param forceSearch ignore group search settings + * @return list of entries that match the search terms + */ QList EntrySearcher::search(const QString& searchString, const Group* baseGroup, bool forceSearch) { Q_ASSERT(baseGroup); + parseSearchTerms(searchString); + return repeat(baseGroup, forceSearch); +} + +/** + * Repeat the last search starting from the given group + * + * @param baseGroup group to start search from, cannot be null + * @param forceSearch ignore group search settings + * @return list of entries that match the search terms + */ +QList EntrySearcher::repeat(const Group* baseGroup, bool forceSearch) +{ + Q_ASSERT(baseGroup); + QList results; for (const auto group : baseGroup->groupsRecursive(true)) { if (forceSearch || group->resolveSearchingEnabled()) { - results.append(searchEntries(searchString, group->entries())); + for (auto* entry : group->entries()) { + if (searchEntryImpl(entry)) { + results.append(entry); + } + } } } - return results; } +/** + * Search provided entries by parsing the search string + * for search terms. + * + * @param searchString search terms + * @param entries list of entries to include in the search + * @return list of entries that match the search terms + */ QList EntrySearcher::searchEntries(const QString& searchString, const QList& entries) +{ + parseSearchTerms(searchString); + return repeatEntries(entries); +} + +/** + * Repeat the last search on the given entries + * + * @param entries list of entries to include in the search + * @return list of entries that match the search terms + */ +QList EntrySearcher::repeatEntries(const QList& entries) { QList results; - for (Entry* entry : entries) { - if (searchEntryImpl(searchString, entry)) { + for (auto* entry : entries) { + if (searchEntryImpl(entry)) { results.append(entry); } } return results; } +/** + * Set the next search to be case sensitive or not + * + * @param state + */ void EntrySearcher::setCaseSensitive(bool state) { m_caseSensitive = state; @@ -63,16 +115,15 @@ bool EntrySearcher::isCaseSensitive() return m_caseSensitive; } -bool EntrySearcher::searchEntryImpl(const QString& searchString, Entry* entry) +bool EntrySearcher::searchEntryImpl(Entry* entry) { // Pre-load in case they are needed - auto attributes = QStringList(entry->attributes()->keys()); + auto attributes_keys = entry->attributes()->customKeys(); + auto attributes = QStringList(attributes_keys + entry->attributes()->values(attributes_keys)); auto attachments = QStringList(entry->attachments()->keys()); bool found; - auto searchTerms = parseSearchTerms(searchString); - - for (const auto& term : searchTerms) { + for (const auto& term : m_searchTerms) { switch (term->field) { case Field::Title: found = term->regex.match(entry->resolvePlaceholder(entry->title())).hasMatch(); @@ -112,10 +163,9 @@ bool EntrySearcher::searchEntryImpl(const QString& searchString, Entry* entry) return true; } -QList> EntrySearcher::parseSearchTerms(const QString& searchString) +void EntrySearcher::parseSearchTerms(const QString& searchString) { - auto terms = QList>(); - + m_searchTerms.clear(); auto results = m_termParser.globalMatch(searchString); while (results.hasNext()) { auto result = results.next(); @@ -165,8 +215,6 @@ QList> EntrySearcher::parseSearchTerms } } - terms.append(term); + m_searchTerms.append(term); } - - return terms; } diff --git a/src/core/EntrySearcher.h b/src/core/EntrySearcher.h index d5a9951f97..153a0612ee 100644 --- a/src/core/EntrySearcher.h +++ b/src/core/EntrySearcher.h @@ -31,14 +31,15 @@ class EntrySearcher explicit EntrySearcher(bool caseSensitive = false); QList search(const QString& searchString, const Group* baseGroup, bool forceSearch = false); + QList repeat(const Group* baseGroup, bool forceSearch = false); + QList searchEntries(const QString& searchString, const QList& entries); + QList repeatEntries(const QList& entries); void setCaseSensitive(bool state); bool isCaseSensitive(); private: - bool searchEntryImpl(const QString& searchString, Entry* entry); - enum class Field { Undefined, @@ -59,10 +60,12 @@ class EntrySearcher bool exclude; }; - QList> parseSearchTerms(const QString& searchString); + bool searchEntryImpl(Entry* entry); + void parseSearchTerms(const QString& searchString); bool m_caseSensitive; QRegularExpression m_termParser; + QList> m_searchTerms; friend class TestEntrySearcher; }; diff --git a/src/core/FileWatcher.cpp b/src/core/FileWatcher.cpp index d7056f9c76..64e86c3fa2 100644 --- a/src/core/FileWatcher.cpp +++ b/src/core/FileWatcher.cpp @@ -123,6 +123,7 @@ BulkFileWatcher::BulkFileWatcher(QObject* parent) connect(&m_fileWatchUnblockTimer, SIGNAL(timeout()), this, SLOT(observeFileChanges())); connect(&m_pendingSignalsTimer, SIGNAL(timeout()), this, SLOT(emitSignals())); m_fileWatchUnblockTimer.setSingleShot(true); + m_pendingSignalsTimer.setSingleShot(true); } void BulkFileWatcher::clear() diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index 96be7dc1e9..1558466406 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -251,7 +251,11 @@ QSharedPointer DatabaseOpenWidget::databaseKey() #ifdef WITH_XC_TOUCHID // check if TouchID is available and enabled for unlocking the database - if (m_ui->checkTouchID->isChecked() && TouchID::getInstance().isAvailable() && masterKey->isEmpty()) { + if (m_ui->checkTouchID->isChecked() && TouchID::getInstance().isAvailable() + && m_ui->editPassword->text().isEmpty()) { + // clear empty password from composite key + masterKey->clear(); + // try to get, decrypt and use PasswordKey QSharedPointer passwordKey = TouchID::getInstance().getKey(m_filename); if (passwordKey != NULL) { diff --git a/src/gui/EditWidget.cpp b/src/gui/EditWidget.cpp index 6a57659333..be7ea01df8 100644 --- a/src/gui/EditWidget.cpp +++ b/src/gui/EditWidget.cpp @@ -59,6 +59,7 @@ void EditWidget::addPage(const QString& labelText, const QIcon& icon, QWidget* w */ auto* scrollArea = new QScrollArea(m_ui->stackedWidget); scrollArea->setFrameShape(QFrame::NoFrame); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollArea->setWidget(widget); scrollArea->setWidgetResizable(true); m_ui->stackedWidget->addWidget(scrollArea); diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index 990e480ee2..f99fd7b5db 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -65,7 +65,6 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent) connect(m_ui->deleteButton, SIGNAL(clicked()), SLOT(removeCustomIcon())); connect(m_ui->faviconButton, SIGNAL(clicked()), SLOT(downloadFavicon())); - connect(m_ui->defaultIconsRadio, SIGNAL(toggled(bool)), this, SIGNAL(widgetUpdated())); connect(m_ui->defaultIconsRadio, SIGNAL(toggled(bool)), this, SIGNAL(widgetUpdated())); connect(m_ui->defaultIconsView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SIGNAL(widgetUpdated())); diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index c85dbdfa01..d71b3e39e4 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -142,6 +142,7 @@ void EditEntryWidget::setupMain() connect(m_mainUi->togglePasswordGeneratorButton, SIGNAL(toggled(bool)), SLOT(togglePasswordGeneratorButton(bool))); #ifdef WITH_XC_NETWORKING connect(m_mainUi->fetchFaviconButton, SIGNAL(clicked()), m_iconsWidget, SLOT(downloadFavicon())); + connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), m_iconsWidget, SLOT(setUrl(QString))); #endif connect(m_mainUi->expireCheck, SIGNAL(toggled(bool)), m_mainUi->expireDatePicker, SLOT(setEnabled(bool))); connect(m_mainUi->notesEnabled, SIGNAL(toggled(bool)), this, SLOT(toggleHideNotes(bool))); @@ -764,7 +765,6 @@ void EditEntryWidget::setForms(Entry* entry, bool restore) iconStruct.uuid = entry->iconUuid(); iconStruct.number = entry->iconNumber(); m_iconsWidget->load(entry->uuid(), m_db, iconStruct, entry->webUrl()); - connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), m_iconsWidget, SLOT(setUrl(QString))); m_autoTypeUi->enableButton->setChecked(entry->autoTypeEnabled()); if (entry->defaultAutoTypeSequence().isEmpty()) { diff --git a/tests/TestEntrySearcher.cpp b/tests/TestEntrySearcher.cpp index 8128dd36e9..e949b97b8c 100644 --- a/tests/TestEntrySearcher.cpp +++ b/tests/TestEntrySearcher.cpp @@ -177,8 +177,8 @@ void TestEntrySearcher::testAllAttributesAreSearched() void TestEntrySearcher::testSearchTermParser() { // Test standard search terms - auto terms = - m_entrySearcher.parseSearchTerms("-test \"quoted \\\"string\\\"\" user:user pass:\"test me\" noquote "); + m_entrySearcher.parseSearchTerms("-test \"quoted \\\"string\\\"\" user:user pass:\"test me\" noquote "); + auto terms = m_entrySearcher.m_searchTerms; QCOMPARE(terms.length(), 5); @@ -200,7 +200,8 @@ void TestEntrySearcher::testSearchTermParser() QCOMPARE(terms[4]->word, QString("noquote")); // Test wildcard and regex search terms - terms = m_entrySearcher.parseSearchTerms("+url:*.google.com *user:\\d+\\w{2}"); + m_entrySearcher.parseSearchTerms("+url:*.google.com *user:\\d+\\w{2}"); + terms = m_entrySearcher.m_searchTerms; QCOMPARE(terms.length(), 2);