From 6b6c109903fa5364ab91afd5ef57a10b87ac8eb9 Mon Sep 17 00:00:00 2001 From: Florian Geyer Date: Sat, 12 May 2012 13:22:41 +0200 Subject: [PATCH] Add search. Refs #24 --- src/core/Entry.cpp | 11 +++- src/core/Entry.h | 1 + src/core/Group.cpp | 42 +++++++++++++ src/core/Group.h | 6 +- src/gui/DatabaseWidget.cpp | 27 ++++++++- src/gui/DatabaseWidget.h | 4 ++ src/gui/EntryModel.cpp | 121 +++++++++++++++++++++++++++++-------- src/gui/EntryModel.h | 11 ++++ src/gui/EntryView.cpp | 18 ++++++ src/gui/EntryView.h | 3 + src/gui/GroupView.cpp | 7 ++- tests/TestEntryModel.cpp | 4 +- tests/gui/TestGui.cpp | 4 +- 13 files changed, 224 insertions(+), 35 deletions(-) diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 6cbf8211..9c6352cd 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -465,8 +465,9 @@ void Entry::setGroup(Group* group) } } - group->addEntry(this); m_group = group; + group->addEntry(this); + QObject::setParent(group); if (m_updateTimeinfo) { @@ -488,3 +489,11 @@ const Database* Entry::database() const return 0; } } + +bool Entry::match(const QString& searchTerm, Qt::CaseSensitivity caseSensitivity) +{ + return title().contains(searchTerm, caseSensitivity) || + username().contains(searchTerm, caseSensitivity) || + url().contains(searchTerm, caseSensitivity) || + notes().contains(searchTerm, caseSensitivity); +} diff --git a/src/core/Entry.h b/src/core/Entry.h index e51b7e95..212f7f73 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -126,6 +126,7 @@ public: void setGroup(Group* group); void setUpdateTimeinfo(bool value); + bool match(const QString &searchTerm, Qt::CaseSensitivity caseSensitivity); Q_SIGNALS: /** diff --git a/src/core/Group.cpp b/src/core/Group.cpp index 7a2851a3..59e29bc3 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -487,3 +487,45 @@ void Group::recCreateDelObjects() m_db->addDeletedObject(m_uuid); } } + +QList Group::search(const QString& searchTerm, Qt::CaseSensitivity caseSensitivity, + bool resolveInherit) +{ + QList searchResult; + if (includeInSearch(resolveInherit)) { + Q_FOREACH (Entry* entry, m_entries) { + if (entry->match(searchTerm, caseSensitivity)) { + searchResult.append(entry); + } + } + Q_FOREACH (Group* group, m_children) { + searchResult.append(group->search(searchTerm, caseSensitivity, false)); + } + } + return searchResult; +} + +bool Group::includeInSearch(bool resolveInherit) +{ + switch (m_searchingEnabled) { + case Inherit: + if (!m_parent) { + return true; + } + else { + if (resolveInherit) { + return m_parent->includeInSearch(true); + } + else { + return true; + } + } + case Enable: + return true; + case Disable: + return false; + default: + Q_ASSERT(false); + return false; + } +} diff --git a/src/core/Group.h b/src/core/Group.h index ebd77aa9..52914152 100644 --- a/src/core/Group.h +++ b/src/core/Group.h @@ -76,8 +76,10 @@ public: QList entries(); const QList& entries() const; QList entriesRecursive(bool includeHistoryItems = false) const; - QList groupsRecursive(bool includeSelf) const; + QList groupsRecursive(bool includeSelf) const; + QList search(const QString& searchTerm, Qt::CaseSensitivity caseSensitivity, + bool resolveInherit = true); Q_SIGNALS: void dataChanged(Group* group); @@ -136,6 +138,8 @@ private: friend void Database::setRootGroup(Group* group); friend Entry::~Entry(); friend void Entry::setGroup(Group* group); + + bool includeInSearch(bool resolveInherit); }; #endif // KEEPASSX_GROUP_H diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index e9fbaf46..d71ba6fe 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "core/Metadata.h" @@ -53,12 +54,22 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) policy.setHorizontalStretch(30); m_groupView->setSizePolicy(policy); - policy = m_entryView->sizePolicy(); + QWidget* widget = new QWidget(); + policy = widget->sizePolicy(); policy.setHorizontalStretch(70); - m_entryView->setSizePolicy(policy); + widget->setSizePolicy(policy); splitter->addWidget(m_groupView); - splitter->addWidget(m_entryView); + + QVBoxLayout* vLayout = new QVBoxLayout(); + QHBoxLayout* hLayout = new QHBoxLayout(); + hLayout->addWidget(new QLabel("Find:")); + m_searchEdit = new QLineEdit(); + hLayout->addWidget(m_searchEdit); + vLayout->addLayout(hLayout); + vLayout->addWidget(m_entryView); + widget->setLayout(vLayout); + splitter->addWidget(widget); layout->addWidget(splitter); m_mainWidget->setLayout(layout); @@ -87,6 +98,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) connect(m_changeMasterKeyWidget, SIGNAL(editFinished(bool)), SLOT(updateMasterKey(bool))); connect(m_databaseSettingsWidget, SIGNAL(editFinished(bool)), SLOT(updateSettings(bool))); connect(this, SIGNAL(currentChanged(int)), this, SLOT(emitCurrentModeChanged())); + connect(m_searchEdit, SIGNAL(returnPressed()), this, SLOT(search())); setCurrentIndex(0); } @@ -311,6 +323,15 @@ void DatabaseWidget::switchToDatabaseSettings() setCurrentIndex(4); } +void DatabaseWidget::search() +{ + Group* searchGroup = m_db->rootGroup(); + QList searchResult = searchGroup->search(m_searchEdit->text(), Qt::CaseInsensitive); + + m_groupView->setCurrentIndex(QModelIndex()); + m_entryView->search(searchResult); +} + bool DatabaseWidget::dbHasKey() { return m_db->hasKey(); diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 16a64047..4128cdcb 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -20,6 +20,8 @@ #include +class QLineEdit; + class ChangeMasterKeyWidget; class DatabaseSettingsWidget; class Database; @@ -64,6 +66,7 @@ public Q_SLOTS: void switchToGroupEdit(); void switchToMasterKeyChange(); void switchToDatabaseSettings(); + void search(); private Q_SLOTS: void switchToView(bool accepted); @@ -86,6 +89,7 @@ private: Group* m_newGroup; Entry* m_newEntry; Group* m_newParent; + QLineEdit* m_searchEdit; }; #endif // KEEPASSX_DATABASEWIDGET_H diff --git a/src/gui/EntryModel.cpp b/src/gui/EntryModel.cpp index cb30f406..7cd48648 100644 --- a/src/gui/EntryModel.cpp +++ b/src/gui/EntryModel.cpp @@ -31,45 +31,67 @@ EntryModel::EntryModel(QObject* parent) Entry* EntryModel::entryFromIndex(const QModelIndex& index) const { - Q_ASSERT(index.isValid() && index.row() < m_group->entries().size()); - return m_group->entries().at(index.row()); + Q_ASSERT(index.isValid() && index.row() < m_entries.size()); + return m_entries.at(index.row()); } QModelIndex EntryModel::indexFromEntry(Entry* entry) const { - int row = m_group->entries().indexOf(entry); + int row = m_entries.indexOf(entry); Q_ASSERT(row != -1); return index(row, 0); } void EntryModel::setGroup(Group* group) { - if (group == m_group) { + if (!group || group == m_group) { return; } beginResetModel(); - if (m_group) { - disconnect(m_group, 0, this, 0); - } + severConnections(); + m_group = group; - connect(group, SIGNAL(entryAboutToAdd(Entry*)), SLOT(entryAboutToAdd(Entry*))); - connect(group, SIGNAL(entryAdded()), SLOT(entryAdded())); - connect(group, SIGNAL(entryAboutToRemove(Entry*)), SLOT(entryAboutToRemove(Entry*))); - connect(group, SIGNAL(entryRemoved()), SLOT(entryRemoved())); - connect(group, SIGNAL(entryDataChanged(Entry*)), SLOT(entryDataChanged(Entry*))); + m_entries = group->entries(); + + makeConnections(group); endResetModel(); + Q_EMIT switchedToView(); +} + +void EntryModel::setEntries(QList entries) +{ + beginResetModel(); + + severConnections(); + + m_group = 0; + m_allGroups.clear(); + m_entries = entries; + + if (entries.count() > 0) { + m_allGroups = entries.at(0)->group()->database()->rootGroup()->groupsRecursive(true); + } + + QListIterator iGroups(m_allGroups); + while (iGroups.hasNext()) { + const Group* group = iGroups.next(); + makeConnections(group); + } + + endResetModel(); + Q_EMIT switchedToSearch(); } int EntryModel::rowCount(const QModelIndex& parent) const { - if (!m_group || parent.isValid()) { + if (parent.isValid()) { return 0; } else { - return m_group->entries().size(); + return m_entries.size(); } } @@ -77,7 +99,7 @@ int EntryModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); - return 3; + return 4; } QVariant EntryModel::data(const QModelIndex& index, int role) const @@ -91,15 +113,26 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const if (role == Qt::DisplayRole) { switch (index.column()) { case 0: - return entry->title(); + if (entry->group()) { + return entry->group()->name(); + } case 1: - return entry->username(); + return entry->title(); case 2: + return entry->username(); + case 3: return entry->url(); } } - else if ((role == Qt::DecorationRole) && (index.column() == 0)) { - return entry->iconPixmap(); + else if (role == Qt::DecorationRole) { + switch (index.column()) { + case 0: + if (entry->group()) { + return entry->group()->iconPixmap(); + } + case 1: + return entry->iconPixmap(); + } } return QVariant(); @@ -109,10 +142,12 @@ QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int ro if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case 0: - return tr("Title"); + return tr("Group"); case 1: - return tr("Username"); + return tr("Title"); case 2: + return tr("Username"); + case 3: return tr("URL"); } } @@ -156,7 +191,8 @@ QMimeData* EntryModel::mimeData(const QModelIndexList& indexes) const if (!indexes[i].isValid()) { continue; } - stream << m_group->database()->uuid() << entryFromIndex(indexes[i])->uuid(); + Entry* entry = entryFromIndex(indexes[i]); + stream << entry->group()->database()->uuid() << entry->uuid(); } data->setData(mimeTypes().first(), encoded); @@ -167,26 +203,61 @@ void EntryModel::entryAboutToAdd(Entry* entry) { Q_UNUSED(entry); - beginInsertRows(QModelIndex(), m_group->entries().size(), m_group->entries().size()); + beginInsertRows(QModelIndex(), m_entries.size(), m_entries.size()); + if (!m_group) { + m_entries.append(entry); + } } void EntryModel::entryAdded() { + if (m_group) { + m_entries = m_group->entries(); + } endInsertRows(); } void EntryModel::entryAboutToRemove(Entry* entry) { - beginRemoveRows(QModelIndex(), m_group->entries().indexOf(entry), m_group->entries().indexOf(entry)); + beginRemoveRows(QModelIndex(), m_entries.indexOf(entry), m_entries.indexOf(entry)); + if (!m_group) { + m_entries.removeAll(entry); + } } void EntryModel::entryRemoved() { + if (m_group) { + m_entries = m_group->entries(); + } + endRemoveRows(); } void EntryModel::entryDataChanged(Entry* entry) { - int row = m_group->entries().indexOf(entry); + int row = m_entries.indexOf(entry); Q_EMIT dataChanged(index(row, 0), index(row, columnCount()-1)); } + +void EntryModel::severConnections() +{ + if (m_group) { + disconnect(m_group, 0, this, 0); + } + + QListIterator i(m_allGroups); + while (i.hasNext()) { + const Group* group = i.next(); + disconnect(group, 0, this, 0); + } +} + +void EntryModel::makeConnections(const Group *group) +{ + connect(group, SIGNAL(entryAboutToAdd(Entry*)), SLOT(entryAboutToAdd(Entry*))); + connect(group, SIGNAL(entryAdded()), SLOT(entryAdded())); + connect(group, SIGNAL(entryAboutToRemove(Entry*)), SLOT(entryAboutToRemove(Entry*))); + connect(group, SIGNAL(entryRemoved()), SLOT(entryRemoved())); + connect(group, SIGNAL(entryDataChanged(Entry*)), SLOT(entryDataChanged(Entry*))); +} diff --git a/src/gui/EntryModel.h b/src/gui/EntryModel.h index fd44f96a..69bd3cc8 100644 --- a/src/gui/EntryModel.h +++ b/src/gui/EntryModel.h @@ -41,6 +41,12 @@ public: QStringList mimeTypes() const; QMimeData* mimeData(const QModelIndexList& indexes) const; + void setEntries(QList entries); + +Q_SIGNALS: + void switchedToSearch(); + void switchedToView(); + public Q_SLOTS: void setGroup(Group* group); @@ -53,6 +59,11 @@ private Q_SLOTS: private: Group* m_group; + QList m_entries; + QList m_allGroups; + + void severConnections(); + void makeConnections(const Group* group); }; #endif // KEEPASSX_ENTRYMODEL_H diff --git a/src/gui/EntryView.cpp b/src/gui/EntryView.cpp index 47055179..4750ae83 100644 --- a/src/gui/EntryView.cpp +++ b/src/gui/EntryView.cpp @@ -40,6 +40,8 @@ EntryView::EntryView(QWidget* parent) connect(this, SIGNAL(activated(const QModelIndex&)), SLOT(emitEntryActivated(const QModelIndex&))); connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SIGNAL(entrySelectionChanged())); + connect(m_model, SIGNAL(switchedToSearch()), this, SLOT(switchToSearch())); + connect(m_model, SIGNAL(switchedToView()), this, SLOT(switchToView())); sortByColumn(0, Qt::AscendingOrder); } @@ -50,6 +52,12 @@ void EntryView::setGroup(Group* group) Q_EMIT entrySelectionChanged(); } +void EntryView::search(QList entries) +{ + m_model->setEntries(entries); + Q_EMIT entrySelectionChanged(); +} + void EntryView::emitEntryActivated(const QModelIndex& index) { Q_EMIT entryActivated(entryFromIndex(index)); @@ -86,3 +94,13 @@ Entry* EntryView::entryFromIndex(const QModelIndex& index) return 0; } } + +void EntryView::switchToSearch() +{ + showColumn(0); +} + +void EntryView::switchToView() +{ + hideColumn(0); +} diff --git a/src/gui/EntryView.h b/src/gui/EntryView.h index 931eca21..abacfc05 100644 --- a/src/gui/EntryView.h +++ b/src/gui/EntryView.h @@ -36,12 +36,15 @@ public: bool isSingleEntrySelected(); void setCurrentEntry(Entry* entry); Entry* entryFromIndex(const QModelIndex& index); + void search(QList entries); public Q_SLOTS: void setGroup(Group* group); private Q_SLOTS: void emitEntryActivated(const QModelIndex& index); + void switchToSearch(); + void switchToView(); Q_SIGNALS: void entryActivated(Entry* entry); diff --git a/src/gui/GroupView.cpp b/src/gui/GroupView.cpp index dc5c76bf..1deee16c 100644 --- a/src/gui/GroupView.cpp +++ b/src/gui/GroupView.cpp @@ -63,7 +63,12 @@ void GroupView::dragMoveEvent(QDragMoveEvent* event) Group* GroupView::currentGroup() { - return m_model->groupFromIndex(currentIndex()); + if (currentIndex() == QModelIndex()) { + return 0; + } + else { + return m_model->groupFromIndex(currentIndex()); + } } void GroupView::expandedChanged(const QModelIndex& index) diff --git a/tests/TestEntryModel.cpp b/tests/TestEntryModel.cpp index e04b0445..63a2ada4 100644 --- a/tests/TestEntryModel.cpp +++ b/tests/TestEntryModel.cpp @@ -62,8 +62,8 @@ void TestEntryModel::test() entry1->setTitle("changed"); QCOMPARE(spyDataChanged.count(), 1); - QModelIndex index1 = model->index(0, 0); - QModelIndex index2 = model->index(1, 0); + QModelIndex index1 = model->index(0, 1); + QModelIndex index2 = model->index(1, 1); QCOMPARE(model->data(index1).toString(), entry1->title()); QCOMPARE(model->data(index2).toString(), entry2->title()); diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index f72d2470..b4f1e7bd 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -74,7 +74,7 @@ void TestGui::testEditEntry() DatabaseTabWidget* tabWidget = m_mainWindow->findChild("tabWidget"); DatabaseWidget* dbWidget = tabWidget->currentDatabaseWidget(); EntryView* entryView = dbWidget->findChild("entryView"); - QModelIndex item = entryView->model()->index(0, 0); + QModelIndex item = entryView->model()->index(0, 1); QRect itemRect = entryView->visualRect(item); QTest::mouseClick(entryView->viewport(), Qt::LeftButton, Qt::NoModifier, itemRect.center()); QTest::qWait(20); @@ -126,7 +126,7 @@ void TestGui::testAddEntry() QTest::qWait(20); QCOMPARE(dbWidget->currentMode(), DatabaseWidget::ViewMode); - QModelIndex item = entryView->model()->index(1, 0); + QModelIndex item = entryView->model()->index(1, 1); Entry* entry = entryView->entryFromIndex(item); QCOMPARE(entry->title(), QString("test"));