From 34a7ba4477b1d965807786f90a81583265c694bf Mon Sep 17 00:00:00 2001 From: Fonic Date: Tue, 19 Dec 2017 10:47:51 +0100 Subject: [PATCH 1/9] Add additional columns to entry view table Add additional columns 'Password', 'Notes', 'Expires', 'Created', 'Modified', 'Accessed' and 'Attachments' to entry view table: - add columns themselves - add display role data providers - introduce/apply sort role - add sort role data providers - add settings to display usernames/passwords visible/hidden - minor addition to EntryModel::columnCount() as advised by Qt documentation --- src/gui/entry/EntryModel.cpp | 249 ++++++++++++++++++++++++++++++++++- src/gui/entry/EntryModel.h | 53 +++++++- src/gui/entry/EntryView.cpp | 7 + 3 files changed, 304 insertions(+), 5 deletions(-) diff --git a/src/gui/entry/EntryModel.cpp b/src/gui/entry/EntryModel.cpp index 4d3578069..b042c255c 100644 --- a/src/gui/entry/EntryModel.cpp +++ b/src/gui/entry/EntryModel.cpp @@ -20,6 +20,12 @@ #include #include #include +/** + * @author Fonic + * Add include required for additional columns 'Expires', 'Created', 'Modified' + * and 'Accessed' + */ +#include #include "core/DatabaseIcons.h" #include "core/Entry.h" @@ -27,9 +33,36 @@ #include "core/Group.h" #include "core/Metadata.h" +/** + * @author Fonic + * Define constant string used to display hidden content in columns 'Username' + * and 'Password' + * + * TODO: + * Decide which of the proposed options should be used (stars, bullet, black + * circle) + */ +//const QString EntryModel::HiddenContent("******"); +//const QString EntryModel::HiddenContent(QString(QChar(0x2022)).repeated(6)); +const QString EntryModel::HiddenContent(QString(QChar(0x25CF)).repeated(6)); + +/** + * @author Fonic + * Define date format used to display dates in columns 'Expires', 'Created', + * 'Modified' and 'Accessed' + */ +const Qt::DateFormat EntryModel::DateFormat = Qt::DefaultLocaleShortDate; + +/** + * @author Fonic + * Initialize 'Hide Usernames' and 'Hide Passwords' settings using sane + * defaults (usernames visible, passwords hidden) + */ EntryModel::EntryModel(QObject* parent) : QAbstractTableModel(parent) , m_group(nullptr) + , m_hideUsernames(false) + , m_hidePasswords(true) { } @@ -116,9 +149,18 @@ int EntryModel::rowCount(const QModelIndex& parent) const int EntryModel::columnCount(const QModelIndex& parent) const { - Q_UNUSED(parent); - - return 4; + /** + * @author Fonic + * Change column count to include additional columns 'Password', 'Notes', + * 'Expires', 'Created', 'Modified', 'Accessed' and 'Attachments'. Also, + * return 0 when parent is valid as advised by Qt documentation + */ + if (parent.isValid()) { + return 0; + } + else { + return 11; + } } QVariant EntryModel::data(const QModelIndex& index, int role) const @@ -130,6 +172,25 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const Entry* entry = entryFromIndex(index); EntryAttributes* attr = entry->attributes(); + /** + * @author Fonic + * + * Add display data providers for additional columns 'Password', 'Notes', + * 'Expires', 'Created', 'Modified', 'Accessed' and 'Attachments' + * + * Add ability to display usernames and passwords hidden or visible + * depending on current state of 'Hide Usernames' and 'Hide Passwords' + * settings + * + * TODO: + * Decide which of the additional columns should expand placeholders + * -> code added where applicable, but currently commented out + * + * Check what attr->isReference() does and if it applies to any of the + * additional columns + * -> code added for columns 'Password' and 'Notes', as EntryAttributes:: + * PasswordKey and EntryAttributes::NotesKey already existed + */ if (role == Qt::DisplayRole) { QString result; switch (index.column()) { @@ -145,17 +206,139 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const } return result; case Username: - result = entry->resolveMultiplePlaceholders(entry->username()); + /* + * Display usernames hidden or visible according to current state + * of 'Hide Usernames' setting + */ + if (m_hideUsernames) { + result = EntryModel::HiddenContent; + } + else { + //result = entry->username(); + result = entry->resolveMultiplePlaceholders(entry->username()); + } if (attr->isReference(EntryAttributes::UserNameKey)) { result.prepend(tr("Ref: ","Reference abbreviation")); } return result; + case Password: + /* + * Display passwords hidden or visible according to current state + * of 'Hide Passwords' setting + */ + if (m_hidePasswords) { + result = EntryModel::HiddenContent; + } + else { + //result = entry->resolveMultiplePlaceholders(entry->password()); + result = entry->password(); + } + if (attr->isReference(EntryAttributes::PasswordKey)) { + result.prepend(tr("Ref: ","Reference abbreviation")); + } + return result; case Url: + //result = entry->resolveMultiplePlaceholders(entry->displayUrl()); result = entry->displayUrl(); if (attr->isReference(EntryAttributes::URLKey)) { result.prepend(tr("Ref: ","Reference abbreviation")); } return result; + case Notes: + /* + * Display only first line of notes in simplified format like + * KeePassX does + */ + //result = entry->resolveMultiplePlaceholders(entry->notes().section("\n", 0, 0).simplified()); + result = entry->notes().section("\n", 0, 0).simplified(); + if (attr->isReference(EntryAttributes::NotesKey)) { + result.prepend(tr("Ref: ","Reference abbreviation")); + } + return result; + case Expires: + /* + * Display either date of expiry or 'Never' like KeePassX does + */ + result = entry->timeInfo().expires() ? entry->timeInfo().expiryTime().toLocalTime().toString(EntryModel::DateFormat) : tr("Never"); + return result; + case Created: + result = entry->timeInfo().creationTime().toLocalTime().toString(EntryModel::DateFormat); + return result; + case Modified: + result = entry->timeInfo().lastModificationTime().toLocalTime().toString(EntryModel::DateFormat); + return result; + case Accessed: + result = entry->timeInfo().lastAccessTime().toLocalTime().toString(EntryModel::DateFormat); + return result; + case Attachments: + /* + * Display comma-separated list of attachments + * + * TODO: + * 'entry->attachments()->keys().join()' works locally, yet it fails + * on GitHub/Travis CI, most likely due to an older Qt version, thus + * using loop for now (http://doc.qt.io/qt-5/qlist.html#more-members) + */ + //result = entry->resolveMultiplePlaceholders(entry->attachments()->keys().join(", ")); + //result = entry->attachments()->keys().join(", "); + + QList attachments = entry->attachments()->keys(); + for (int i=0; i < attachments.size(); i++) { + if (result.isEmpty()) { + result.append(attachments.at(i)); + } + else { + result.append(QString(", ") + attachments.at(i)); + } + } + //result = entry->resolveMultiplePlaceholders(result); + return result; + } + + } + /** + * @author Fonic + * + * Add sort data providers for columns 'Username' and 'Password', required + * for correct sorting even if displayed hidden (i.e. settings 'Hide User- + * names' and/or 'Hide Passwords' are enabled) + * + * Add sort data providers for columns 'Expires', 'Created', 'Modified' + * and 'Accessed', required for correct sorting of dates (without this, + * sorting would be based on string representation of dates, yielding un- + * desired results) + * + * NOTE: + * Qt::UserRole is used as sort role, using 'm_sortModel->setSortRole(Qt:: + * UserRole)' in EntryView.cpp, EntryView::EntryView() + */ + else if (role == Qt::UserRole) { + switch (index.column()) { + case Username: + //return entry->username(); + return entry->resolveMultiplePlaceholders(entry->username()); + case Password: + //return entry->resolveMultiplePlaceholders(entry->password()); + return entry->password(); + case Expires: + /* + * TODO: + * Is there any better way to return a QDateTime representing + * 'Never' / infinity / end of all time? + */ + return entry->timeInfo().expires() ? entry->timeInfo().expiryTime() : QDateTime(QDate(9999, 1, 1)); + case Created: + return entry->timeInfo().creationTime(); + case Modified: + return entry->timeInfo().lastModificationTime(); + case Accessed: + return entry->timeInfo().lastAccessTime(); + default: + /* + * For all other columns, simply use data provided by Qt::Display- + * Role for sorting + */ + return data(index, Qt::DisplayRole); } } else if (role == Qt::DecorationRole) { @@ -190,8 +373,14 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const return QVariant(); } + QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int role) const { + /** + * @author Fonic + * Add captions for additional columns 'Password', 'Notes', 'Expires', + * 'Created', 'Modified', 'Accessed' and 'Attachments' + */ if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case ParentGroup: @@ -200,8 +389,22 @@ QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int ro return tr("Title"); case Username: return tr("Username"); + case Password: + return tr("Password"); case Url: return tr("URL"); + case Notes: + return tr("Notes"); + case Expires: + return tr("Expires"); + case Created: + return tr("Created"); + case Modified: + return tr("Modified"); + case Accessed: + return tr("Accessed"); + case Attachments: + return tr("Attachments"); } } @@ -337,3 +540,41 @@ void EntryModel::makeConnections(const Group* group) connect(group, SIGNAL(entryRemoved(Entry*)), SLOT(entryRemoved())); connect(group, SIGNAL(entryDataChanged(Entry*)), SLOT(entryDataChanged(Entry*))); } + +/** + * @author Fonic + * Get current state of 'Hide Usernames' setting + */ +bool EntryModel::hideUsernames() const +{ + return m_hideUsernames; +} + +/** + * @author Fonic + * Set state of 'Hide Usernames' setting and signal change + */ +void EntryModel::setHideUsernames(const bool hide) +{ + m_hideUsernames = hide; + emit hideUsernamesChanged(); +} + +/** + * @author Fonic + * Get current state of 'Hide Passwords' setting + */ +bool EntryModel::hidePasswords() const +{ + return m_hidePasswords; +} + +/** + * @author Fonic + * Set state of 'Hide Passwords' setting and signal change + */ +void EntryModel::setHidePasswords(const bool hide) +{ + m_hidePasswords = hide; + emit hidePasswordsChanged(); +} diff --git a/src/gui/entry/EntryModel.h b/src/gui/entry/EntryModel.h index d12982d83..5bb58c9ad 100644 --- a/src/gui/entry/EntryModel.h +++ b/src/gui/entry/EntryModel.h @@ -28,12 +28,24 @@ class EntryModel : public QAbstractTableModel Q_OBJECT public: + /** + * @author Fonic + * Add entries for additional columns 'Password', 'Notes', 'Expires', + * 'Created', 'Modified', 'Accessed' and 'Attachments' + */ enum ModelColumn { ParentGroup = 0, Title = 1, Username = 2, - Url = 3 + Password = 3, + Url = 4, + Notes = 5, + Expires = 6, + Created = 7, + Modified = 8, + Accessed = 9, + Attachments = 10 }; explicit EntryModel(QObject* parent = nullptr); @@ -52,9 +64,26 @@ public: void setEntryList(const QList& entries); + /** + * @author Fonic + * Methods to get/set state of 'Hide Usernames' and 'Hide Passwords' + * settings + */ + bool hideUsernames() const; + void setHideUsernames(const bool hide); + bool hidePasswords() const; + void setHidePasswords(const bool hide); + signals: void switchedToEntryListMode(); void switchedToGroupMode(); + /** + * @author Fonic + * Signals to notify about state changes of 'Hide Usernames' and 'Hide + * Passwords' settings + */ + void hideUsernamesChanged(); + void hidePasswordsChanged(); public slots: void setGroup(Group* group); @@ -74,6 +103,28 @@ private: QList m_entries; QList m_orgEntries; QList m_allGroups; + + /** + * @author Fonic + * Properties to store state of 'Hide Usernames' and 'Hide Passwords' + * settings + */ + bool m_hideUsernames; + bool m_hidePasswords; + + /** + * @author Fonic + * Constant string used to display hidden content in columns 'Username' + * and 'Password' + */ + static const QString HiddenContent; + + /** + * @author Fonic + * Date format used to display dates in columns 'Expires', 'Created', + * 'Modified' and 'Accessed' + */ + static const Qt::DateFormat DateFormat; }; #endif // KEEPASSX_ENTRYMODEL_H diff --git a/src/gui/entry/EntryView.cpp b/src/gui/entry/EntryView.cpp index ac8e66782..6d2c88279 100644 --- a/src/gui/entry/EntryView.cpp +++ b/src/gui/entry/EntryView.cpp @@ -32,6 +32,13 @@ EntryView::EntryView(QWidget* parent) m_sortModel->setDynamicSortFilter(true); m_sortModel->setSortLocaleAware(true); m_sortModel->setSortCaseSensitivity(Qt::CaseInsensitive); + /** + * @author Fonic + * Set Qt::UserRole as sort role + * -> refer to 'if (role == Qt::UserRole)', EntryModel.cpp, EntryModel:: + * data() for details + */ + m_sortModel->setSortRole(Qt::UserRole); QTreeView::setModel(m_sortModel); setUniformRowHeights(true); From 268035ff9e81d6d4689931346decb73d31d1e4f1 Mon Sep 17 00:00:00 2001 From: Fonic Date: Tue, 19 Dec 2017 11:10:32 +0100 Subject: [PATCH 2/9] Update entry model test to account for additional columns Update comparison values of modelProxy->columnCount() to account for additional columns 'Password', 'Notes', 'Expires', 'Created', 'Modified', 'Accessed' and 'Attachments' --- tests/TestEntryModel.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/TestEntryModel.cpp b/tests/TestEntryModel.cpp index ad7c9060a..49ae3b0d1 100644 --- a/tests/TestEntryModel.cpp +++ b/tests/TestEntryModel.cpp @@ -288,9 +288,15 @@ void TestEntryModel::testProxyModel() modelSource->setGroup(db->rootGroup()); + /** + * @author Fonic + * Update comparison value of modelProxy->columnCount() to account for + * additional columns 'Password', 'Notes', 'Expires', 'Created', 'Modified', + * 'Accessed' and 'Attachments' + */ QSignalSpy spyColumnRemove(modelProxy, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int))); modelProxy->hideColumn(0, true); - QCOMPARE(modelProxy->columnCount(), 3); + QCOMPARE(modelProxy->columnCount(), 10); QVERIFY(spyColumnRemove.size() >= 1); int oldSpyColumnRemoveSize = spyColumnRemove.size(); @@ -304,9 +310,15 @@ void TestEntryModel::testProxyModel() entryList << entry; modelSource->setEntryList(entryList); + /** + * @author Fonic + * Update comparison value of modelProxy->columnCount() to account for + * additional columns 'Password', 'Notes', 'Expires', 'Created', 'Modified', + * 'Accessed' and 'Attachments' + */ QSignalSpy spyColumnInsert(modelProxy, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int))); modelProxy->hideColumn(0, false); - QCOMPARE(modelProxy->columnCount(), 4); + QCOMPARE(modelProxy->columnCount(), 11); QVERIFY(spyColumnInsert.size() >= 1); int oldSpyColumnInsertSize = spyColumnInsert.size(); From 31d73626e5ebfdc0115b9fd745d6af760608f539 Mon Sep 17 00:00:00 2001 From: Fonic Date: Thu, 21 Dec 2017 10:01:47 +0100 Subject: [PATCH 3/9] Add header context menu to entry view table Add header context menu to entry view table (accessible using right click on header), providing: - Actions to toggle 'Hide Usernames' / 'Hide Passwords' - Actions to toggle column visibility - Actions to resize columns - Action to reset view to defaults --- src/gui/entry/EntryModel.cpp | 18 +++ src/gui/entry/EntryModel.h | 8 ++ src/gui/entry/EntryView.cpp | 268 ++++++++++++++++++++++++++++++++++- src/gui/entry/EntryView.h | 24 ++++ 4 files changed, 313 insertions(+), 5 deletions(-) diff --git a/src/gui/entry/EntryModel.cpp b/src/gui/entry/EntryModel.cpp index b042c255c..e03b0f791 100644 --- a/src/gui/entry/EntryModel.cpp +++ b/src/gui/entry/EntryModel.cpp @@ -578,3 +578,21 @@ void EntryModel::setHidePasswords(const bool hide) m_hidePasswords = hide; emit hidePasswordsChanged(); } + +/** + * @author Fonic + * Toggle state of 'Hide Usernames' setting + */ +void EntryModel::toggleHideUsernames(const bool hide) +{ + setHideUsernames(hide); +} + +/** + * @author Fonic + * Toggle state of 'Hide Passwords' setting + */ +void EntryModel::toggleHidePasswords(const bool hide) +{ + setHidePasswords(hide); +} diff --git a/src/gui/entry/EntryModel.h b/src/gui/entry/EntryModel.h index 5bb58c9ad..5b4f5e734 100644 --- a/src/gui/entry/EntryModel.h +++ b/src/gui/entry/EntryModel.h @@ -77,6 +77,7 @@ public: signals: void switchedToEntryListMode(); void switchedToGroupMode(); + /** * @author Fonic * Signals to notify about state changes of 'Hide Usernames' and 'Hide @@ -88,6 +89,13 @@ signals: public slots: void setGroup(Group* group); + /** + * @author Fonic + * Slots to toggle state of 'Hide Usernames' and 'Hide Passwords' settings + */ + void toggleHideUsernames(const bool hide); + void toggleHidePasswords(const bool hide); + private slots: void entryAboutToAdd(Entry* entry); void entryAdded(Entry* entry); diff --git a/src/gui/entry/EntryView.cpp b/src/gui/entry/EntryView.cpp index 6d2c88279..6657818d2 100644 --- a/src/gui/entry/EntryView.cpp +++ b/src/gui/entry/EntryView.cpp @@ -19,9 +19,26 @@ #include #include +/** + * @author Fonic + * Add include required for header context menu + */ +#include #include "gui/SortFilterHideProxyModel.h" +/** + * @author Fonic + * + * TODO NOTE: + * Currently, 'zombie' columns which are not hidden but have width == 0 + * (rendering them invisible) may appear. This is caused by DatabaseWidget + * StateSync. Corresponding checks/workarounds may be removed once sync + * code is updated accordingly + * -> relevant code pieces: if (header()->sectionSize(...) == 0) { ... } + * + */ + EntryView::EntryView(QWidget* parent) : QTreeView(parent) , m_model(new EntryModel(this)) @@ -47,7 +64,6 @@ EntryView::EntryView(QWidget* parent) setDragEnabled(true); setSortingEnabled(true); setSelectionMode(QAbstractItemView::ExtendedSelection); - header()->setDefaultSectionSize(150); // QAbstractItemView::startDrag() uses this property as the default drag action setDefaultDropAction(Qt::MoveAction); @@ -58,6 +74,69 @@ EntryView::EntryView(QWidget* parent) connect(m_model, SIGNAL(switchedToGroupMode()), SLOT(switchToGroupMode())); connect(this, SIGNAL(clicked(QModelIndex)), SLOT(emitEntryPressed(QModelIndex))); + + /** + * @author Fonic + * Create header context menu: + * - Actions to toggle state of 'Hide Usernames'/'Hide Passwords' settings + * - Actions to toggle column visibility, with each action carrying 'its' + * column index as data + * - Actions to resize columns + * - Action to reset view to defaults + */ + m_headerMenu = new QMenu(this); + m_headerMenu->setTitle(tr("Customize View")); + m_headerMenu->addSection(tr("Customize View")); + + m_hideUsernamesAction = m_headerMenu->addAction(tr("Hide Usernames"), m_model, SLOT(toggleHideUsernames(bool))); + m_hideUsernamesAction->setCheckable(true); + m_hidePasswordsAction = m_headerMenu->addAction(tr("Hide Passwords"), m_model, SLOT(toggleHidePasswords(bool))); + m_hidePasswordsAction->setCheckable(true); + m_headerMenu->addSeparator(); + + m_columnActions = new QActionGroup(this); + m_columnActions->setExclusive(false); + for (int colidx = 1; colidx < header()->count(); colidx++) { + QString caption = m_model->headerData(colidx, Qt::Horizontal, Qt::DisplayRole).toString(); + QAction* action = m_headerMenu->addAction(caption); + action->setCheckable(true); + action->setData(colidx); + m_columnActions->addAction(action); + } + connect(m_columnActions, SIGNAL(triggered(QAction*)), this, SLOT(toggleColumnVisibility(QAction*))); + + m_headerMenu->addSeparator(); + m_headerMenu->addAction(tr("Fit to window"), this, SLOT(fitColumnsToWindow())); + m_headerMenu->addAction(tr("Fit to contents"), this, SLOT(fitColumnsToContents())); + m_headerMenu->addSeparator(); + m_headerMenu->addAction(tr("Reset to defaults"), this, SLOT(resetViewToDefaults())); + + /** + * @author Fonic + * Configure header: + * - Set default section size + * - Disable stretching of last section (interferes with fitting columns + * to window) + * - Associate with context menu + */ + header()->setDefaultSectionSize(100); + header()->setStretchLastSection(false); + header()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showHeaderMenu(QPoint))); + + /** + * @author Fonic + * Finalize setup by resetting view to defaults. Although not really + * necessary at this point, it makes sense in order to avoid duplicating + * code (sorting order, visibility of first column etc.) + * + * TODO: + * Not working as expected, columns will end up being very small, most + * likely due to EntryView not being sized properly at this time. Either + * find a way to make this work by analizing when/where EntryView is + * created or remove + */ + //resetViewToDefaults(); } void EntryView::keyPressEvent(QKeyEvent* event) @@ -153,22 +232,201 @@ Entry* EntryView::entryFromIndex(const QModelIndex& index) void EntryView::switchToEntryListMode() { - m_sortModel->hideColumn(0, false); + /** + * @author Fonic + * Use header()->showSection() instead of m_sortModel->hideColumn() as + * the latter messes up column indices, interfering with code relying on + * proper indices + */ + header()->showSection(EntryModel::ParentGroup); + if (header()->sectionSize(EntryModel::ParentGroup) == 0) { + header()->resizeSection(EntryModel::ParentGroup, header()->defaultSectionSize()); + } + /** + * @author Fonic + * Set sorting column and order (TODO: check what first two lines do, if + * they are actually necessary, if indices are still correct and if indices + * may be replaced by EntryModel::) + */ m_sortModel->sort(1, Qt::AscendingOrder); m_sortModel->sort(0, Qt::AscendingOrder); - sortByColumn(0, Qt::AscendingOrder); + sortByColumn(EntryModel::ParentGroup, Qt::AscendingOrder); m_inEntryListMode = true; } void EntryView::switchToGroupMode() { - m_sortModel->hideColumn(0, true); + /** + * @author Fonic + * Use header()->hideSection() instead of m_sortModel->hideColumn() as + * the latter messes up column indices, interfering with code relying on + * proper indices + */ + header()->hideSection(EntryModel::ParentGroup); + /** + * @author Fonic + * Set sorting column and order (TODO: check what first two lines do, if + * they are actually necessary, if indices are still correct and if indices + * may be replaced by EntryModel::) + */ m_sortModel->sort(-1, Qt::AscendingOrder); m_sortModel->sort(0, Qt::AscendingOrder); - sortByColumn(0, Qt::AscendingOrder); + sortByColumn(EntryModel::Title, Qt::AscendingOrder); m_inEntryListMode = false; } + +/** + * @author Fonic + * Sync checkable menu actions to current state and display header context + * menu at specified position + */ +void EntryView::showHeaderMenu(const QPoint& position) +{ + /* Sync checked state of menu actions to current state of view */ + m_hideUsernamesAction->setChecked(m_model->hideUsernames()); + m_hidePasswordsAction->setChecked(m_model->hidePasswords()); + foreach (QAction *action, m_columnActions->actions()) { + if (static_cast(action->data().type()) != QMetaType::Int) { + Q_ASSERT(false); + continue; + } + int colidx = action->data().toInt(); + bool hidden = header()->isSectionHidden(colidx) || (header()->sectionSize(colidx) == 0); + action->setChecked(!hidden); + } + + /* Display menu */ + m_headerMenu->popup(mapToGlobal(position)); +} + +/** + * @author Fonic + * Toggle visibility of column referenced by triggering action + */ +void EntryView::toggleColumnVisibility(QAction *action) +{ + /* + * Verify action carries a column index as data. Since QVariant.toInt() + * below will accept anything that's interpretable as int, perform a type + * check here to make sure data actually IS int + */ + if (static_cast(action->data().type()) != QMetaType::Int) { + Q_ASSERT(false); + return; + } + + /* + * Toggle column visibility. Visible columns will only be hidden if at + * least one visible column remains, as the table header will disappear + * entirely when all columns are hidden, rendering the context menu in- + * accessible + */ + int colidx = action->data().toInt(); + if (action->isChecked()) { + header()->showSection(colidx); + if (header()->sectionSize(colidx) == 0) { + header()->resizeSection(colidx, header()->defaultSectionSize()); + } + } + else { + if ((header()->count() - header()->hiddenSectionCount()) > 1) { + header()->hideSection(colidx); + } + else { + action->setChecked(true); + } + } +} + +/** + * @author Fonic + * Resize columns to fit all visible columns within the available space + * + * NOTE: + * If EntryView::resizeEvent() is overridden at some point in the future, + * its implementation MUST call the corresponding parent method using + * 'QTreeView::resizeEvent(event)'. Without this, fitting to window will + * be broken and/or work unreliably (stumbled upon during testing) + */ +void EntryView::fitColumnsToWindow() +{ + header()->resizeSections(QHeaderView::Stretch); +} + +/** + * @author Fonic + * Resize columns to fit current table contents, i.e. make all contents + * entirely visible + */ +void EntryView::fitColumnsToContents() +{ + /* Resize columns to fit contents */ + header()->resizeSections(QHeaderView::ResizeToContents); + + /* + * Determine total width of currently visible columns. If there is + * still some space available on the header, equally distribute it to + * visible columns and add remaining fraction to last visible column + */ + int width = 0; + for (int colidx = 0; colidx < header()->count(); colidx++) { + if (!header()->isSectionHidden(colidx)) { + width += header()->sectionSize(colidx); + } + } + int visible = header()->count() - header()->hiddenSectionCount(); + int avail = header()->width() - width; + if ((visible > 0) && (avail > 0)) { + int add = avail / visible; + width = 0; + int last = 0; + for (int colidx = 0; colidx < header()->count(); colidx++) { + if (!header()->isSectionHidden(colidx)) { + header()->resizeSection(colidx, header()->sectionSize(colidx) + add); + width += header()->sectionSize(colidx); + if (header()->visualIndex(colidx) > last) { + last = header()->visualIndex(colidx); + } + } + } + header()->resizeSection(header()->logicalIndex(last), header()->sectionSize(last) + (header()->width() - width)); + } +} + +/** + * @author Fonic + * Reset view to defaults + * + * NOTE: + * header()->saveState()/restoreState() could also be used for this, but + * testing showed that it complicates things more than it helps when trying + * to account for current list mode + */ +void EntryView::resetViewToDefaults() +{ + /* Reset state of 'Hide Usernames'/'Hide Passwords' settings */ + m_model->setHideUsernames(false); + m_model->setHidePasswords(true); + + /* Reset visibility, size and position of all columns */ + for (int colidx = 0; colidx < header()->count(); colidx++) { + header()->showSection(colidx); + header()->resizeSection(colidx, header()->defaultSectionSize()); + header()->moveSection(header()->visualIndex(colidx), colidx); + } + + /* Reenter current list mode (affects first column and sorting) */ + if (m_inEntryListMode) { + switchToEntryListMode(); + } + else { + switchToGroupMode(); + } + + /* Nicely fitting columns to window feels like a sane default */ + fitColumnsToWindow(); +} diff --git a/src/gui/entry/EntryView.h b/src/gui/entry/EntryView.h index 14c6b7ccc..2cadf0d82 100644 --- a/src/gui/entry/EntryView.h +++ b/src/gui/entry/EntryView.h @@ -26,6 +26,11 @@ class Entry; class EntryModel; class Group; class SortFilterHideProxyModel; +/** + * @author Fonic + * Add forward declaration for QActionGroup + */ +class QActionGroup; class EntryView : public QTreeView { @@ -59,10 +64,29 @@ private slots: void switchToEntryListMode(); void switchToGroupMode(); + /** + * @author Fonic + * Slots for header context menu and actions + */ + void showHeaderMenu(const QPoint& position); + void toggleColumnVisibility(QAction *action); + void fitColumnsToWindow(); + void fitColumnsToContents(); + void resetViewToDefaults(); + private: EntryModel* const m_model; SortFilterHideProxyModel* const m_sortModel; bool m_inEntryListMode; + + /** + * @author Fonic + * Properties to store header context menu and actions + */ + QMenu* m_headerMenu; + QAction* m_hideUsernamesAction; + QAction* m_hidePasswordsAction; + QActionGroup* m_columnActions; }; #endif // KEEPASSX_ENTRYVIEW_H From 66291ecc3861579382ddc4f7560620b8cb9109f8 Mon Sep 17 00:00:00 2001 From: Fonic Date: Thu, 21 Dec 2017 10:32:07 +0100 Subject: [PATCH 4/9] Update GUI test to account for changed column indices Update clicks within entry view referencing column indices to account for changed column indices due to new way of showing/hiding column Entry Model::ParentGroup. This column now has fixed index 0 wether it's shown or hidden, thus all indices need to be shifted by +1 when not in search mode --- tests/gui/TestGui.cpp | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index d1a251069..00b957241 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -686,6 +686,21 @@ void TestGui::testSearch() QTRY_COMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); } +/** + * @author Fonic + * Update clicks within entry view referencing column indices to account + * for changed column indices due to new way of showing/hiding column Entry + * Model::ParentGroup. This column now has fixed index 0 wether it's shown + * or hidden, thus all indices need to be shifted by +1 when not in search + * mode (which is the case within this entire method) + * + * Old: + * clickIndex(entryView->model()->index(row, column), button); + * + * New: + * clickIndex(entryView->model()->index(row, column+1), button); + * + */ void TestGui::testDeleteEntry() { // Add canned entries for consistent testing @@ -698,7 +713,7 @@ void TestGui::testDeleteEntry() QWidget* entryDeleteWidget = toolBar->widgetForAction(entryDeleteAction); QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); - clickIndex(entryView->model()->index(1, 0), entryView, Qt::LeftButton); + clickIndex(entryView->model()->index(1, 1), entryView, Qt::LeftButton); QVERIFY(entryDeleteWidget->isVisible()); QVERIFY(entryDeleteWidget->isEnabled()); QVERIFY(!m_db->metadata()->recycleBin()); @@ -709,8 +724,8 @@ void TestGui::testDeleteEntry() QCOMPARE(entryView->model()->rowCount(), 3); QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 1); - clickIndex(entryView->model()->index(1, 0), entryView, Qt::LeftButton); - clickIndex(entryView->model()->index(2, 0), entryView, Qt::LeftButton, Qt::ControlModifier); + clickIndex(entryView->model()->index(1, 1), entryView, Qt::LeftButton); + clickIndex(entryView->model()->index(2, 1), entryView, Qt::LeftButton, Qt::ControlModifier); QCOMPARE(entryView->selectionModel()->selectedRows().size(), 2); MessageBox::setNextAnswer(QMessageBox::No); @@ -729,7 +744,7 @@ void TestGui::testDeleteEntry() groupView, Qt::LeftButton); QCOMPARE(groupView->currentGroup()->name(), m_db->metadata()->recycleBin()->name()); - clickIndex(entryView->model()->index(0, 0), entryView, Qt::LeftButton); + clickIndex(entryView->model()->index(0, 1), entryView, Qt::LeftButton); MessageBox::setNextAnswer(QMessageBox::No); QTest::mouseClick(entryDeleteWidget, Qt::LeftButton); QCOMPARE(entryView->model()->rowCount(), 3); @@ -740,8 +755,8 @@ void TestGui::testDeleteEntry() QCOMPARE(entryView->model()->rowCount(), 2); QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 2); - clickIndex(entryView->model()->index(0, 0), entryView, Qt::LeftButton); - clickIndex(entryView->model()->index(1, 0), entryView, Qt::LeftButton, Qt::ControlModifier); + clickIndex(entryView->model()->index(0, 1), entryView, Qt::LeftButton); + clickIndex(entryView->model()->index(1, 1), entryView, Qt::LeftButton, Qt::ControlModifier); MessageBox::setNextAnswer(QMessageBox::Yes); QTest::mouseClick(entryDeleteWidget, Qt::LeftButton); QCOMPARE(entryView->model()->rowCount(), 0); From 18be1a0254d1c1381738cd1d52aaf9a03c34c0aa Mon Sep 17 00:00:00 2001 From: Fonic Date: Thu, 21 Dec 2017 13:52:01 +0100 Subject: [PATCH 5/9] Add 'copy-on-doubleclick' feature to entry view table Add 'copy-on-doubleclick' feature to entry view table by extending already existing DatabaseWidget::entryActivationSignalReceived(). Currently, username, password and notes are copyied on doubleclick, while doubleclicking URL still opens browser as before. Can easily be extended to account for other/additional columns (switch-case). --- src/gui/DatabaseWidget.cpp | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 8f4fc9bb3..013818c90 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -908,12 +908,39 @@ void DatabaseWidget::unlockDatabase(bool accepted) } } +/** + * @author Fonic + * Add 'copy-on-doubleclick' functionality for certain columns + * + * TODO: + * If pull request #1298 gets merged, double-clicking column 'Attachments' + * could open the new details view (see second screenshot) + */ void DatabaseWidget::entryActivationSignalReceived(Entry* entry, EntryModel::ModelColumn column) { - if (column == EntryModel::Url && !entry->url().isEmpty()) { - openUrlForEntry(entry); + /* Should never happen */ + if (!entry) { + Q_ASSERT(false); + return; } - else { + + /* Decide what to do based on specified column */ + switch (column) { + case EntryModel::Username: + setClipboardTextAndMinimize(entry->resolveMultiplePlaceholders(entry->username())); + break; + case EntryModel::Password: + setClipboardTextAndMinimize(entry->resolveMultiplePlaceholders(entry->password())); + break; + case EntryModel::Url: + if (!entry->url().isEmpty()) { + openUrlForEntry(entry); + } + break; + case EntryModel::Notes: + setClipboardTextAndMinimize(entry->resolveMultiplePlaceholders(entry->notes())); + break; + default: switchToEntryEdit(entry); } } From e3a5a22b8445e36e7eb99ed9844daf7cb2d71740 Mon Sep 17 00:00:00 2001 From: Fonic Date: Sat, 23 Dec 2017 08:40:00 +0100 Subject: [PATCH 6/9] Update state syncer to account for new features Update state syncer (class DatabaseWidgetStateSync) to account for new features: - properly sync view state when switching tabs - properly read/write view state from/to config Update classes EntryModel and EntryView to consistenly name list/search modes. Before, both classes defined list mode as 'group mode' and search mode as 'entry list mode', which differed from naming in other classes such as DatabaseWidget. --- src/core/Config.cpp | 7 + src/gui/DatabaseWidget.cpp | 74 ++++++--- src/gui/DatabaseWidget.h | 27 +++- src/gui/DatabaseWidgetStateSync.cpp | 107 +++++++++++-- src/gui/DatabaseWidgetStateSync.h | 25 +++- src/gui/entry/EntryModel.cpp | 4 +- src/gui/entry/EntryModel.h | 9 +- src/gui/entry/EntryView.cpp | 225 ++++++++++++++++++++-------- src/gui/entry/EntryView.h | 41 ++++- 9 files changed, 408 insertions(+), 111 deletions(-) diff --git a/src/core/Config.cpp b/src/core/Config.cpp index 96fd385ff..2153d65f6 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -138,6 +138,13 @@ void Config::init(const QString& fileName) m_defaults.insert("GUI/DarkTrayIcon", false); m_defaults.insert("GUI/MinimizeToTray", false); m_defaults.insert("GUI/MinimizeOnClose", false); + /** + * @author Fonic + * Set defaults for state of 'Hide Usernames'/'Hide Passwords' settings + * of entry view + */ + m_defaults.insert("GUI/EntryHideUsernames", false); + m_defaults.insert("GUI/EntryHidePasswords", true); } Config* Config::instance() diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 013818c90..98547c0b8 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -181,7 +181,13 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) connect(m_mainSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(mainSplitterSizesChanged())); connect(m_detailSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(detailSplitterSizesChanged())); - connect(m_entryView->header(), SIGNAL(sectionResized(int,int,int)), SIGNAL(entryColumnSizesChanged())); + + /** + * @author Fonic + * Connect signal to pass through state changes of entry view view + */ + connect(m_entryView, SIGNAL(viewStateChanged()), SIGNAL(entryViewStateChanged())); + connect(m_groupView, SIGNAL(groupChanged(Group*)), this, SLOT(onGroupChanged(Group*))); connect(m_groupView, SIGNAL(groupChanged(Group*)), SIGNAL(groupChanged())); connect(m_entryView, SIGNAL(entryActivated(Entry*, EntryModel::ModelColumn)), @@ -290,28 +296,58 @@ void DatabaseWidget::setDetailSplitterSizes(const QList &sizes) m_detailSplitter->setSizes(sizes); } -QList DatabaseWidget::entryHeaderViewSizes() const +/** + * @author Fonic + * Get current state of entry view 'Hide Usernames' setting + */ +bool DatabaseWidget::entryViewHideUsernames() const { - QList sizes; - - for (int i = 0; i < m_entryView->header()->count(); i++) { - sizes.append(m_entryView->header()->sectionSize(i)); - } - - return sizes; + return m_entryView->hideUsernames(); } -void DatabaseWidget::setEntryViewHeaderSizes(const QList& sizes) +/** + * @author Fonic + * Set state of entry view 'Hide Usernames' setting + */ +void DatabaseWidget::setEntryViewHideUsernames(const bool hide) { - const bool enoughSizes = sizes.size() == m_entryView->header()->count(); - Q_ASSERT(enoughSizes); - if (!enoughSizes) { - return; - } + m_entryView->setHideUsernames(hide); +} - for (int i = 0; i < sizes.size(); i++) { - m_entryView->header()->resizeSection(i, sizes[i]); - } +/** + * @author Fonic + * Get current state of entry view 'Hide Passwords' setting + */ +bool DatabaseWidget::entryViewHidePasswords() const +{ + return m_entryView->hidePasswords(); +} + +/** + * @author Fonic + * Set state of entry view 'Hide Passwords' setting + */ +void DatabaseWidget::setEntryViewHidePasswords(const bool hide) +{ + m_entryView->setHidePasswords(hide); +} + +/** + * @author Fonic + * Get current state of entry view view + */ +QByteArray DatabaseWidget::entryViewViewState() const +{ + return m_entryView->viewState(); +} + +/** + * @author Fonic + * Set state of entry view view + */ +bool DatabaseWidget::setEntryViewViewState(const QByteArray& state) const +{ + return m_entryView->setViewState(state); } void DatabaseWidget::clearAllWidgets() @@ -1178,7 +1214,7 @@ bool DatabaseWidget::canDeleteCurrentGroup() const bool DatabaseWidget::isInSearchMode() const { - return m_entryView->inEntryListMode(); + return m_entryView->inSearchMode(); } Group* DatabaseWidget::currentGroup() const diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 3643bb03a..3f459e3f6 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -92,8 +92,23 @@ public: void setMainSplitterSizes(const QList& sizes); QList detailSplitterSizes() const; void setDetailSplitterSizes(const QList& sizes); - QList entryHeaderViewSizes() const; - void setEntryViewHeaderSizes(const QList& sizes); + + /** + * @author Fonic + * Methods to get/set state of entry view 'Hide Usernames'/'Hide + * Passwords' settings + */ + bool entryViewHideUsernames() const; + void setEntryViewHideUsernames(const bool hide); + bool entryViewHidePasswords() const; + void setEntryViewHidePasswords(const bool hide); + /** + * @author Fonic + * Methods to get/set state of entry view view state + */ + QByteArray entryViewViewState() const; + bool setEntryViewViewState(const QByteArray& state) const; + void clearAllWidgets(); bool currentEntryHasTitle(); bool currentEntryHasUsername(); @@ -127,7 +142,13 @@ signals: void searchModeActivated(); void mainSplitterSizesChanged(); void detailSplitterSizesChanged(); - void entryColumnSizesChanged(); + + /** + * @author Fonic + * Signal to notify about state changes entry view view + */ + void entryViewStateChanged(); + void updateSearch(QString text); public slots: diff --git a/src/gui/DatabaseWidgetStateSync.cpp b/src/gui/DatabaseWidgetStateSync.cpp index 57a3dcf86..e8808d5d0 100644 --- a/src/gui/DatabaseWidgetStateSync.cpp +++ b/src/gui/DatabaseWidgetStateSync.cpp @@ -27,16 +27,40 @@ DatabaseWidgetStateSync::DatabaseWidgetStateSync(QObject* parent) { m_mainSplitterSizes = variantToIntList(config()->get("GUI/SplitterState")); m_detailSplitterSizes = variantToIntList(config()->get("GUI/DetailSplitterState")); - m_columnSizesList = variantToIntList(config()->get("GUI/EntryListColumnSizes")); - m_columnSizesSearch = variantToIntList(config()->get("GUI/EntrySearchColumnSizes")); + + /** + * @author Fonic + * Load state of entry view 'Hide Usernames'/'Hide Passwords' settings + */ + m_entryHideUsernames = config()->get("GUI/EntryHideUsernames").toBool(); + m_entryHidePasswords = config()->get("GUI/EntryHidePasswords").toBool(); + + /** + * @author Fonic + * Load states of entry view list/search view + */ + m_entryListViewState = config()->get("GUI/EntryListViewState").toByteArray(); + m_entrySearchViewState = config()->get("GUI/EntrySearchViewState").toByteArray(); } DatabaseWidgetStateSync::~DatabaseWidgetStateSync() { config()->set("GUI/SplitterState", intListToVariant(m_mainSplitterSizes)); config()->set("GUI/DetailSplitterState", intListToVariant(m_detailSplitterSizes)); - config()->set("GUI/EntryListColumnSizes", intListToVariant(m_columnSizesList)); - config()->set("GUI/EntrySearchColumnSizes", intListToVariant(m_columnSizesSearch)); + + /** + * @author Fonic + * Save state of entry view 'Hide Usernames'/'Hide Passwords' settings + */ + config()->set("GUI/EntryHideUsernames", m_entryHideUsernames); + config()->set("GUI/EntryHidePasswords", m_entryHidePasswords); + + /** + * @author Fonic + * Save states of entry view list/search view + */ + config()->set("GUI/EntryListViewState", m_entryListViewState); + config()->set("GUI/EntrySearchViewState", m_entrySearchViewState); } void DatabaseWidgetStateSync::setActive(DatabaseWidget* dbWidget) @@ -70,8 +94,14 @@ void DatabaseWidgetStateSync::setActive(DatabaseWidget* dbWidget) SLOT(updateSplitterSizes())); connect(m_activeDbWidget, SIGNAL(detailSplitterSizesChanged()), SLOT(updateSplitterSizes())); - connect(m_activeDbWidget, SIGNAL(entryColumnSizesChanged()), - SLOT(updateColumnSizes())); + + /** + * @author Fonic + * Connect signal to receive state changes of entry view view + */ + connect(m_activeDbWidget, SIGNAL(entryViewStateChanged()), + SLOT(updateViewState())); + connect(m_activeDbWidget, SIGNAL(listModeActivated()), SLOT(restoreListView())); connect(m_activeDbWidget, SIGNAL(searchModeActivated()), @@ -83,19 +113,59 @@ void DatabaseWidgetStateSync::setActive(DatabaseWidget* dbWidget) } } +/** + * @author Fonic + * Restore entry view list view state + * + * NOTE: + * States of entry view 'Hide Usernames'/'Hide Passwords' settings are considered + * 'global', i.e. they are the same for both list and search mode + * + * NOTE: + * If m_entryListViewState is empty, it is the first time after clean/invalid + * config that list view is activated. Thus, save its current state. Without + * this, m_entryListViewState would remain empty until there is an actual view + * state change (e.g. column is resized) + */ void DatabaseWidgetStateSync::restoreListView() { - if (!m_columnSizesList.isEmpty()) { - m_activeDbWidget->setEntryViewHeaderSizes(m_columnSizesList); + m_activeDbWidget->setEntryViewHideUsernames(m_entryHideUsernames); + m_activeDbWidget->setEntryViewHidePasswords(m_entryHidePasswords); + + if (!m_entryListViewState.isEmpty()) { + m_activeDbWidget->setEntryViewViewState(m_entryListViewState); + } + else { + m_entryListViewState = m_activeDbWidget->entryViewViewState(); } m_blockUpdates = false; } +/** + * @author Fonic + * Restore entry view search view state + * + * NOTE: + * States of entry view 'Hide Usernames'/'Hide Passwords' settings are considered + * 'global', i.e. they are the same for both list and search mode + * + * NOTE: + * If m_entrySearchViewState is empty, it is the first time after clean/invalid + * config that search view is activated. Thus, save its current state. Without + * this, m_entrySearchViewState would remain empty until there is an actual view + * state change (e.g. column is resized) + */ void DatabaseWidgetStateSync::restoreSearchView() { - if (!m_columnSizesSearch.isEmpty()) { - m_activeDbWidget->setEntryViewHeaderSizes(m_columnSizesSearch); + m_activeDbWidget->setEntryViewHideUsernames(m_entryHideUsernames); + m_activeDbWidget->setEntryViewHidePasswords(m_entryHidePasswords); + + if (!m_entrySearchViewState.isEmpty()) { + m_activeDbWidget->setEntryViewViewState(m_entrySearchViewState); + } + else { + m_entrySearchViewState = m_activeDbWidget->entryViewViewState(); } m_blockUpdates = false; @@ -116,17 +186,26 @@ void DatabaseWidgetStateSync::updateSplitterSizes() m_detailSplitterSizes = m_activeDbWidget->detailSplitterSizes(); } -void DatabaseWidgetStateSync::updateColumnSizes() +/** + * @author Fonic + * Update entry view list/search view state (NOTE: states of entry view + * 'Hide Usernames'/'Hide Passwords' settings are considered 'global', + * i.e. they are the same for both list and search mode) + */ +void DatabaseWidgetStateSync::updateViewState() { if (m_blockUpdates) { return; } - if (m_activeDbWidget->isGroupSelected()) { - m_columnSizesList = m_activeDbWidget->entryHeaderViewSizes(); + m_entryHideUsernames = m_activeDbWidget->entryViewHideUsernames(); + m_entryHidePasswords = m_activeDbWidget->entryViewHidePasswords(); + + if (m_activeDbWidget->isInSearchMode()) { + m_entrySearchViewState = m_activeDbWidget->entryViewViewState(); } else { - m_columnSizesSearch = m_activeDbWidget->entryHeaderViewSizes(); + m_entryListViewState = m_activeDbWidget->entryViewViewState(); } } diff --git a/src/gui/DatabaseWidgetStateSync.h b/src/gui/DatabaseWidgetStateSync.h index 79a8ded38..be5fcb5d9 100644 --- a/src/gui/DatabaseWidgetStateSync.h +++ b/src/gui/DatabaseWidgetStateSync.h @@ -37,7 +37,12 @@ public slots: private slots: void blockUpdates(); void updateSplitterSizes(); - void updateColumnSizes(); + + /** + * @author Fonic + * Slot to update entry view view state + */ + void updateViewState(); private: static QList variantToIntList(const QVariant& variant); @@ -48,8 +53,22 @@ private: bool m_blockUpdates; QList m_mainSplitterSizes; QList m_detailSplitterSizes; - QList m_columnSizesList; - QList m_columnSizesSearch; + + /** + * @author Fonic + * Properties to store state of entry view 'Hide Usernames'/'Hide + * Passwords' settings + */ + bool m_entryHideUsernames; + bool m_entryHidePasswords; + + /** + * @author Fonic + * Properties to store states of entry view list/search view (replaces + * m_columnSizesList/m_columnSizesSearch) + */ + QByteArray m_entryListViewState; + QByteArray m_entrySearchViewState; }; #endif // KEEPASSX_DATABASEWIDGETSTATESYNC_H diff --git a/src/gui/entry/EntryModel.cpp b/src/gui/entry/EntryModel.cpp index e03b0f791..18d6f1a00 100644 --- a/src/gui/entry/EntryModel.cpp +++ b/src/gui/entry/EntryModel.cpp @@ -97,7 +97,7 @@ void EntryModel::setGroup(Group* group) makeConnections(group); endResetModel(); - emit switchedToGroupMode(); + emit switchedToListMode(); } void EntryModel::setEntryList(const QList& entries) @@ -134,7 +134,7 @@ void EntryModel::setEntryList(const QList& entries) } endResetModel(); - emit switchedToEntryListMode(); + emit switchedToSearchMode(); } int EntryModel::rowCount(const QModelIndex& parent) const diff --git a/src/gui/entry/EntryModel.h b/src/gui/entry/EntryModel.h index 5b4f5e734..7540df986 100644 --- a/src/gui/entry/EntryModel.h +++ b/src/gui/entry/EntryModel.h @@ -75,8 +75,13 @@ public: void setHidePasswords(const bool hide); signals: - void switchedToEntryListMode(); - void switchedToGroupMode(); + /** + * @author Fonic + * Signals to notify about list/search mode switches (NOTE: previously + * named 'switchedToGroupMode'/'switchedToEntryListMode') + */ + void switchedToListMode(); + void switchedToSearchMode(); /** * @author Fonic diff --git a/src/gui/entry/EntryView.cpp b/src/gui/entry/EntryView.cpp index 6657818d2..e52944e55 100644 --- a/src/gui/entry/EntryView.cpp +++ b/src/gui/entry/EntryView.cpp @@ -43,7 +43,7 @@ EntryView::EntryView(QWidget* parent) : QTreeView(parent) , m_model(new EntryModel(this)) , m_sortModel(new SortFilterHideProxyModel(this)) - , m_inEntryListMode(false) + , m_inSearchMode(false) { m_sortModel->setSourceModel(m_model); m_sortModel->setDynamicSortFilter(true); @@ -70,8 +70,20 @@ EntryView::EntryView(QWidget* parent) connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(emitEntryActivated(QModelIndex))); connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SIGNAL(entrySelectionChanged())); - connect(m_model, SIGNAL(switchedToEntryListMode()), SLOT(switchToEntryListMode())); - connect(m_model, SIGNAL(switchedToGroupMode()), SLOT(switchToGroupMode())); + /** + * @author Fonic + * Connect signals to get notified about list/search mode switches (NOTE: + * previously named 'switch[ed]ToGroupMode'/'switch[ed]ToEntryListMode') + */ + connect(m_model, SIGNAL(switchedToListMode()), SLOT(switchToListMode())); + connect(m_model, SIGNAL(switchedToSearchMode()), SLOT(switchToSearchMode())); + /** + * @author Fonic + * Connect signals to notify about changes of view state when state of + * 'Hide Usernames'/'Hide Passwords' settings changes in model + */ + connect(m_model, SIGNAL(hideUsernamesChanged()), SIGNAL(viewStateChanged())); + connect(m_model, SIGNAL(hidePasswordsChanged()), SIGNAL(viewStateChanged())); connect(this, SIGNAL(clicked(QModelIndex)), SLOT(emitEntryPressed(QModelIndex))); @@ -118,25 +130,51 @@ EntryView::EntryView(QWidget* parent) * - Disable stretching of last section (interferes with fitting columns * to window) * - Associate with context menu + * - Connect signals to notify about changes of view state when state + * of header changes */ header()->setDefaultSectionSize(100); header()->setStretchLastSection(false); header()->setContextMenuPolicy(Qt::CustomContextMenu); connect(header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showHeaderMenu(QPoint))); + connect(header(), SIGNAL(sectionCountChanged(int, int)), this, SIGNAL(viewStateChanged())); + connect(header(), SIGNAL(sectionMoved(int, int, int)), this, SIGNAL(viewStateChanged())); + connect(header(), SIGNAL(sectionResized(int, int, int)), this, SIGNAL(viewStateChanged())); + connect(header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), this, SIGNAL(viewStateChanged())); /** * @author Fonic - * Finalize setup by resetting view to defaults. Although not really - * necessary at this point, it makes sense in order to avoid duplicating - * code (sorting order, visibility of first column etc.) + * Fit columns to window * * TODO: * Not working as expected, columns will end up being very small, most - * likely due to EntryView not being sized properly at this time. Either - * find a way to make this work by analizing when/where EntryView is - * created or remove + * likely due to EntryView not being sized properly at this time. Find + * a way to make this work by analizing when/where EntryView is created */ - //resetViewToDefaults(); + //fitColumnsToWindow(); + + /** + * @author Fonic + * Configure default search view state and save for later use + */ + header()->showSection(EntryModel::ParentGroup); + m_sortModel->sort(EntryModel::ParentGroup, Qt::AscendingOrder); + sortByColumn(EntryModel::ParentGroup, Qt::AscendingOrder); + m_defaultSearchViewState = header()->saveState(); + + /** + * @author Fonic + * Configure default list view state and save for later use + * + * NOTE: + * Default list view is intentionally configured last since this is the + * view that's supposed to be active after initialization as m_inSearchMode + * is initialized with 'false' + */ + header()->hideSection(EntryModel::ParentGroup); + m_sortModel->sort(EntryModel::Title, Qt::AscendingOrder); + sortByColumn(EntryModel::Title, Qt::AscendingOrder); + m_defaultListViewState = header()->saveState(); } void EntryView::keyPressEvent(QKeyEvent* event) @@ -175,9 +213,9 @@ void EntryView::setFirstEntryActive() } } -bool EntryView::inEntryListMode() +bool EntryView::inSearchMode() { - return m_inEntryListMode; + return m_inSearchMode; } void EntryView::emitEntryActivated(const QModelIndex& index) @@ -230,53 +268,112 @@ Entry* EntryView::entryFromIndex(const QModelIndex& index) } } -void EntryView::switchToEntryListMode() +/** + * @author Fonic + * Switch to list mode, i.e. list entries of group (NOTE: previously named + * 'switchToGroupMode') + */ +void EntryView::switchToListMode() { - /** - * @author Fonic - * Use header()->showSection() instead of m_sortModel->hideColumn() as - * the latter messes up column indices, interfering with code relying on - * proper indices - */ - header()->showSection(EntryModel::ParentGroup); - if (header()->sectionSize(EntryModel::ParentGroup) == 0) { - header()->resizeSection(EntryModel::ParentGroup, header()->defaultSectionSize()); + /* Check if already in this mode */ + if (!m_inSearchMode) { + return; } /** - * @author Fonic - * Set sorting column and order (TODO: check what first two lines do, if - * they are actually necessary, if indices are still correct and if indices - * may be replaced by EntryModel::) - */ - m_sortModel->sort(1, Qt::AscendingOrder); - m_sortModel->sort(0, Qt::AscendingOrder); - sortByColumn(EntryModel::ParentGroup, Qt::AscendingOrder); - - m_inEntryListMode = true; -} - -void EntryView::switchToGroupMode() -{ - /** - * @author Fonic * Use header()->hideSection() instead of m_sortModel->hideColumn() as * the latter messes up column indices, interfering with code relying on * proper indices */ header()->hideSection(EntryModel::ParentGroup); - /** - * @author Fonic - * Set sorting column and order (TODO: check what first two lines do, if - * they are actually necessary, if indices are still correct and if indices - * may be replaced by EntryModel::) - */ - m_sortModel->sort(-1, Qt::AscendingOrder); - m_sortModel->sort(0, Qt::AscendingOrder); - sortByColumn(EntryModel::Title, Qt::AscendingOrder); + m_inSearchMode = false; +} - m_inEntryListMode = false; +/** + * @author Fonic + * Switch to search mode, i.e. list search results (NOTE: previously named + * 'switchToEntryListMode') + */ +void EntryView::switchToSearchMode() +{ + /* Check if already in this mode */ + if (m_inSearchMode) { + return; + } + + /* + * Use header()->showSection() instead of m_sortModel->hideColumn() as + * the latter messes up column indices, interfering with code relying on + * proper indices + */ + header()->showSection(EntryModel::ParentGroup); + + /* + * Always set sorting to column 'Group', as it does not feel right to have + * the last known sort configuration of search view restored by 'Database + * WidgetStateSync', which is what happens without this + */ + m_sortModel->sort(EntryModel::ParentGroup, Qt::AscendingOrder); + sortByColumn(EntryModel::ParentGroup, Qt::AscendingOrder); + + m_inSearchMode = true; +} + +/** + * @author Fonic + * Get current state of 'Hide Usernames' setting (NOTE: just pass-through for + * m_model) + */ +bool EntryView::hideUsernames() const +{ + return m_model->hideUsernames(); +} + +/** + * @author Fonic + * Set state of 'Hide Usernames' setting (NOTE: just pass-through for m_model) + */ +void EntryView::setHideUsernames(const bool hide) +{ + m_model->setHideUsernames(hide); +} + +/** + * @author Fonic + * Get current state of 'Hide Passwords' setting (NOTE: just pass-through for + * m_model) + */ +bool EntryView::hidePasswords() const +{ + return m_model->hidePasswords(); +} + +/** + * @author Fonic + * Set state of 'Hide Passwords' setting (NOTE: just pass-through for m_model) + */ +void EntryView::setHidePasswords(const bool hide) +{ + m_model->setHidePasswords(hide); +} + +/** + * @author Fonic + * Get current state of view + */ +QByteArray EntryView::viewState() const +{ + return header()->saveState(); +} + +/** + * @author Fonic + * Set state of entry view view + */ +bool EntryView::setViewState(const QByteArray& state) const +{ + return header()->restoreState(state); } /** @@ -351,10 +448,16 @@ void EntryView::toggleColumnVisibility(QAction *action) * its implementation MUST call the corresponding parent method using * 'QTreeView::resizeEvent(event)'. Without this, fitting to window will * be broken and/or work unreliably (stumbled upon during testing) + * + * NOTE: + * Testing showed that it is absolutely necessary to emit signal 'viewState + * Changed' here. Without this, an incomplete view state might get saved by + * 'DatabaseWidgetStateSync' (e.g. only some columns resized) */ void EntryView::fitColumnsToWindow() { header()->resizeSections(QHeaderView::Stretch); + emit viewStateChanged(); } /** @@ -395,16 +498,17 @@ void EntryView::fitColumnsToContents() } header()->resizeSection(header()->logicalIndex(last), header()->sectionSize(last) + (header()->width() - width)); } + + /* + * This should not be necessary due to use of header()->resizeSection, + * but lets do it anyway for the sake of completeness + */ + emit viewStateChanged(); } /** * @author Fonic * Reset view to defaults - * - * NOTE: - * header()->saveState()/restoreState() could also be used for this, but - * testing showed that it complicates things more than it helps when trying - * to account for current list mode */ void EntryView::resetViewToDefaults() { @@ -412,19 +516,12 @@ void EntryView::resetViewToDefaults() m_model->setHideUsernames(false); m_model->setHidePasswords(true); - /* Reset visibility, size and position of all columns */ - for (int colidx = 0; colidx < header()->count(); colidx++) { - header()->showSection(colidx); - header()->resizeSection(colidx, header()->defaultSectionSize()); - header()->moveSection(header()->visualIndex(colidx), colidx); - } - - /* Reenter current list mode (affects first column and sorting) */ - if (m_inEntryListMode) { - switchToEntryListMode(); + /* Reset columns (size, order, sorting etc.) */ + if (m_inSearchMode) { + header()->restoreState(m_defaultSearchViewState); } else { - switchToGroupMode(); + header()->restoreState(m_defaultListViewState); } /* Nicely fitting columns to window feels like a sane default */ diff --git a/src/gui/entry/EntryView.h b/src/gui/entry/EntryView.h index 2cadf0d82..5b99bec99 100644 --- a/src/gui/entry/EntryView.h +++ b/src/gui/entry/EntryView.h @@ -43,10 +43,27 @@ public: void setCurrentEntry(Entry* entry); Entry* entryFromIndex(const QModelIndex& index); void setEntryList(const QList& entries); - bool inEntryListMode(); + bool inSearchMode(); int numberOfSelectedEntries(); void setFirstEntryActive(); + /** + * @author Fonic + * Methods to get/set state of 'Hide Usernames'/'Hide Passwords' settings + * (NOTE: these are just pass-through methods to avoid exposing entry model) + */ + bool hideUsernames() const; + void setHideUsernames(const bool hide); + bool hidePasswords() const; + void setHidePasswords(const bool hide); + + /** + * @author Fonic + * Methods to get/set state of view + */ + QByteArray viewState() const; + bool setViewState(const QByteArray& state) const; + public slots: void setGroup(Group* group); @@ -54,6 +71,11 @@ signals: void entryActivated(Entry* entry, EntryModel::ModelColumn column); void entryPressed(Entry* entry); void entrySelectionChanged(); + /** + * @author Fonic + * Signal to notify about changes of view state + */ + void viewStateChanged(); protected: void keyPressEvent(QKeyEvent* event) override; @@ -61,8 +83,13 @@ protected: private slots: void emitEntryActivated(const QModelIndex& index); void emitEntryPressed(const QModelIndex& index); - void switchToEntryListMode(); - void switchToGroupMode(); + /** + * @author Fonic + * Methods to switch to list/search mode (NOTE: previously named 'switch + * ToGroupMode'/'switchToEntryListMode') + */ + void switchToListMode(); + void switchToSearchMode(); /** * @author Fonic @@ -77,8 +104,14 @@ private slots: private: EntryModel* const m_model; SortFilterHideProxyModel* const m_sortModel; - bool m_inEntryListMode; + bool m_inSearchMode; + /** + * @author Fonic + * Properties to store default view states used by resetViewToDefaults() + */ + QByteArray m_defaultListViewState; + QByteArray m_defaultSearchViewState; /** * @author Fonic * Properties to store header context menu and actions From 161d0ea50fdd544fb5a64fd3b19297615d5a53f2 Mon Sep 17 00:00:00 2001 From: Fonic Date: Mon, 15 Jan 2018 10:06:45 +0100 Subject: [PATCH 7/9] Add column 'Paperclip' to entry view table Add additional column 'Paperclip' to entry view table: - add column itself - add display role data provider - add sort role data provider - update total column count --- src/gui/entry/EntryModel.cpp | 45 ++++++++++++++++++++++++++++-------- src/gui/entry/EntryModel.h | 12 ++++++++-- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/gui/entry/EntryModel.cpp b/src/gui/entry/EntryModel.cpp index 18d6f1a00..12d85e499 100644 --- a/src/gui/entry/EntryModel.cpp +++ b/src/gui/entry/EntryModel.cpp @@ -42,9 +42,9 @@ * Decide which of the proposed options should be used (stars, bullet, black * circle) */ -//const QString EntryModel::HiddenContent("******"); -//const QString EntryModel::HiddenContent(QString(QChar(0x2022)).repeated(6)); -const QString EntryModel::HiddenContent(QString(QChar(0x25CF)).repeated(6)); +//const QString EntryModel::HiddenContentDisplay("******"); +//const QString EntryModel::HiddenContentDisplay(QString(QChar(0x2022)).repeated(6)); +const QString EntryModel::HiddenContentDisplay(QString(QChar(0x25CF)).repeated(6)); /** * @author Fonic @@ -53,6 +53,18 @@ const QString EntryModel::HiddenContent(QString(QChar(0x25CF)).repeated(6)); */ const Qt::DateFormat EntryModel::DateFormat = Qt::DefaultLocaleShortDate; +/** + * @author Fonic + * Define constant string used to display header and data of column 'Paper- + * clip' + * + * TODO: + * When using unicode, ASAN reports memory leaks, but when using a plain + * string like 'x', no leaks are reported. Check if this is something to + * worry about, might as well be a Qt bug + */ +const QString EntryModel::PaperClipDisplay(QString("\U0001f4ce")); + /** * @author Fonic * Initialize 'Hide Usernames' and 'Hide Passwords' settings using sane @@ -152,14 +164,15 @@ int EntryModel::columnCount(const QModelIndex& parent) const /** * @author Fonic * Change column count to include additional columns 'Password', 'Notes', - * 'Expires', 'Created', 'Modified', 'Accessed' and 'Attachments'. Also, - * return 0 when parent is valid as advised by Qt documentation + * 'Expires', 'Created', 'Modified', 'Accessed', 'Paperclip' and + * 'Attachments'. Also, return 0 when parent is valid as advised by Qt + * documentation */ if (parent.isValid()) { return 0; } else { - return 11; + return 12; } } @@ -176,7 +189,8 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const * @author Fonic * * Add display data providers for additional columns 'Password', 'Notes', - * 'Expires', 'Created', 'Modified', 'Accessed' and 'Attachments' + * 'Expires', 'Created', 'Modified', 'Accessed', 'Paperclip' and + * 'Attachments' * * Add ability to display usernames and passwords hidden or visible * depending on current state of 'Hide Usernames' and 'Hide Passwords' @@ -211,7 +225,7 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const * of 'Hide Usernames' setting */ if (m_hideUsernames) { - result = EntryModel::HiddenContent; + result = EntryModel::HiddenContentDisplay; } else { //result = entry->username(); @@ -227,7 +241,7 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const * of 'Hide Passwords' setting */ if (m_hidePasswords) { - result = EntryModel::HiddenContent; + result = EntryModel::HiddenContentDisplay; } else { //result = entry->resolveMultiplePlaceholders(entry->password()); @@ -270,6 +284,9 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const case Accessed: result = entry->timeInfo().lastAccessTime().toLocalTime().toString(EntryModel::DateFormat); return result; + case Paperclip: + result = entry->attachments()->keys().isEmpty() ? QString() : EntryModel::PaperClipDisplay; + return result; case Attachments: /* * Display comma-separated list of attachments @@ -308,6 +325,10 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const * sorting would be based on string representation of dates, yielding un- * desired results) * + * Add sort data provider for column 'Paperclip', required to display + * entries with attachments above those without when sorting ascendingly + * (and vice versa when sorting descendingly) + * * NOTE: * Qt::UserRole is used as sort role, using 'm_sortModel->setSortRole(Qt:: * UserRole)' in EntryView.cpp, EntryView::EntryView() @@ -333,6 +354,8 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const return entry->timeInfo().lastModificationTime(); case Accessed: return entry->timeInfo().lastAccessTime(); + case Paperclip: + return entry->attachments()->keys().isEmpty() ? 1 : 0; default: /* * For all other columns, simply use data provided by Qt::Display- @@ -379,7 +402,7 @@ QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int ro /** * @author Fonic * Add captions for additional columns 'Password', 'Notes', 'Expires', - * 'Created', 'Modified', 'Accessed' and 'Attachments' + * 'Created', 'Modified', 'Accessed', 'Paperclip' and 'Attachments' */ if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { @@ -403,6 +426,8 @@ QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int ro return tr("Modified"); case Accessed: return tr("Accessed"); + case Paperclip: + return EntryModel::PaperClipDisplay; case Attachments: return tr("Attachments"); } diff --git a/src/gui/entry/EntryModel.h b/src/gui/entry/EntryModel.h index 7540df986..75243e52f 100644 --- a/src/gui/entry/EntryModel.h +++ b/src/gui/entry/EntryModel.h @@ -45,7 +45,8 @@ public: Created = 7, Modified = 8, Accessed = 9, - Attachments = 10 + Paperclip = 10, + Attachments = 11 }; explicit EntryModel(QObject* parent = nullptr); @@ -130,7 +131,7 @@ private: * Constant string used to display hidden content in columns 'Username' * and 'Password' */ - static const QString HiddenContent; + static const QString HiddenContentDisplay; /** * @author Fonic @@ -138,6 +139,13 @@ private: * 'Modified' and 'Accessed' */ static const Qt::DateFormat DateFormat; + + /** + * @author Fonic + * Constant string used to display header and data of column 'Paper- + * clip' + */ + static const QString PaperClipDisplay; }; #endif // KEEPASSX_ENTRYMODEL_H From 9ff648177c23402f6a48433c28c4867479c8aef9 Mon Sep 17 00:00:00 2001 From: Fonic Date: Mon, 15 Jan 2018 10:24:28 +0100 Subject: [PATCH 8/9] Update entry model test to account for additional column 'Paperclip' Update comparison values of modelProxy->columnCount() to account for additional column 'Paperclip' --- tests/TestEntryModel.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/TestEntryModel.cpp b/tests/TestEntryModel.cpp index 49ae3b0d1..ab4ecf754 100644 --- a/tests/TestEntryModel.cpp +++ b/tests/TestEntryModel.cpp @@ -292,11 +292,11 @@ void TestEntryModel::testProxyModel() * @author Fonic * Update comparison value of modelProxy->columnCount() to account for * additional columns 'Password', 'Notes', 'Expires', 'Created', 'Modified', - * 'Accessed' and 'Attachments' + * 'Accessed', 'Paperclip' and 'Attachments' */ QSignalSpy spyColumnRemove(modelProxy, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int))); modelProxy->hideColumn(0, true); - QCOMPARE(modelProxy->columnCount(), 10); + QCOMPARE(modelProxy->columnCount(), 11); QVERIFY(spyColumnRemove.size() >= 1); int oldSpyColumnRemoveSize = spyColumnRemove.size(); @@ -314,11 +314,11 @@ void TestEntryModel::testProxyModel() * @author Fonic * Update comparison value of modelProxy->columnCount() to account for * additional columns 'Password', 'Notes', 'Expires', 'Created', 'Modified', - * 'Accessed' and 'Attachments' + * 'Accessed', 'Paperclip' and 'Attachments' */ QSignalSpy spyColumnInsert(modelProxy, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int))); modelProxy->hideColumn(0, false); - QCOMPARE(modelProxy->columnCount(), 11); + QCOMPARE(modelProxy->columnCount(), 12); QVERIFY(spyColumnInsert.size() >= 1); int oldSpyColumnInsertSize = spyColumnInsert.size(); From 8c78aca69e114cd3caea519cefb1ddbf225d77f3 Mon Sep 17 00:00:00 2001 From: Fonic Date: Tue, 16 Jan 2018 13:20:45 +0100 Subject: [PATCH 9/9] Code cleanup Comprehensive code cleanup: - formatting - comments - obsolete code --- src/core/Config.cpp | 9 +- src/gui/DatabaseWidget.cpp | 62 +++---- src/gui/DatabaseWidget.h | 29 +-- src/gui/DatabaseWidgetStateSync.cpp | 118 +++++------- src/gui/DatabaseWidgetStateSync.h | 23 +-- src/gui/DetailsWidget.cpp | 2 +- src/gui/entry/EntryModel.cpp | 233 +++++------------------ src/gui/entry/EntryModel.h | 64 +------ src/gui/entry/EntryView.cpp | 276 ++++++++-------------------- src/gui/entry/EntryView.h | 46 +---- tests/gui/TestGui.cpp | 15 -- 11 files changed, 220 insertions(+), 657 deletions(-) diff --git a/src/core/Config.cpp b/src/core/Config.cpp index 2153d65f6..fafb3cfd4 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -138,13 +138,8 @@ void Config::init(const QString& fileName) m_defaults.insert("GUI/DarkTrayIcon", false); m_defaults.insert("GUI/MinimizeToTray", false); m_defaults.insert("GUI/MinimizeOnClose", false); - /** - * @author Fonic - * Set defaults for state of 'Hide Usernames'/'Hide Passwords' settings - * of entry view - */ - m_defaults.insert("GUI/EntryHideUsernames", false); - m_defaults.insert("GUI/EntryHidePasswords", true); + m_defaults.insert("GUI/HideUsernames", false); + m_defaults.insert("GUI/HidePasswords", true); } Config* Config::instance() diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 98547c0b8..361ca36fe 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -181,13 +181,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) connect(m_mainSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(mainSplitterSizesChanged())); connect(m_detailSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(detailSplitterSizesChanged())); - - /** - * @author Fonic - * Connect signal to pass through state changes of entry view view - */ connect(m_entryView, SIGNAL(viewStateChanged()), SIGNAL(entryViewStateChanged())); - connect(m_groupView, SIGNAL(groupChanged(Group*)), this, SLOT(onGroupChanged(Group*))); connect(m_groupView, SIGNAL(groupChanged(Group*)), SIGNAL(groupChanged())); connect(m_entryView, SIGNAL(entryActivated(Entry*, EntryModel::ModelColumn)), @@ -297,55 +291,49 @@ void DatabaseWidget::setDetailSplitterSizes(const QList &sizes) } /** - * @author Fonic * Get current state of entry view 'Hide Usernames' setting */ -bool DatabaseWidget::entryViewHideUsernames() const +bool DatabaseWidget::isUsernamesHidden() const { - return m_entryView->hideUsernames(); + return m_entryView->isUsernamesHidden(); } /** - * @author Fonic * Set state of entry view 'Hide Usernames' setting */ -void DatabaseWidget::setEntryViewHideUsernames(const bool hide) +void DatabaseWidget::setUsernamesHidden(const bool hide) { - m_entryView->setHideUsernames(hide); + m_entryView->setUsernamesHidden(hide); } /** - * @author Fonic * Get current state of entry view 'Hide Passwords' setting */ -bool DatabaseWidget::entryViewHidePasswords() const +bool DatabaseWidget::isPasswordsHidden() const { - return m_entryView->hidePasswords(); + return m_entryView->isPasswordsHidden(); } /** - * @author Fonic * Set state of entry view 'Hide Passwords' setting */ -void DatabaseWidget::setEntryViewHidePasswords(const bool hide) +void DatabaseWidget::setPasswordsHidden(const bool hide) { - m_entryView->setHidePasswords(hide); + m_entryView->setPasswordsHidden(hide); } /** - * @author Fonic - * Get current state of entry view view + * Get current view state of entry view */ -QByteArray DatabaseWidget::entryViewViewState() const +QByteArray DatabaseWidget::entryViewState() const { return m_entryView->viewState(); } /** - * @author Fonic - * Set state of entry view view + * Set view state of entry view */ -bool DatabaseWidget::setEntryViewViewState(const QByteArray& state) const +bool DatabaseWidget::setEntryViewState(const QByteArray& state) const { return m_entryView->setViewState(state); } @@ -696,8 +684,8 @@ void DatabaseWidget::openUrlForEntry(Entry* entry) void DatabaseWidget::createGroup() { + Q_ASSERT(m_groupView->currentGroup()); if (!m_groupView->currentGroup()) { - Q_ASSERT(false); return; } @@ -710,8 +698,8 @@ void DatabaseWidget::createGroup() void DatabaseWidget::deleteGroup() { Group* currentGroup = m_groupView->currentGroup(); + Q_ASSERT(currentGroup && canDeleteCurrentGroup()); if (!currentGroup || !canDeleteCurrentGroup()) { - Q_ASSERT(false); return; } @@ -944,23 +932,14 @@ void DatabaseWidget::unlockDatabase(bool accepted) } } -/** - * @author Fonic - * Add 'copy-on-doubleclick' functionality for certain columns - * - * TODO: - * If pull request #1298 gets merged, double-clicking column 'Attachments' - * could open the new details view (see second screenshot) - */ void DatabaseWidget::entryActivationSignalReceived(Entry* entry, EntryModel::ModelColumn column) { - /* Should never happen */ + Q_ASSERT(entry); if (!entry) { - Q_ASSERT(false); return; } - /* Decide what to do based on specified column */ + // Implement 'copy-on-doubleclick' functionality for certain columns switch (column) { case EntryModel::Username: setClipboardTextAndMinimize(entry->resolveMultiplePlaceholders(entry->username())); @@ -973,9 +952,12 @@ void DatabaseWidget::entryActivationSignalReceived(Entry* entry, EntryModel::Mod openUrlForEntry(entry); } break; - case EntryModel::Notes: - setClipboardTextAndMinimize(entry->resolveMultiplePlaceholders(entry->notes())); - break; + // TODO: switch to 'Notes' tab in details view/pane + //case EntryModel::Notes: + // break; + // TODO: switch to 'Attachments' tab in details view/pane + //case EntryModel::Attachments: + // break; default: switchToEntryEdit(entry); } diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 3f459e3f6..ba799893a 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -92,23 +92,12 @@ public: void setMainSplitterSizes(const QList& sizes); QList detailSplitterSizes() const; void setDetailSplitterSizes(const QList& sizes); - - /** - * @author Fonic - * Methods to get/set state of entry view 'Hide Usernames'/'Hide - * Passwords' settings - */ - bool entryViewHideUsernames() const; - void setEntryViewHideUsernames(const bool hide); - bool entryViewHidePasswords() const; - void setEntryViewHidePasswords(const bool hide); - /** - * @author Fonic - * Methods to get/set state of entry view view state - */ - QByteArray entryViewViewState() const; - bool setEntryViewViewState(const QByteArray& state) const; - + bool isUsernamesHidden() const; + void setUsernamesHidden(const bool hide); + bool isPasswordsHidden() const; + void setPasswordsHidden(const bool hide); + QByteArray entryViewState() const; + bool setEntryViewState(const QByteArray& state) const; void clearAllWidgets(); bool currentEntryHasTitle(); bool currentEntryHasUsername(); @@ -142,13 +131,7 @@ signals: void searchModeActivated(); void mainSplitterSizesChanged(); void detailSplitterSizesChanged(); - - /** - * @author Fonic - * Signal to notify about state changes entry view view - */ void entryViewStateChanged(); - void updateSearch(QString text); public slots: diff --git a/src/gui/DatabaseWidgetStateSync.cpp b/src/gui/DatabaseWidgetStateSync.cpp index e8808d5d0..39904ed99 100644 --- a/src/gui/DatabaseWidgetStateSync.cpp +++ b/src/gui/DatabaseWidgetStateSync.cpp @@ -27,40 +27,20 @@ DatabaseWidgetStateSync::DatabaseWidgetStateSync(QObject* parent) { m_mainSplitterSizes = variantToIntList(config()->get("GUI/SplitterState")); m_detailSplitterSizes = variantToIntList(config()->get("GUI/DetailSplitterState")); - - /** - * @author Fonic - * Load state of entry view 'Hide Usernames'/'Hide Passwords' settings - */ - m_entryHideUsernames = config()->get("GUI/EntryHideUsernames").toBool(); - m_entryHidePasswords = config()->get("GUI/EntryHidePasswords").toBool(); - - /** - * @author Fonic - * Load states of entry view list/search view - */ - m_entryListViewState = config()->get("GUI/EntryListViewState").toByteArray(); - m_entrySearchViewState = config()->get("GUI/EntrySearchViewState").toByteArray(); + m_hideUsernames = config()->get("GUI/HideUsernames").toBool(); + m_hidePasswords = config()->get("GUI/HidePasswords").toBool(); + m_listViewState = config()->get("GUI/ListViewState").toByteArray(); + m_searchViewState = config()->get("GUI/SearchViewState").toByteArray(); } DatabaseWidgetStateSync::~DatabaseWidgetStateSync() { config()->set("GUI/SplitterState", intListToVariant(m_mainSplitterSizes)); config()->set("GUI/DetailSplitterState", intListToVariant(m_detailSplitterSizes)); - - /** - * @author Fonic - * Save state of entry view 'Hide Usernames'/'Hide Passwords' settings - */ - config()->set("GUI/EntryHideUsernames", m_entryHideUsernames); - config()->set("GUI/EntryHidePasswords", m_entryHidePasswords); - - /** - * @author Fonic - * Save states of entry view list/search view - */ - config()->set("GUI/EntryListViewState", m_entryListViewState); - config()->set("GUI/EntrySearchViewState", m_entrySearchViewState); + config()->set("GUI/HideUsernames", m_hideUsernames); + config()->set("GUI/HidePasswords", m_hidePasswords); + config()->set("GUI/ListViewState", m_listViewState); + config()->set("GUI/SearchViewState", m_searchViewState); } void DatabaseWidgetStateSync::setActive(DatabaseWidget* dbWidget) @@ -94,14 +74,8 @@ void DatabaseWidgetStateSync::setActive(DatabaseWidget* dbWidget) SLOT(updateSplitterSizes())); connect(m_activeDbWidget, SIGNAL(detailSplitterSizesChanged()), SLOT(updateSplitterSizes())); - - /** - * @author Fonic - * Connect signal to receive state changes of entry view view - */ connect(m_activeDbWidget, SIGNAL(entryViewStateChanged()), SLOT(updateViewState())); - connect(m_activeDbWidget, SIGNAL(listModeActivated()), SLOT(restoreListView())); connect(m_activeDbWidget, SIGNAL(searchModeActivated()), @@ -114,58 +88,54 @@ void DatabaseWidgetStateSync::setActive(DatabaseWidget* dbWidget) } /** - * @author Fonic * Restore entry view list view state * * NOTE: - * States of entry view 'Hide Usernames'/'Hide Passwords' settings are considered - * 'global', i.e. they are the same for both list and search mode + * States of entry view 'Hide Usernames'/'Hide Passwords' settings are global, + * i.e. they are the same for both list and search mode * * NOTE: - * If m_entryListViewState is empty, it is the first time after clean/invalid - * config that list view is activated. Thus, save its current state. Without - * this, m_entryListViewState would remain empty until there is an actual view - * state change (e.g. column is resized) + * If m_listViewState is empty, the list view has been activated for the first + * time after starting with a clean (or invalid) config. Thus, save the current + * state. Without this, m_listViewState would remain empty until there is an + * actual view state change (e.g. column is resized) */ void DatabaseWidgetStateSync::restoreListView() { - m_activeDbWidget->setEntryViewHideUsernames(m_entryHideUsernames); - m_activeDbWidget->setEntryViewHidePasswords(m_entryHidePasswords); + m_activeDbWidget->setUsernamesHidden(m_hideUsernames); + m_activeDbWidget->setPasswordsHidden(m_hidePasswords); - if (!m_entryListViewState.isEmpty()) { - m_activeDbWidget->setEntryViewViewState(m_entryListViewState); - } - else { - m_entryListViewState = m_activeDbWidget->entryViewViewState(); + if (!m_listViewState.isEmpty()) { + m_activeDbWidget->setEntryViewState(m_listViewState); + } else { + m_listViewState = m_activeDbWidget->entryViewState(); } m_blockUpdates = false; } /** - * @author Fonic * Restore entry view search view state * * NOTE: - * States of entry view 'Hide Usernames'/'Hide Passwords' settings are considered - * 'global', i.e. they are the same for both list and search mode + * States of entry view 'Hide Usernames'/'Hide Passwords' settings are global, + * i.e. they are the same for both list and search mode * * NOTE: - * If m_entrySearchViewState is empty, it is the first time after clean/invalid - * config that search view is activated. Thus, save its current state. Without - * this, m_entrySearchViewState would remain empty until there is an actual view - * state change (e.g. column is resized) + * If m_searchViewState is empty, the search view has been activated for the + * first time after starting with a clean (or invalid) config. Thus, save the + * current state. Without this, m_searchViewState would remain empty until + * there is an actual view state change (e.g. column is resized) */ void DatabaseWidgetStateSync::restoreSearchView() { - m_activeDbWidget->setEntryViewHideUsernames(m_entryHideUsernames); - m_activeDbWidget->setEntryViewHidePasswords(m_entryHidePasswords); + m_activeDbWidget->setUsernamesHidden(m_hideUsernames); + m_activeDbWidget->setPasswordsHidden(m_hidePasswords); - if (!m_entrySearchViewState.isEmpty()) { - m_activeDbWidget->setEntryViewViewState(m_entrySearchViewState); - } - else { - m_entrySearchViewState = m_activeDbWidget->entryViewViewState(); + if (!m_searchViewState.isEmpty()) { + m_activeDbWidget->setEntryViewState(m_searchViewState); + } else { + m_searchViewState = m_activeDbWidget->entryViewState(); } m_blockUpdates = false; @@ -187,10 +157,11 @@ void DatabaseWidgetStateSync::updateSplitterSizes() } /** - * @author Fonic - * Update entry view list/search view state (NOTE: states of entry view - * 'Hide Usernames'/'Hide Passwords' settings are considered 'global', - * i.e. they are the same for both list and search mode) + * Update entry view list/search view state + * + * NOTE: + * States of entry view 'Hide Usernames'/'Hide Passwords' settings are global, + * i.e. they are the same for both list and search mode */ void DatabaseWidgetStateSync::updateViewState() { @@ -198,14 +169,13 @@ void DatabaseWidgetStateSync::updateViewState() return; } - m_entryHideUsernames = m_activeDbWidget->entryViewHideUsernames(); - m_entryHidePasswords = m_activeDbWidget->entryViewHidePasswords(); + m_hideUsernames = m_activeDbWidget->isUsernamesHidden(); + m_hidePasswords = m_activeDbWidget->isPasswordsHidden(); if (m_activeDbWidget->isInSearchMode()) { - m_entrySearchViewState = m_activeDbWidget->entryViewViewState(); - } - else { - m_entryListViewState = m_activeDbWidget->entryViewViewState(); + m_searchViewState = m_activeDbWidget->entryViewState(); + } else { + m_listViewState = m_activeDbWidget->entryViewState(); } } @@ -219,8 +189,7 @@ QList DatabaseWidgetStateSync::variantToIntList(const QVariant& variant) int size = var.toInt(&ok); if (ok) { result.append(size); - } - else { + } else { result.clear(); break; } @@ -239,4 +208,3 @@ QVariant DatabaseWidgetStateSync::intListToVariant(const QList& list) return result; } - diff --git a/src/gui/DatabaseWidgetStateSync.h b/src/gui/DatabaseWidgetStateSync.h index be5fcb5d9..aca593f1f 100644 --- a/src/gui/DatabaseWidgetStateSync.h +++ b/src/gui/DatabaseWidgetStateSync.h @@ -37,11 +37,6 @@ public slots: private slots: void blockUpdates(); void updateSplitterSizes(); - - /** - * @author Fonic - * Slot to update entry view view state - */ void updateViewState(); private: @@ -54,21 +49,11 @@ private: QList m_mainSplitterSizes; QList m_detailSplitterSizes; - /** - * @author Fonic - * Properties to store state of entry view 'Hide Usernames'/'Hide - * Passwords' settings - */ - bool m_entryHideUsernames; - bool m_entryHidePasswords; + bool m_hideUsernames; + bool m_hidePasswords; - /** - * @author Fonic - * Properties to store states of entry view list/search view (replaces - * m_columnSizesList/m_columnSizesSearch) - */ - QByteArray m_entryListViewState; - QByteArray m_entrySearchViewState; + QByteArray m_listViewState; + QByteArray m_searchViewState; }; #endif // KEEPASSX_DATABASEWIDGETSTATESYNC_H diff --git a/src/gui/DetailsWidget.cpp b/src/gui/DetailsWidget.cpp index 1d20984be..44c29f8f8 100644 --- a/src/gui/DetailsWidget.cpp +++ b/src/gui/DetailsWidget.cpp @@ -127,7 +127,7 @@ void DetailsWidget::getSelectedEntry(Entry* selectedEntry) shortPassword(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password()))); m_ui->passwordLabel->setToolTip(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password())); } else { - m_ui->passwordLabel->setText("****"); + m_ui->passwordLabel->setText(QString("\u25cf").repeated(6)); } QString url = m_currentEntry->webUrl(); diff --git a/src/gui/entry/EntryModel.cpp b/src/gui/entry/EntryModel.cpp index 12d85e499..4a583b30e 100644 --- a/src/gui/entry/EntryModel.cpp +++ b/src/gui/entry/EntryModel.cpp @@ -20,11 +20,6 @@ #include #include #include -/** - * @author Fonic - * Add include required for additional columns 'Expires', 'Created', 'Modified' - * and 'Accessed' - */ #include #include "core/DatabaseIcons.h" @@ -33,43 +28,15 @@ #include "core/Group.h" #include "core/Metadata.h" -/** - * @author Fonic - * Define constant string used to display hidden content in columns 'Username' - * and 'Password' - * - * TODO: - * Decide which of the proposed options should be used (stars, bullet, black - * circle) - */ -//const QString EntryModel::HiddenContentDisplay("******"); -//const QString EntryModel::HiddenContentDisplay(QString(QChar(0x2022)).repeated(6)); -const QString EntryModel::HiddenContentDisplay(QString(QChar(0x25CF)).repeated(6)); +// String being displayed when hiding content +const QString EntryModel::HiddenContentDisplay(QString("\u25cf").repeated(6)); -/** - * @author Fonic - * Define date format used to display dates in columns 'Expires', 'Created', - * 'Modified' and 'Accessed' - */ +// Format used to display dates const Qt::DateFormat EntryModel::DateFormat = Qt::DefaultLocaleShortDate; -/** - * @author Fonic - * Define constant string used to display header and data of column 'Paper- - * clip' - * - * TODO: - * When using unicode, ASAN reports memory leaks, but when using a plain - * string like 'x', no leaks are reported. Check if this is something to - * worry about, might as well be a Qt bug - */ -const QString EntryModel::PaperClipDisplay(QString("\U0001f4ce")); +// Paperclip symbol +const QString EntryModel::PaperClipDisplay("\U0001f4ce"); -/** - * @author Fonic - * Initialize 'Hide Usernames' and 'Hide Passwords' settings using sane - * defaults (usernames visible, passwords hidden) - */ EntryModel::EntryModel(QObject* parent) : QAbstractTableModel(parent) , m_group(nullptr) @@ -153,27 +120,19 @@ int EntryModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; - } - else { + } else { return m_entries.size(); } } int EntryModel::columnCount(const QModelIndex& parent) const { - /** - * @author Fonic - * Change column count to include additional columns 'Password', 'Notes', - * 'Expires', 'Created', 'Modified', 'Accessed', 'Paperclip' and - * 'Attachments'. Also, return 0 when parent is valid as advised by Qt - * documentation - */ + // Advised by Qt documentation if (parent.isValid()) { return 0; } - else { - return 12; - } + + return 12; } QVariant EntryModel::data(const QModelIndex& index, int role) const @@ -185,26 +144,6 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const Entry* entry = entryFromIndex(index); EntryAttributes* attr = entry->attributes(); - /** - * @author Fonic - * - * Add display data providers for additional columns 'Password', 'Notes', - * 'Expires', 'Created', 'Modified', 'Accessed', 'Paperclip' and - * 'Attachments' - * - * Add ability to display usernames and passwords hidden or visible - * depending on current state of 'Hide Usernames' and 'Hide Passwords' - * settings - * - * TODO: - * Decide which of the additional columns should expand placeholders - * -> code added where applicable, but currently commented out - * - * Check what attr->isReference() does and if it applies to any of the - * additional columns - * -> code added for columns 'Password' and 'Notes', as EntryAttributes:: - * PasswordKey and EntryAttributes::NotesKey already existed - */ if (role == Qt::DisplayRole) { QString result; switch (index.column()) { @@ -216,63 +155,44 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const case Title: result = entry->resolveMultiplePlaceholders(entry->title()); if (attr->isReference(EntryAttributes::TitleKey)) { - result.prepend(tr("Ref: ","Reference abbreviation")); + result.prepend(tr("Ref: ", "Reference abbreviation")); } return result; case Username: - /* - * Display usernames hidden or visible according to current state - * of 'Hide Usernames' setting - */ if (m_hideUsernames) { result = EntryModel::HiddenContentDisplay; - } - else { - //result = entry->username(); + } else { result = entry->resolveMultiplePlaceholders(entry->username()); } if (attr->isReference(EntryAttributes::UserNameKey)) { - result.prepend(tr("Ref: ","Reference abbreviation")); + result.prepend(tr("Ref: ", "Reference abbreviation")); } return result; case Password: - /* - * Display passwords hidden or visible according to current state - * of 'Hide Passwords' setting - */ if (m_hidePasswords) { result = EntryModel::HiddenContentDisplay; - } - else { - //result = entry->resolveMultiplePlaceholders(entry->password()); - result = entry->password(); + } else { + result = entry->resolveMultiplePlaceholders(entry->password()); } if (attr->isReference(EntryAttributes::PasswordKey)) { - result.prepend(tr("Ref: ","Reference abbreviation")); + result.prepend(tr("Ref: ", "Reference abbreviation")); } return result; case Url: - //result = entry->resolveMultiplePlaceholders(entry->displayUrl()); - result = entry->displayUrl(); + result = entry->resolveMultiplePlaceholders(entry->displayUrl()); if (attr->isReference(EntryAttributes::URLKey)) { - result.prepend(tr("Ref: ","Reference abbreviation")); + result.prepend(tr("Ref: ", "Reference abbreviation")); } return result; case Notes: - /* - * Display only first line of notes in simplified format like - * KeePassX does - */ - //result = entry->resolveMultiplePlaceholders(entry->notes().section("\n", 0, 0).simplified()); - result = entry->notes().section("\n", 0, 0).simplified(); + // Display only first line of notes in simplified format + result = entry->resolveMultiplePlaceholders(entry->notes().section("\n", 0, 0).simplified()); if (attr->isReference(EntryAttributes::NotesKey)) { - result.prepend(tr("Ref: ","Reference abbreviation")); + result.prepend(tr("Ref: ", "Reference abbreviation")); } return result; case Expires: - /* - * Display either date of expiry or 'Never' like KeePassX does - */ + // Display either date of expiry or 'Never' result = entry->timeInfo().expires() ? entry->timeInfo().expiryTime().toLocalTime().toString(EntryModel::DateFormat) : tr("Never"); return result; case Created: @@ -288,65 +208,25 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const result = entry->attachments()->keys().isEmpty() ? QString() : EntryModel::PaperClipDisplay; return result; case Attachments: - /* - * Display comma-separated list of attachments - * - * TODO: - * 'entry->attachments()->keys().join()' works locally, yet it fails - * on GitHub/Travis CI, most likely due to an older Qt version, thus - * using loop for now (http://doc.qt.io/qt-5/qlist.html#more-members) - */ - //result = entry->resolveMultiplePlaceholders(entry->attachments()->keys().join(", ")); - //result = entry->attachments()->keys().join(", "); - + // Display comma-separated list of attachments QList attachments = entry->attachments()->keys(); - for (int i=0; i < attachments.size(); i++) { + for (int i = 0; i < attachments.size(); ++i) { if (result.isEmpty()) { result.append(attachments.at(i)); + continue; } - else { - result.append(QString(", ") + attachments.at(i)); - } + result.append(QString(", ") + attachments.at(i)); } - //result = entry->resolveMultiplePlaceholders(result); return result; } - - } - /** - * @author Fonic - * - * Add sort data providers for columns 'Username' and 'Password', required - * for correct sorting even if displayed hidden (i.e. settings 'Hide User- - * names' and/or 'Hide Passwords' are enabled) - * - * Add sort data providers for columns 'Expires', 'Created', 'Modified' - * and 'Accessed', required for correct sorting of dates (without this, - * sorting would be based on string representation of dates, yielding un- - * desired results) - * - * Add sort data provider for column 'Paperclip', required to display - * entries with attachments above those without when sorting ascendingly - * (and vice versa when sorting descendingly) - * - * NOTE: - * Qt::UserRole is used as sort role, using 'm_sortModel->setSortRole(Qt:: - * UserRole)' in EntryView.cpp, EntryView::EntryView() - */ - else if (role == Qt::UserRole) { + } else if (role == Qt::UserRole) { // Qt::UserRole is used as sort role, see EntryView::EntryView() switch (index.column()) { case Username: - //return entry->username(); return entry->resolveMultiplePlaceholders(entry->username()); case Password: - //return entry->resolveMultiplePlaceholders(entry->password()); - return entry->password(); + return entry->resolveMultiplePlaceholders(entry->password()); case Expires: - /* - * TODO: - * Is there any better way to return a QDateTime representing - * 'Never' / infinity / end of all time? - */ + // There seems to be no better way of expressing 'infinity' return entry->timeInfo().expires() ? entry->timeInfo().expiryTime() : QDateTime(QDate(9999, 1, 1)); case Created: return entry->timeInfo().creationTime(); @@ -355,16 +235,15 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const case Accessed: return entry->timeInfo().lastAccessTime(); case Paperclip: + // Display entries with attachments above those without when + // sorting ascendingly (and vice versa when sorting descendingly) return entry->attachments()->keys().isEmpty() ? 1 : 0; default: - /* - * For all other columns, simply use data provided by Qt::Display- - * Role for sorting - */ + // For all other columns, simply use data provided by Qt::Display- + // Role for sorting return data(index, Qt::DisplayRole); } - } - else if (role == Qt::DecorationRole) { + } else if (role == Qt::DecorationRole) { switch (index.column()) { case ParentGroup: if (entry->group()) { @@ -374,20 +253,17 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const case Title: if (entry->isExpired()) { return databaseIcons()->iconPixmap(DatabaseIcons::ExpiredIconIndex); - } - else { + } else { return entry->iconScaledPixmap(); } } - } - else if (role == Qt::FontRole) { + } else if (role == Qt::FontRole) { QFont font; if (entry->isExpired()) { font.setStrikeOut(true); } return font; - } - else if (role == Qt::TextColorRole) { + } else if (role == Qt::TextColorRole) { if (entry->hasReferences()) { QPalette p; return QVariant(p.color(QPalette::Active, QPalette::Mid)); @@ -399,11 +275,6 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int role) const { - /** - * @author Fonic - * Add captions for additional columns 'Password', 'Notes', 'Expires', - * 'Created', 'Modified', 'Accessed', 'Paperclip' and 'Attachments' - */ if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case ParentGroup: @@ -450,8 +321,7 @@ Qt::ItemFlags EntryModel::flags(const QModelIndex& modelIndex) const { if (!modelIndex.isValid()) { return Qt::NoItemFlags; - } - else { + } else { return QAbstractItemModel::flags(modelIndex) | Qt::ItemIsDragEnabled; } } @@ -492,8 +362,7 @@ QMimeData* EntryModel::mimeData(const QModelIndexList& indexes) const if (seenEntries.isEmpty()) { delete data; return nullptr; - } - else { + } else { data->setData(mimeTypes().at(0), encoded); return data; } @@ -567,57 +436,51 @@ void EntryModel::makeConnections(const Group* group) } /** - * @author Fonic * Get current state of 'Hide Usernames' setting */ -bool EntryModel::hideUsernames() const +bool EntryModel::isUsernamesHidden() const { return m_hideUsernames; } /** - * @author Fonic * Set state of 'Hide Usernames' setting and signal change */ -void EntryModel::setHideUsernames(const bool hide) +void EntryModel::setUsernamesHidden(const bool hide) { m_hideUsernames = hide; - emit hideUsernamesChanged(); + emit usernamesHiddenChanged(); } /** - * @author Fonic * Get current state of 'Hide Passwords' setting */ -bool EntryModel::hidePasswords() const +bool EntryModel::isPasswordsHidden() const { return m_hidePasswords; } /** - * @author Fonic * Set state of 'Hide Passwords' setting and signal change */ -void EntryModel::setHidePasswords(const bool hide) +void EntryModel::setPasswordsHidden(const bool hide) { m_hidePasswords = hide; - emit hidePasswordsChanged(); + emit passwordsHiddenChanged(); } /** - * @author Fonic * Toggle state of 'Hide Usernames' setting */ -void EntryModel::toggleHideUsernames(const bool hide) +void EntryModel::toggleUsernamesHidden(const bool hide) { - setHideUsernames(hide); + setUsernamesHidden(hide); } /** - * @author Fonic * Toggle state of 'Hide Passwords' setting */ -void EntryModel::toggleHidePasswords(const bool hide) +void EntryModel::togglePasswordsHidden(const bool hide) { - setHidePasswords(hide); + setPasswordsHidden(hide); } diff --git a/src/gui/entry/EntryModel.h b/src/gui/entry/EntryModel.h index 75243e52f..7c668a5f6 100644 --- a/src/gui/entry/EntryModel.h +++ b/src/gui/entry/EntryModel.h @@ -28,11 +28,6 @@ class EntryModel : public QAbstractTableModel Q_OBJECT public: - /** - * @author Fonic - * Add entries for additional columns 'Password', 'Notes', 'Expires', - * 'Created', 'Modified', 'Accessed' and 'Attachments' - */ enum ModelColumn { ParentGroup = 0, @@ -65,42 +60,21 @@ public: void setEntryList(const QList& entries); - /** - * @author Fonic - * Methods to get/set state of 'Hide Usernames' and 'Hide Passwords' - * settings - */ - bool hideUsernames() const; - void setHideUsernames(const bool hide); - bool hidePasswords() const; - void setHidePasswords(const bool hide); + bool isUsernamesHidden() const; + void setUsernamesHidden(const bool hide); + bool isPasswordsHidden() const; + void setPasswordsHidden(const bool hide); signals: - /** - * @author Fonic - * Signals to notify about list/search mode switches (NOTE: previously - * named 'switchedToGroupMode'/'switchedToEntryListMode') - */ void switchedToListMode(); void switchedToSearchMode(); - - /** - * @author Fonic - * Signals to notify about state changes of 'Hide Usernames' and 'Hide - * Passwords' settings - */ - void hideUsernamesChanged(); - void hidePasswordsChanged(); + void usernamesHiddenChanged(); + void passwordsHiddenChanged(); public slots: void setGroup(Group* group); - - /** - * @author Fonic - * Slots to toggle state of 'Hide Usernames' and 'Hide Passwords' settings - */ - void toggleHideUsernames(const bool hide); - void toggleHidePasswords(const bool hide); + void toggleUsernamesHidden(const bool hide); + void togglePasswordsHidden(const bool hide); private slots: void entryAboutToAdd(Entry* entry); @@ -118,33 +92,11 @@ private: QList m_orgEntries; QList m_allGroups; - /** - * @author Fonic - * Properties to store state of 'Hide Usernames' and 'Hide Passwords' - * settings - */ bool m_hideUsernames; bool m_hidePasswords; - /** - * @author Fonic - * Constant string used to display hidden content in columns 'Username' - * and 'Password' - */ static const QString HiddenContentDisplay; - - /** - * @author Fonic - * Date format used to display dates in columns 'Expires', 'Created', - * 'Modified' and 'Accessed' - */ static const Qt::DateFormat DateFormat; - - /** - * @author Fonic - * Constant string used to display header and data of column 'Paper- - * clip' - */ static const QString PaperClipDisplay; }; diff --git a/src/gui/entry/EntryView.cpp b/src/gui/entry/EntryView.cpp index e52944e55..ceb3c64a0 100644 --- a/src/gui/entry/EntryView.cpp +++ b/src/gui/entry/EntryView.cpp @@ -19,26 +19,10 @@ #include #include -/** - * @author Fonic - * Add include required for header context menu - */ #include #include "gui/SortFilterHideProxyModel.h" -/** - * @author Fonic - * - * TODO NOTE: - * Currently, 'zombie' columns which are not hidden but have width == 0 - * (rendering them invisible) may appear. This is caused by DatabaseWidget - * StateSync. Corresponding checks/workarounds may be removed once sync - * code is updated accordingly - * -> relevant code pieces: if (header()->sectionSize(...) == 0) { ... } - * - */ - EntryView::EntryView(QWidget* parent) : QTreeView(parent) , m_model(new EntryModel(this)) @@ -49,12 +33,7 @@ EntryView::EntryView(QWidget* parent) m_sortModel->setDynamicSortFilter(true); m_sortModel->setSortLocaleAware(true); m_sortModel->setSortCaseSensitivity(Qt::CaseInsensitive); - /** - * @author Fonic - * Set Qt::UserRole as sort role - * -> refer to 'if (role == Qt::UserRole)', EntryModel.cpp, EntryModel:: - * data() for details - */ + // Use Qt::UserRole as sort role, see EntryModel::data() m_sortModel->setSortRole(Qt::UserRole); QTreeView::setModel(m_sortModel); @@ -70,49 +49,31 @@ EntryView::EntryView(QWidget* parent) connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(emitEntryActivated(QModelIndex))); connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SIGNAL(entrySelectionChanged())); - /** - * @author Fonic - * Connect signals to get notified about list/search mode switches (NOTE: - * previously named 'switch[ed]ToGroupMode'/'switch[ed]ToEntryListMode') - */ connect(m_model, SIGNAL(switchedToListMode()), SLOT(switchToListMode())); connect(m_model, SIGNAL(switchedToSearchMode()), SLOT(switchToSearchMode())); - /** - * @author Fonic - * Connect signals to notify about changes of view state when state of - * 'Hide Usernames'/'Hide Passwords' settings changes in model - */ - connect(m_model, SIGNAL(hideUsernamesChanged()), SIGNAL(viewStateChanged())); - connect(m_model, SIGNAL(hidePasswordsChanged()), SIGNAL(viewStateChanged())); - + connect(m_model, SIGNAL(usernamesHiddenChanged()), SIGNAL(viewStateChanged())); + connect(m_model, SIGNAL(passwordsHiddenChanged()), SIGNAL(viewStateChanged())); connect(this, SIGNAL(clicked(QModelIndex)), SLOT(emitEntryPressed(QModelIndex))); - /** - * @author Fonic - * Create header context menu: - * - Actions to toggle state of 'Hide Usernames'/'Hide Passwords' settings - * - Actions to toggle column visibility, with each action carrying 'its' - * column index as data - * - Actions to resize columns - * - Action to reset view to defaults - */ m_headerMenu = new QMenu(this); m_headerMenu->setTitle(tr("Customize View")); m_headerMenu->addSection(tr("Customize View")); - m_hideUsernamesAction = m_headerMenu->addAction(tr("Hide Usernames"), m_model, SLOT(toggleHideUsernames(bool))); + m_hideUsernamesAction = m_headerMenu->addAction(tr("Hide Usernames"), m_model, SLOT(toggleUsernamesHidden(bool))); m_hideUsernamesAction->setCheckable(true); - m_hidePasswordsAction = m_headerMenu->addAction(tr("Hide Passwords"), m_model, SLOT(toggleHidePasswords(bool))); + m_hidePasswordsAction = m_headerMenu->addAction(tr("Hide Passwords"), m_model, SLOT(togglePasswordsHidden(bool))); m_hidePasswordsAction->setCheckable(true); m_headerMenu->addSeparator(); + // Actions to toggle column visibility, each carrying the corresponding + // colummn index as data m_columnActions = new QActionGroup(this); m_columnActions->setExclusive(false); - for (int colidx = 1; colidx < header()->count(); colidx++) { - QString caption = m_model->headerData(colidx, Qt::Horizontal, Qt::DisplayRole).toString(); + for (int columnIndex = 1; columnIndex < header()->count(); ++columnIndex) { + QString caption = m_model->headerData(columnIndex, Qt::Horizontal, Qt::DisplayRole).toString(); QAction* action = m_headerMenu->addAction(caption); action->setCheckable(true); - action->setData(colidx); + action->setData(columnIndex); m_columnActions->addAction(action); } connect(m_columnActions, SIGNAL(triggered(QAction*)), this, SLOT(toggleColumnVisibility(QAction*))); @@ -123,17 +84,8 @@ EntryView::EntryView(QWidget* parent) m_headerMenu->addSeparator(); m_headerMenu->addAction(tr("Reset to defaults"), this, SLOT(resetViewToDefaults())); - /** - * @author Fonic - * Configure header: - * - Set default section size - * - Disable stretching of last section (interferes with fitting columns - * to window) - * - Associate with context menu - * - Connect signals to notify about changes of view state when state - * of header changes - */ header()->setDefaultSectionSize(100); + // Stretching of last section interferes with fitting columns to window header()->setStretchLastSection(false); header()->setContextMenuPolicy(Qt::CustomContextMenu); connect(header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showHeaderMenu(QPoint))); @@ -142,35 +94,17 @@ EntryView::EntryView(QWidget* parent) connect(header(), SIGNAL(sectionResized(int, int, int)), this, SIGNAL(viewStateChanged())); connect(header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), this, SIGNAL(viewStateChanged())); - /** - * @author Fonic - * Fit columns to window - * - * TODO: - * Not working as expected, columns will end up being very small, most - * likely due to EntryView not being sized properly at this time. Find - * a way to make this work by analizing when/where EntryView is created - */ + // TODO: not working as expected, columns will end up being very small, + // most likely due to the widget not being sized properly at this time //fitColumnsToWindow(); - /** - * @author Fonic - * Configure default search view state and save for later use - */ + // Configure default search view state and save for later use header()->showSection(EntryModel::ParentGroup); m_sortModel->sort(EntryModel::ParentGroup, Qt::AscendingOrder); sortByColumn(EntryModel::ParentGroup, Qt::AscendingOrder); m_defaultSearchViewState = header()->saveState(); - /** - * @author Fonic - * Configure default list view state and save for later use - * - * NOTE: - * Default list view is intentionally configured last since this is the - * view that's supposed to be active after initialization as m_inSearchMode - * is initialized with 'false' - */ + // Configure default list view state and save for later use header()->hideSection(EntryModel::ParentGroup); m_sortModel->sort(EntryModel::Title, Qt::AscendingOrder); sortByColumn(EntryModel::Title, Qt::AscendingOrder); @@ -207,8 +141,7 @@ void EntryView::setFirstEntryActive() if (m_model->rowCount() > 0) { QModelIndex index = m_sortModel->mapToSource(m_sortModel->index(0, 0)); setCurrentEntry(m_model->entryFromIndex(index)); - } - else { + } else { emit entrySelectionChanged(); } } @@ -241,8 +174,7 @@ Entry* EntryView::currentEntry() QModelIndexList list = selectionModel()->selectedRows(); if (list.size() == 1) { return m_model->entryFromIndex(m_sortModel->mapToSource(list.first())); - } - else { + } else { return nullptr; } } @@ -262,58 +194,38 @@ Entry* EntryView::entryFromIndex(const QModelIndex& index) { if (index.isValid()) { return m_model->entryFromIndex(m_sortModel->mapToSource(index)); - } - else { + } else { return nullptr; } } /** - * @author Fonic - * Switch to list mode, i.e. list entries of group (NOTE: previously named - * 'switchToGroupMode') + * Switch to list mode, i.e. list entries of group */ void EntryView::switchToListMode() { - /* Check if already in this mode */ if (!m_inSearchMode) { return; } - /** - * Use header()->hideSection() instead of m_sortModel->hideColumn() as - * the latter messes up column indices, interfering with code relying on - * proper indices - */ header()->hideSection(EntryModel::ParentGroup); - m_inSearchMode = false; } /** - * @author Fonic - * Switch to search mode, i.e. list search results (NOTE: previously named - * 'switchToEntryListMode') + * Switch to search mode, i.e. list search results */ void EntryView::switchToSearchMode() { - /* Check if already in this mode */ if (m_inSearchMode) { return; } - /* - * Use header()->showSection() instead of m_sortModel->hideColumn() as - * the latter messes up column indices, interfering with code relying on - * proper indices - */ header()->showSection(EntryModel::ParentGroup); - /* - * Always set sorting to column 'Group', as it does not feel right to have - * the last known sort configuration of search view restored by 'Database - * WidgetStateSync', which is what happens without this - */ + // Always set sorting to column 'Group', as it does not feel right to + // have the last known sort configuration of search view restored by + // 'DatabaseWidgetStateSync', which is what happens without this m_sortModel->sort(EntryModel::ParentGroup, Qt::AscendingOrder); sortByColumn(EntryModel::ParentGroup, Qt::AscendingOrder); @@ -321,46 +233,41 @@ void EntryView::switchToSearchMode() } /** - * @author Fonic * Get current state of 'Hide Usernames' setting (NOTE: just pass-through for * m_model) */ -bool EntryView::hideUsernames() const +bool EntryView::isUsernamesHidden() const { - return m_model->hideUsernames(); + return m_model->isUsernamesHidden(); } /** - * @author Fonic * Set state of 'Hide Usernames' setting (NOTE: just pass-through for m_model) */ -void EntryView::setHideUsernames(const bool hide) +void EntryView::setUsernamesHidden(const bool hide) { - m_model->setHideUsernames(hide); + m_model->setUsernamesHidden(hide); } /** - * @author Fonic * Get current state of 'Hide Passwords' setting (NOTE: just pass-through for * m_model) */ -bool EntryView::hidePasswords() const +bool EntryView::isPasswordsHidden() const { - return m_model->hidePasswords(); + return m_model->isPasswordsHidden(); } /** - * @author Fonic * Set state of 'Hide Passwords' setting (NOTE: just pass-through for m_model) */ -void EntryView::setHidePasswords(const bool hide) +void EntryView::setPasswordsHidden(const bool hide) { - m_model->setHidePasswords(hide); + m_model->setPasswordsHidden(hide); } /** - * @author Fonic - * Get current state of view + * Get current view state */ QByteArray EntryView::viewState() const { @@ -368,8 +275,7 @@ QByteArray EntryView::viewState() const } /** - * @author Fonic - * Set state of entry view view + * Set view state */ bool EntryView::setViewState(const QByteArray& state) const { @@ -377,70 +283,59 @@ bool EntryView::setViewState(const QByteArray& state) const } /** - * @author Fonic * Sync checkable menu actions to current state and display header context * menu at specified position */ void EntryView::showHeaderMenu(const QPoint& position) { - /* Sync checked state of menu actions to current state of view */ - m_hideUsernamesAction->setChecked(m_model->hideUsernames()); - m_hidePasswordsAction->setChecked(m_model->hidePasswords()); - foreach (QAction *action, m_columnActions->actions()) { + m_hideUsernamesAction->setChecked(m_model->isUsernamesHidden()); + m_hidePasswordsAction->setChecked(m_model->isPasswordsHidden()); + const QList actions = m_columnActions->actions(); + for (auto& action : actions) { + Q_ASSERT(static_cast(action->data().type()) == QMetaType::Int); if (static_cast(action->data().type()) != QMetaType::Int) { - Q_ASSERT(false); continue; } - int colidx = action->data().toInt(); - bool hidden = header()->isSectionHidden(colidx) || (header()->sectionSize(colidx) == 0); + int columnIndex = action->data().toInt(); + bool hidden = header()->isSectionHidden(columnIndex) || (header()->sectionSize(columnIndex) == 0); action->setChecked(!hidden); } - /* Display menu */ m_headerMenu->popup(mapToGlobal(position)); } /** - * @author Fonic * Toggle visibility of column referenced by triggering action */ void EntryView::toggleColumnVisibility(QAction *action) { - /* - * Verify action carries a column index as data. Since QVariant.toInt() - * below will accept anything that's interpretable as int, perform a type - * check here to make sure data actually IS int - */ + // Verify action carries a column index as data. Since QVariant.toInt() + // below will accept anything that's interpretable as int, perform a type + // check here to make sure data actually IS int + Q_ASSERT(static_cast(action->data().type()) == QMetaType::Int); if (static_cast(action->data().type()) != QMetaType::Int) { - Q_ASSERT(false); return; } - /* - * Toggle column visibility. Visible columns will only be hidden if at - * least one visible column remains, as the table header will disappear - * entirely when all columns are hidden, rendering the context menu in- - * accessible - */ - int colidx = action->data().toInt(); + // Toggle column visibility. Visible columns will only be hidden if at + // least one visible column remains, as the table header will disappear + // entirely when all columns are hidden + int columnIndex = action->data().toInt(); if (action->isChecked()) { - header()->showSection(colidx); - if (header()->sectionSize(colidx) == 0) { - header()->resizeSection(colidx, header()->defaultSectionSize()); + header()->showSection(columnIndex); + if (header()->sectionSize(columnIndex) == 0) { + header()->resizeSection(columnIndex, header()->defaultSectionSize()); } + return; } - else { - if ((header()->count() - header()->hiddenSectionCount()) > 1) { - header()->hideSection(colidx); - } - else { - action->setChecked(true); - } + if ((header()->count() - header()->hiddenSectionCount()) > 1) { + header()->hideSection(columnIndex); + return; } + action->setChecked(true); } /** - * @author Fonic * Resize columns to fit all visible columns within the available space * * NOTE: @@ -461,69 +356,60 @@ void EntryView::fitColumnsToWindow() } /** - * @author Fonic * Resize columns to fit current table contents, i.e. make all contents * entirely visible */ void EntryView::fitColumnsToContents() { - /* Resize columns to fit contents */ + // Resize columns to fit contents header()->resizeSections(QHeaderView::ResizeToContents); - /* - * Determine total width of currently visible columns. If there is - * still some space available on the header, equally distribute it to - * visible columns and add remaining fraction to last visible column - */ + // Determine total width of currently visible columns. If there is + // still some space available on the header, equally distribute it to + // visible columns and add remaining fraction to last visible column int width = 0; - for (int colidx = 0; colidx < header()->count(); colidx++) { - if (!header()->isSectionHidden(colidx)) { - width += header()->sectionSize(colidx); + for (int columnIndex = 0; columnIndex < header()->count(); ++columnIndex) { + if (!header()->isSectionHidden(columnIndex)) { + width += header()->sectionSize(columnIndex); } } int visible = header()->count() - header()->hiddenSectionCount(); int avail = header()->width() - width; - if ((visible > 0) && (avail > 0)) { - int add = avail / visible; - width = 0; - int last = 0; - for (int colidx = 0; colidx < header()->count(); colidx++) { - if (!header()->isSectionHidden(colidx)) { - header()->resizeSection(colidx, header()->sectionSize(colidx) + add); - width += header()->sectionSize(colidx); - if (header()->visualIndex(colidx) > last) { - last = header()->visualIndex(colidx); - } + if ((visible <= 0) || (avail <= 0)) { + return; + } + int add = avail / visible; + width = 0; + int last = 0; + for (int columnIndex = 0; columnIndex < header()->count(); ++columnIndex) { + if (!header()->isSectionHidden(columnIndex)) { + header()->resizeSection(columnIndex, header()->sectionSize(columnIndex) + add); + width += header()->sectionSize(columnIndex); + if (header()->visualIndex(columnIndex) > last) { + last = header()->visualIndex(columnIndex); } } - header()->resizeSection(header()->logicalIndex(last), header()->sectionSize(last) + (header()->width() - width)); } + header()->resizeSection(header()->logicalIndex(last), header()->sectionSize(last) + (header()->width() - width)); - /* - * This should not be necessary due to use of header()->resizeSection, - * but lets do it anyway for the sake of completeness - */ + // Shouldn't be necessary due to use of header()->resizeSection, but + // lets just do it anyway for the sake of completeness emit viewStateChanged(); } /** - * @author Fonic * Reset view to defaults */ void EntryView::resetViewToDefaults() { - /* Reset state of 'Hide Usernames'/'Hide Passwords' settings */ - m_model->setHideUsernames(false); - m_model->setHidePasswords(true); + m_model->setUsernamesHidden(false); + m_model->setPasswordsHidden(true); - /* Reset columns (size, order, sorting etc.) */ if (m_inSearchMode) { header()->restoreState(m_defaultSearchViewState); - } - else { + } else { header()->restoreState(m_defaultListViewState); } - /* Nicely fitting columns to window feels like a sane default */ fitColumnsToWindow(); } diff --git a/src/gui/entry/EntryView.h b/src/gui/entry/EntryView.h index 5b99bec99..1bea22f49 100644 --- a/src/gui/entry/EntryView.h +++ b/src/gui/entry/EntryView.h @@ -26,10 +26,6 @@ class Entry; class EntryModel; class Group; class SortFilterHideProxyModel; -/** - * @author Fonic - * Add forward declaration for QActionGroup - */ class QActionGroup; class EntryView : public QTreeView @@ -46,21 +42,10 @@ public: bool inSearchMode(); int numberOfSelectedEntries(); void setFirstEntryActive(); - - /** - * @author Fonic - * Methods to get/set state of 'Hide Usernames'/'Hide Passwords' settings - * (NOTE: these are just pass-through methods to avoid exposing entry model) - */ - bool hideUsernames() const; - void setHideUsernames(const bool hide); - bool hidePasswords() const; - void setHidePasswords(const bool hide); - - /** - * @author Fonic - * Methods to get/set state of view - */ + bool isUsernamesHidden() const; + void setUsernamesHidden(const bool hide); + bool isPasswordsHidden() const; + void setPasswordsHidden(const bool hide); QByteArray viewState() const; bool setViewState(const QByteArray& state) const; @@ -71,10 +56,6 @@ signals: void entryActivated(Entry* entry, EntryModel::ModelColumn column); void entryPressed(Entry* entry); void entrySelectionChanged(); - /** - * @author Fonic - * Signal to notify about changes of view state - */ void viewStateChanged(); protected: @@ -83,18 +64,8 @@ protected: private slots: void emitEntryActivated(const QModelIndex& index); void emitEntryPressed(const QModelIndex& index); - /** - * @author Fonic - * Methods to switch to list/search mode (NOTE: previously named 'switch - * ToGroupMode'/'switchToEntryListMode') - */ void switchToListMode(); void switchToSearchMode(); - - /** - * @author Fonic - * Slots for header context menu and actions - */ void showHeaderMenu(const QPoint& position); void toggleColumnVisibility(QAction *action); void fitColumnsToWindow(); @@ -106,16 +77,9 @@ private: SortFilterHideProxyModel* const m_sortModel; bool m_inSearchMode; - /** - * @author Fonic - * Properties to store default view states used by resetViewToDefaults() - */ QByteArray m_defaultListViewState; QByteArray m_defaultSearchViewState; - /** - * @author Fonic - * Properties to store header context menu and actions - */ + QMenu* m_headerMenu; QAction* m_hideUsernamesAction; QAction* m_hidePasswordsAction; diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 00b957241..a663b30a3 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -686,21 +686,6 @@ void TestGui::testSearch() QTRY_COMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); } -/** - * @author Fonic - * Update clicks within entry view referencing column indices to account - * for changed column indices due to new way of showing/hiding column Entry - * Model::ParentGroup. This column now has fixed index 0 wether it's shown - * or hidden, thus all indices need to be shifted by +1 when not in search - * mode (which is the case within this entire method) - * - * Old: - * clickIndex(entryView->model()->index(row, column), button); - * - * New: - * clickIndex(entryView->model()->index(row, column+1), button); - * - */ void TestGui::testDeleteEntry() { // Add canned entries for consistent testing