Add entry view column for password strength

* Closes #4216

Reduced to three-tiered rating system and fixed column implementation. Hide password strength indicator in entry view if excluded from reports.

Introduce password health caching to prevent unnecessary calculations.
This commit is contained in:
Ojas Anand 2021-02-26 16:43:23 -05:00 committed by Jonathan White
parent c9c19d043f
commit 022154462e
23 changed files with 213 additions and 187 deletions

View file

@ -20,12 +20,42 @@
#include <QAccessible>
#include <QHeaderView>
#include <QKeyEvent>
#include <QMenu>
#include <QPainter>
#include <QShortcut>
#include <QStyledItemDelegate>
#include "gui/SortFilterHideProxyModel.h"
#define ICON_ONLY_SECTION_SIZE 26
class PasswordStrengthItemDelegate : public QStyledItemDelegate
{
public:
explicit PasswordStrengthItemDelegate(QObject* parent)
: QStyledItemDelegate(parent){};
void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const override
{
QStyledItemDelegate::initStyleOption(option, index);
auto value = index.data(Qt::DecorationRole);
if (value.isValid() && value.type() == QVariant::Color && option->rect.width() > 0) {
// Rebuild the password strength icon to add a dark border
QColor pen(Qt::black);
if (option->widget) {
pen = option->widget->palette().color(QPalette::Shadow);
}
auto size = option->decorationSize;
QImage image(size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
QPainter p(&image);
p.setBrush(value.value<QColor>());
p.setPen(pen);
p.drawRect(0, 0, size.width() - 1, size.height() - 1);
option->icon = QIcon(QPixmap::fromImage(image));
}
}
};
EntryView::EntryView(QWidget* parent)
: QTreeView(parent)
, m_model(new EntryModel(this))
@ -41,6 +71,7 @@ EntryView::EntryView(QWidget* parent)
// Use Qt::UserRole as sort role, see EntryModel::data()
m_sortModel->setSortRole(Qt::UserRole);
QTreeView::setModel(m_sortModel);
QTreeView::setItemDelegateForColumn(EntryModel::PasswordStrength, new PasswordStrengthItemDelegate(this));
setUniformRowHeights(true);
setRootIsDecorated(false);
@ -52,10 +83,10 @@ EntryView::EntryView(QWidget* parent)
// QAbstractItemView::startDrag() uses this property as the default drag action
setDefaultDropAction(Qt::MoveAction);
// clang-format off
connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(emitEntryActivated(QModelIndex)));
connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(emitEntrySelectionChanged()));
// clang-format on
connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, [this] {
emit entrySelectionChanged(currentEntry());
});
new QShortcut(Qt::CTRL + Qt::Key_F10, this, SLOT(contextMenuShortcutPressed()), nullptr, Qt::WidgetShortcut);
@ -68,13 +99,11 @@ EntryView::EntryView(QWidget* parent)
for (int visualIndex = 1; visualIndex < header()->count(); ++visualIndex) {
int logicalIndex = header()->logicalIndex(visualIndex);
QString caption = m_model->headerData(logicalIndex, Qt::Horizontal, Qt::DisplayRole).toString();
if (logicalIndex == EntryModel::Paperclip) {
caption = tr("Has attachments", "Entry attachment icon toggle");
} else if (logicalIndex == EntryModel::Totp) {
caption = tr("Has TOTP", "Entry TOTP icon toggle");
if (caption.isEmpty()) {
caption = m_model->headerData(logicalIndex, Qt::Horizontal, Qt::ToolTipRole).toString();
}
QAction* action = m_headerMenu->addAction(caption);
auto action = m_headerMenu->addAction(caption);
action->setCheckable(true);
action->setData(logicalIndex);
m_columnActions->addAction(action);
@ -82,7 +111,8 @@ EntryView::EntryView(QWidget* parent)
connect(m_columnActions, SIGNAL(triggered(QAction*)), this, SLOT(toggleColumnVisibility(QAction*)));
connect(header(), &QHeaderView::sortIndicatorChanged, [this](int index, Qt::SortOrder order) {
Q_UNUSED(order)
header()->setSortIndicatorShown(index != EntryModel::Paperclip && index != EntryModel::Totp);
header()->setSortIndicatorShown(index != EntryModel::Paperclip && index != EntryModel::Totp
&& index != EntryModel::PasswordStrength);
});
m_headerMenu->addSeparator();
@ -101,8 +131,6 @@ EntryView::EntryView(QWidget* parent)
connect(header(), SIGNAL(sectionMoved(int, int, int)), SIGNAL(viewStateChanged()));
connect(header(), SIGNAL(sectionResized(int, int, int)), SIGNAL(viewStateChanged()));
connect(header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), SLOT(sortIndicatorChanged(int, Qt::SortOrder)));
// clang-format off
}
void EntryView::contextMenuShortcutPressed()
@ -130,10 +158,10 @@ void EntryView::sortIndicatorChanged(int logicalIndex, Qt::SortOrder order)
// do not emit any signals, header()->setSortIndicator recursively calls this
// function and the signals are emitted in the else part
} else {
// call emitEntrySelectionChanged even though the selection did not really change
// emit entrySelectionChanged even though the selection did not really change
// this triggers the evaluation of the menu activation and anyway, the position
// of the selected entry within the widget did change
emitEntrySelectionChanged();
emit entrySelectionChanged(currentEntry());
emit viewStateChanged();
}
}
@ -228,11 +256,6 @@ void EntryView::emitEntryActivated(const QModelIndex& index)
emit entryActivated(entry, static_cast<EntryModel::ModelColumn>(m_sortModel->mapToSource(index).column()));
}
void EntryView::emitEntrySelectionChanged()
{
emit entrySelectionChanged(currentEntry());
}
void EntryView::setModel(QAbstractItemModel* model)
{
Q_UNUSED(model);
@ -391,18 +414,15 @@ void EntryView::fitColumnsToContents()
}
/**
* Mark icon-only columns as fixed and resize them to their minimum section size.
* Mark icon-only columns as fixed and resize them to icon-only section size
*/
void EntryView::resetFixedColumns()
{
if (!isColumnHidden(EntryModel::Paperclip)) {
header()->setSectionResizeMode(EntryModel::Paperclip, QHeaderView::Fixed);
header()->resizeSection(EntryModel::Paperclip, header()->minimumSectionSize());
}
if (!isColumnHidden(EntryModel::Totp)) {
header()->setSectionResizeMode(EntryModel::Totp, QHeaderView::Fixed);
header()->resizeSection(EntryModel::Totp, header()->minimumSectionSize());
for (const auto& col : {EntryModel::Paperclip, EntryModel::Totp, EntryModel::PasswordStrength}) {
if (!isColumnHidden(col)) {
header()->setSectionResizeMode(col, QHeaderView::Fixed);
header()->resizeSection(col, ICON_ONLY_SECTION_SIZE);
}
}
}
@ -431,6 +451,7 @@ void EntryView::resetViewToDefaults()
header()->hideSection(EntryModel::Accessed);
header()->hideSection(EntryModel::Attachments);
header()->hideSection(EntryModel::Size);
header()->hideSection(EntryModel::PasswordStrength);
// Reset column order to logical indices
for (int i = 0; i < header()->count(); ++i) {