mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-07-24 15:25:31 -04:00
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:
parent
c9c19d043f
commit
022154462e
23 changed files with 213 additions and 187 deletions
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue